diff options
59 files changed, 2463 insertions, 673 deletions
diff --git a/chrome/browser/extensions/api/instance_id/instance_id_api.cc b/chrome/browser/extensions/api/instance_id/instance_id_api.cc index c57b4ed..6707f4c 100644 --- a/chrome/browser/extensions/api/instance_id/instance_id_api.cc +++ b/chrome/browser/extensions/api/instance_id/instance_id_api.cc @@ -19,6 +19,7 @@ namespace { // Error messages. const char kInvalidParameter[] = "Function was called with invalid parameters."; const char kDisabled[] = "Instance ID is currently disabled."; +const char kAsyncOperationPending[] = "Asynchronous operation is pending."; const char kNetworkError[] = "Network error occurred."; const char kServerError[] = "Server error occurred."; const char kUnknownError[] = "Unknown error occurred."; @@ -29,6 +30,8 @@ const char* InstanceIDResultToError(instance_id::InstanceID::Result result) { return kInvalidParameter; case instance_id::InstanceID::DISABLED: return kDisabled; + case instance_id::InstanceID::ASYNC_OPERATION_PENDING: + return kAsyncOperationPending; case instance_id::InstanceID::NETWORK_ERROR: return kNetworkError; case instance_id::InstanceID::SERVER_ERROR: diff --git a/chrome/browser/extensions/api/instance_id/instance_id_api.h b/chrome/browser/extensions/api/instance_id/instance_id_api.h index 6ae1f9d0..4665b47 100644 --- a/chrome/browser/extensions/api/instance_id/instance_id_api.h +++ b/chrome/browser/extensions/api/instance_id/instance_id_api.h @@ -92,7 +92,7 @@ class InstanceIDGetTokenFunction : public InstanceIDApiFunction { class InstanceIDDeleteTokenFunction : public InstanceIDApiFunction { public: - DECLARE_EXTENSION_FUNCTION("instanceID.DeleteToken", INSTANCEID_DELETETOKEN); + DECLARE_EXTENSION_FUNCTION("instanceID.deleteToken", INSTANCEID_DELETETOKEN); InstanceIDDeleteTokenFunction(); diff --git a/chrome/test/data/extensions/api_test/instance_id/delete_token/delete_token.js b/chrome/test/data/extensions/api_test/instance_id/delete_token/delete_token.js index cd3db4a..dbf1719 100644 --- a/chrome/test/data/extensions/api_test/instance_id/delete_token/delete_token.js +++ b/chrome/test/data/extensions/api_test/instance_id/delete_token/delete_token.js @@ -110,6 +110,43 @@ function deleteTokenAfterGetToken() { ); } +var oldToken; +function getTokenDeleteTokeAndGetToken() { + chrome.instanceID.getToken( + {"authorizedEntity": "1", "scope": "GCM"}, + function(token) { + if (chrome.runtime.lastError || !token) { + chrome.test.fail( + "chrome.runtime.lastError was set or token was empty."); + return; + } + oldToken = token; + chrome.instanceID.deleteToken( + {"authorizedEntity": "1", "scope": "GCM"}, + function() { + if (chrome.runtime.lastError) { + chrome.test.fail("chrome.runtime.lastError: " + + chrome.runtime.lastError.message); + return; + } + + chrome.instanceID.getToken( + {"authorizedEntity": "1", "scope": "GCM"}, + function(token) { + if (!token || token == oldToken) { + chrome.test.fail( + "Different token should be returned after deleteToken."); + return; + } + chrome.test.succeed(); + } + ); + } + ); + } + ); +} + chrome.test.runTests([ deleteTokenWithoutParameters, deleteTokenWithoutCallback, @@ -117,8 +154,7 @@ chrome.test.runTests([ deleteTokenWithInvalidAuthorizedEntity, deleteTokenWithoutScope, deleteTokenWithInvalidScope, - // TODO(jianli): To be enabled when deleteToken is implemented. - //deleteTokenBeforeGetToken, - //deleteTokenAfterGetToken, - //getTokenDeleteTokeAndGetToken, + deleteTokenBeforeGetToken, + deleteTokenAfterGetToken, + getTokenDeleteTokeAndGetToken, ]); diff --git a/chrome/test/data/extensions/api_test/instance_id/get_token/get_token.js b/chrome/test/data/extensions/api_test/instance_id/get_token/get_token.js index 7a0a1c9..a679564 100644 --- a/chrome/test/data/extensions/api_test/instance_id/get_token/get_token.js +++ b/chrome/test/data/extensions/api_test/instance_id/get_token/get_token.js @@ -127,7 +127,6 @@ chrome.test.runTests([ getTokenWithoutScope, getTokenWithInvalidScope, getTokenWithInvalidOptionValue, - // TODO(jianli): To be enabled when GetToken is implemented. - //getTokenWithoutOptions, - //getTokenWithValidOptions, + getTokenWithoutOptions, + getTokenWithValidOptions, ]); diff --git a/components/gcm_driver.gypi b/components/gcm_driver.gypi index b0c90b7..a52086c 100644 --- a/components/gcm_driver.gypi +++ b/components/gcm_driver.gypi @@ -54,6 +54,8 @@ 'gcm_driver/gcm_driver_desktop.h', 'gcm_driver/gcm_stats_recorder_impl.cc', 'gcm_driver/gcm_stats_recorder_impl.h', + 'gcm_driver/registration_info.cc', + 'gcm_driver/registration_info.h', 'gcm_driver/system_encryptor.cc', 'gcm_driver/system_encryptor.h', ], diff --git a/components/gcm_driver/BUILD.gn b/components/gcm_driver/BUILD.gn index e8c9a17..a75c3d5 100644 --- a/components/gcm_driver/BUILD.gn +++ b/components/gcm_driver/BUILD.gn @@ -39,6 +39,8 @@ static_library("gcm_driver") { "gcm_driver_desktop.h", "gcm_stats_recorder_impl.cc", "gcm_stats_recorder_impl.h", + "registration_info.cc", + "registration_info.h", "system_encryptor.cc", "system_encryptor.h", ] diff --git a/components/gcm_driver/fake_gcm_client.cc b/components/gcm_driver/fake_gcm_client.cc index 50a4598..b74faa5 100644 --- a/components/gcm_driver/fake_gcm_client.cc +++ b/components/gcm_driver/fake_gcm_client.cc @@ -74,27 +74,33 @@ void FakeGCMClient::Stop() { delegate_->OnDisconnected(); } -void FakeGCMClient::Register(const std::string& app_id, - const std::vector<std::string>& sender_ids) { +void FakeGCMClient::Register( + const linked_ptr<RegistrationInfo>& registration_info) { DCHECK(io_thread_->RunsTasksOnCurrentThread()); - std::string registration_id = GetRegistrationIdFromSenderIds(sender_ids); + GCMRegistrationInfo* gcm_registration_info = + GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); + DCHECK(gcm_registration_info); + + std::string registration_id = GetRegistrationIdFromSenderIds( + gcm_registration_info->sender_ids); base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&FakeGCMClient::RegisterFinished, weak_ptr_factory_.GetWeakPtr(), - app_id, + registration_info, registration_id)); } -void FakeGCMClient::Unregister(const std::string& app_id) { +void FakeGCMClient::Unregister( + const linked_ptr<RegistrationInfo>& registration_info) { DCHECK(io_thread_->RunsTasksOnCurrentThread()); base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&FakeGCMClient::UnregisterFinished, weak_ptr_factory_.GetWeakPtr(), - app_id)); + registration_info)); } void FakeGCMClient::Send(const std::string& app_id, @@ -138,14 +144,16 @@ void FakeGCMClient::UpdateHeartbeatTimer(scoped_ptr<base::Timer> timer) { } void FakeGCMClient::AddInstanceIDData(const std::string& app_id, - const std::string& instance_id_data) { + const std::string& instance_id, + const std::string& extra_data) { } void FakeGCMClient::RemoveInstanceIDData(const std::string& app_id) { } -std::string FakeGCMClient::GetInstanceIDData(const std::string& app_id) { - return std::string(); +void FakeGCMClient::GetInstanceIDData(const std::string& app_id, + std::string* instance_id, + std::string* extra_data) { } void FakeGCMClient::AddHeartbeatInterval(const std::string& scope, @@ -211,14 +219,18 @@ void FakeGCMClient::Started() { delegate_->OnConnected(net::IPEndPoint()); } -void FakeGCMClient::RegisterFinished(const std::string& app_id, - const std::string& registrion_id) { +void FakeGCMClient::RegisterFinished( + const linked_ptr<RegistrationInfo>& registration_info, + const std::string& registrion_id) { delegate_->OnRegisterFinished( - app_id, registrion_id, registrion_id.empty() ? SERVER_ERROR : SUCCESS); + registration_info, + registrion_id, + registrion_id.empty() ? SERVER_ERROR : SUCCESS); } -void FakeGCMClient::UnregisterFinished(const std::string& app_id) { - delegate_->OnUnregisterFinished(app_id, GCMClient::SUCCESS); +void FakeGCMClient::UnregisterFinished( + const linked_ptr<RegistrationInfo>& registration_info) { + delegate_->OnUnregisterFinished(registration_info, GCMClient::SUCCESS); } void FakeGCMClient::SendFinished(const std::string& app_id, diff --git a/components/gcm_driver/fake_gcm_client.h b/components/gcm_driver/fake_gcm_client.h index 155c342..73fa6b9 100644 --- a/components/gcm_driver/fake_gcm_client.h +++ b/components/gcm_driver/fake_gcm_client.h @@ -41,9 +41,9 @@ class FakeGCMClient : public GCMClient { Delegate* delegate) override; void Start(StartMode start_mode) override; void Stop() override; - void Register(const std::string& app_id, - const std::vector<std::string>& sender_ids) override; - void Unregister(const std::string& app_id) override; + void Register(const linked_ptr<RegistrationInfo>& registration_info) override; + void Unregister( + const linked_ptr<RegistrationInfo>& registration_info) override; void Send(const std::string& app_id, const std::string& receiver_id, const OutgoingMessage& message) override; @@ -57,9 +57,12 @@ class FakeGCMClient : public GCMClient { void SetLastTokenFetchTime(const base::Time& time) override; void UpdateHeartbeatTimer(scoped_ptr<base::Timer> timer) override; void AddInstanceIDData(const std::string& app_id, - const std::string& instance_id_data) override; + const std::string& instance_id, + const std::string& extra_data) override; void RemoveInstanceIDData(const std::string& app_id) override; - std::string GetInstanceIDData(const std::string& app_id) override; + void GetInstanceIDData(const std::string& app_id, + std::string* instance_id, + std::string* extra_data) override; void AddHeartbeatInterval(const std::string& scope, int interval_ms) override; void RemoveHeartbeatInterval(const std::string& scope) override; @@ -84,9 +87,11 @@ class FakeGCMClient : public GCMClient { // Called on IO thread. void DoStart(); void Started(); - void RegisterFinished(const std::string& app_id, - const std::string& registrion_id); - void UnregisterFinished(const std::string& app_id); + void RegisterFinished( + const linked_ptr<RegistrationInfo>& registration_info, + const std::string& registrion_id); + void UnregisterFinished( + const linked_ptr<RegistrationInfo>& registration_info); void SendFinished(const std::string& app_id, const OutgoingMessage& message); void MessageReceived(const std::string& app_id, const IncomingMessage& message); diff --git a/components/gcm_driver/fake_gcm_driver.cc b/components/gcm_driver/fake_gcm_driver.cc index 15b41fd..dfdd918 100644 --- a/components/gcm_driver/fake_gcm_driver.cc +++ b/components/gcm_driver/fake_gcm_driver.cc @@ -98,7 +98,7 @@ void FakeGCMDriver::SetLastTokenFetchTime(const base::Time& time) { void FakeGCMDriver::WakeFromSuspendForHeartbeat(bool wake) { } -InstanceIDStore* FakeGCMDriver::GetInstanceIDStore() { +InstanceIDHandler* FakeGCMDriver::GetInstanceIDHandler() { return NULL; } diff --git a/components/gcm_driver/fake_gcm_driver.h b/components/gcm_driver/fake_gcm_driver.h index 0c289f0..96bad7a 100644 --- a/components/gcm_driver/fake_gcm_driver.h +++ b/components/gcm_driver/fake_gcm_driver.h @@ -41,7 +41,7 @@ class FakeGCMDriver : public GCMDriver { base::Time GetLastTokenFetchTime() override; void SetLastTokenFetchTime(const base::Time& time) override; void WakeFromSuspendForHeartbeat(bool wake) override; - InstanceIDStore* GetInstanceIDStore() override; + InstanceIDHandler* GetInstanceIDHandler() override; void AddHeartbeatInterval(const std::string& scope, int interval_ms) override; void RemoveHeartbeatInterval(const std::string& scope) override; diff --git a/components/gcm_driver/gcm_client.h b/components/gcm_driver/gcm_client.h index 8672233..cb6042f 100644 --- a/components/gcm_driver/gcm_client.h +++ b/components/gcm_driver/gcm_client.h @@ -10,8 +10,10 @@ #include <vector> #include "base/basictypes.h" +#include "base/memory/linked_ptr.h" #include "base/memory/scoped_ptr.h" #include "components/gcm_driver/gcm_activity.h" +#include "components/gcm_driver/registration_info.h" template <class T> class scoped_refptr; @@ -163,18 +165,22 @@ class GCMClient { class Delegate { public: // Called when the registration completed successfully or an error occurs. - // |app_id|: application ID. + // |registration_info|: the specific information required for the + // registration. // |registration_id|: non-empty if the registration completed successfully. // |result|: the type of the error if an error occured, success otherwise. - virtual void OnRegisterFinished(const std::string& app_id, - const std::string& registration_id, - Result result) = 0; + virtual void OnRegisterFinished( + const linked_ptr<RegistrationInfo>& registration_info, + const std::string& registration_id, + Result result) = 0; // Called when the unregistration completed. - // |app_id|: application ID. + // |registration_info|: the specific information required for the + // registration. // |result|: result of the unregistration. - virtual void OnUnregisterFinished(const std::string& app_id, - GCMClient::Result result) = 0; + virtual void OnUnregisterFinished( + const linked_ptr<RegistrationInfo>& registration_info, + GCMClient::Result result) = 0; // Called when the message is scheduled to send successfully or an error // occurs. @@ -258,20 +264,24 @@ class GCMClient { // Stops using the GCM service. This will not erase the persisted data. virtual void Stop() = 0; - // Registers the application for GCM. Delegate::OnRegisterFinished will be - // called asynchronously upon completion. - // |app_id|: application ID. - // |sender_ids|: list of IDs of the servers that are allowed to send the - // messages to the application. These IDs are assigned by the - // Google API Console. - virtual void Register(const std::string& app_id, - const std::vector<std::string>& sender_ids) = 0; + // Registers with the server to access the provided service. + // Delegate::OnRegisterFinished will be called asynchronously upon completion. + // |registration_info|: the specific information required for the + // registration. For GCM, it will contain app id and + // sender IDs. For InstanceID, it will contain app_id, + // authorized entity and scope. + virtual void Register( + const linked_ptr<RegistrationInfo>& registration_info) = 0; - // Unregisters the application from GCM when it is uninstalled. + // Unregisters from the server to stop accessing the provided service. // Delegate::OnUnregisterFinished will be called asynchronously upon // completion. - // |app_id|: application ID. - virtual void Unregister(const std::string& app_id) = 0; + // |registration_info|: the specific information required for the + // registration. For GCM, it will contain app id (sender + // IDs can be ingored). For InstanceID, it will contain + // app id, authorized entity and scope. + virtual void Unregister( + const linked_ptr<RegistrationInfo>& registration_info) = 0; // Sends a message to a given receiver. Delegate::OnSendFinished will be // called asynchronously upon completion. @@ -312,14 +322,17 @@ class GCMClient { // Adds the Instance ID data for a specific app to the persistent store. virtual void AddInstanceIDData(const std::string& app_id, - const std::string& instance_id_data) = 0; + const std::string& instance_id, + const std::string& extra_data) = 0; // Removes the Instance ID data for a specific app from the persistent store. virtual void RemoveInstanceIDData(const std::string& app_id) = 0; // Retrieves the Instance ID data for a specific app from the persistent // store. - virtual std::string GetInstanceIDData(const std::string& app_id) = 0; + virtual void GetInstanceIDData(const std::string& app_id, + std::string* instance_id, + std::string* extra_data) = 0; // Gets and sets custom heartbeat interval for the MCS connection. // |scope| is used to identify the component that requests a custom interval diff --git a/components/gcm_driver/gcm_client_impl.cc b/components/gcm_driver/gcm_client_impl.cc index 448e416..13d7b62 100644 --- a/components/gcm_driver/gcm_client_impl.cc +++ b/components/gcm_driver/gcm_client_impl.cc @@ -23,7 +23,11 @@ #include "google_apis/gcm/base/mcs_util.h" #include "google_apis/gcm/engine/checkin_request.h" #include "google_apis/gcm/engine/connection_factory_impl.h" +#include "google_apis/gcm/engine/gcm_registration_request_handler.h" #include "google_apis/gcm/engine/gcm_store_impl.h" +#include "google_apis/gcm/engine/gcm_unregistration_request_handler.h" +#include "google_apis/gcm/engine/instance_id_delete_token_request_handler.h" +#include "google_apis/gcm/engine/instance_id_get_token_request_handler.h" #include "google_apis/gcm/monitoring/gcm_stats_recorder.h" #include "google_apis/gcm/protocol/checkin.pb.h" #include "google_apis/gcm/protocol/mcs.pb.h" @@ -67,6 +71,7 @@ enum ResetStoreError { RESET_STORE_ERROR_COUNT }; +const char kGCMScope[] = "GCM"; const int kMaxRegistrationRetries = 5; const char kMessageTypeDataMessage[] = "gcm"; const char kMessageTypeDeletedMessagesKey[] = "deleted_messages"; @@ -169,6 +174,39 @@ MessageType DecodeMessageType(const std::string& value) { return UNKNOWN; } +int ConstructGCMVersion(const std::string& chrome_version) { + // Major Chrome version is passed as GCM version. + size_t pos = chrome_version.find('.'); + if (pos == std::string::npos) { + NOTREACHED(); + return 0; + } + + int gcm_version = 0; + base::StringToInt( + base::StringPiece(chrome_version.c_str(), pos), &gcm_version); + return gcm_version; +} + +std::string SerializeInstanceIDData(const std::string& instance_id, + const std::string& extra_data) { + DCHECK(!instance_id.empty() && !extra_data.empty()); + DCHECK(instance_id.find(',') == std::string::npos); + return instance_id + "," + extra_data; +} + +bool DeserializeInstanceIDData(const std::string& serialized_data, + std::string* instance_id, + std::string* extra_data) { + DCHECK(instance_id && extra_data); + std::size_t pos = serialized_data.find(','); + if (pos == std::string::npos) + return false; + *instance_id = serialized_data.substr(0, pos); + *extra_data = serialized_data.substr(pos + 1); + return !instance_id->empty() && !extra_data->empty(); +} + void RecordOutgoingMessageToUMA( const gcm::GCMClient::OutgoingMessage& message) { OutgoingMessageTTLCategory ttl_category; @@ -335,7 +373,6 @@ void GCMClientImpl::OnLoadCompleted(scoped_ptr<GCMStore::LoadResult> result) { } gcm_store_reset_ = false; - registrations_ = result->registrations; device_checkin_info_.android_id = result->device_android_id; device_checkin_info_.secret = result->device_security_token; device_checkin_info_.last_checkin_accounts = result->last_checkin_accounts; @@ -348,7 +385,28 @@ void GCMClientImpl::OnLoadCompleted(scoped_ptr<GCMStore::LoadResult> result) { device_checkin_info_.accounts_set = true; last_checkin_time_ = result->last_checkin_time; gservices_settings_.UpdateFromLoadResult(*result); - instance_id_data_ = result->instance_id_data; + + for (auto iter = result->registrations.begin(); + iter != result->registrations.end(); + ++iter) { + std::string registration_id; + scoped_ptr<RegistrationInfo> registration = + RegistrationInfo::BuildFromString( + iter->first, iter->second, ®istration_id); + // TODO(jianli): Add UMA to track the error case. + if (registration.get()) + registrations_[make_linked_ptr(registration.release())] = registration_id; + } + + for (auto iter = result->instance_id_data.begin(); + iter != result->instance_id_data.end(); + ++iter) { + std::string instance_id; + std::string extra_data; + if (DeserializeInstanceIDData(iter->second, &instance_id, &extra_data)) + instance_id_data_[iter->first] = std::make_pair(instance_id, extra_data); + } + load_result_ = result.Pass(); state_ = LOADED; @@ -532,11 +590,12 @@ void GCMClientImpl::UpdateHeartbeatTimer(scoped_ptr<base::Timer> timer) { } void GCMClientImpl::AddInstanceIDData(const std::string& app_id, - const std::string& instance_id_data) { - instance_id_data_[app_id] = instance_id_data; + const std::string& instance_id, + const std::string& extra_data) { + instance_id_data_[app_id] = std::make_pair(instance_id, extra_data); gcm_store_->AddInstanceIDData( app_id, - instance_id_data, + SerializeInstanceIDData(instance_id, extra_data), base::Bind(&GCMClientImpl::IgnoreWriteResultCallback, weak_ptr_factory_.GetWeakPtr())); } @@ -549,11 +608,16 @@ void GCMClientImpl::RemoveInstanceIDData(const std::string& app_id) { weak_ptr_factory_.GetWeakPtr())); } -std::string GCMClientImpl::GetInstanceIDData(const std::string& app_id) { +void GCMClientImpl::GetInstanceIDData(const std::string& app_id, + std::string* instance_id, + std::string* extra_data) { + DCHECK(instance_id && extra_data); + auto iter = instance_id_data_.find(app_id); if (iter == instance_id_data_.end()) - return std::string(); - return iter->second; + return; + *instance_id = iter->second.first; + *extra_data = iter->second.second; } void GCMClientImpl::AddHeartbeatInterval(const std::string& scope, @@ -721,53 +785,105 @@ void GCMClientImpl::Stop() { gcm_store_->Close(); } -void GCMClientImpl::Register(const std::string& app_id, - const std::vector<std::string>& sender_ids) { +void GCMClientImpl::Register( + const linked_ptr<RegistrationInfo>& registration_info) { DCHECK_EQ(state_, READY); - // If the same sender ids is provided, return the cached registration ID - // directly. + // Find and use the cached registration ID. RegistrationInfoMap::const_iterator registrations_iter = - registrations_.find(app_id); - if (registrations_iter != registrations_.end() && - registrations_iter->second->sender_ids == sender_ids) { - delegate_->OnRegisterFinished( - app_id, registrations_iter->second->registration_id, SUCCESS); - return; + registrations_.find(registration_info); + if (registrations_iter != registrations_.end()) { + bool matched = true; + + // For GCM registration, we also match the sender IDs since multiple + // registrations are not supported. + const GCMRegistrationInfo* gcm_registration_info = + GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); + if (gcm_registration_info) { + const GCMRegistrationInfo* cached_gcm_registration_info = + GCMRegistrationInfo::FromRegistrationInfo( + registrations_iter->first.get()); + DCHECK(cached_gcm_registration_info); + if (cached_gcm_registration_info && + gcm_registration_info->sender_ids != + cached_gcm_registration_info->sender_ids) { + matched = false; + } + } + + if (matched) { + delegate_->OnRegisterFinished( + registration_info, registrations_iter->second, SUCCESS); + return; + } + } + + scoped_ptr<RegistrationRequest::CustomRequestHandler> request_handler; + std::string source_to_record; + + const GCMRegistrationInfo* gcm_registration_info = + GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); + if (gcm_registration_info) { + std::string senders; + for (auto iter = gcm_registration_info->sender_ids.begin(); + iter != gcm_registration_info->sender_ids.end(); + ++iter) { + if (!senders.empty()) + senders.append(","); + senders.append(*iter); + } + UMA_HISTOGRAM_COUNTS("GCM.RegistrationSenderIdCount", + gcm_registration_info->sender_ids.size()); + + request_handler.reset(new GCMRegistrationRequestHandler(senders)); + source_to_record = senders; + } + + const InstanceIDTokenInfo* instance_id_token_info = + InstanceIDTokenInfo::FromRegistrationInfo(registration_info.get()); + if (instance_id_token_info) { + auto instance_id_iter = instance_id_data_.find(registration_info->app_id); + DCHECK(instance_id_iter != instance_id_data_.end()); + + request_handler.reset(new InstanceIDGetTokenRequestHandler( + instance_id_iter->second.first, + instance_id_token_info->authorized_entity, + instance_id_token_info->scope, + ConstructGCMVersion(chrome_build_info_.version), + instance_id_token_info->options)); + source_to_record = instance_id_token_info->authorized_entity; } RegistrationRequest::RequestInfo request_info( device_checkin_info_.android_id, device_checkin_info_.secret, - app_id, - sender_ids); - DCHECK_EQ(0u, pending_registration_requests_.count(app_id)); + registration_info->app_id); RegistrationRequest* registration_request = new RegistrationRequest(gservices_settings_.GetRegistrationURL(), request_info, + request_handler.Pass(), GetGCMBackoffPolicy(), base::Bind(&GCMClientImpl::OnRegisterCompleted, - weak_ptr_factory_.GetWeakPtr(), - app_id, - sender_ids), + weak_ptr_factory_.GetWeakPtr(), + registration_info), kMaxRegistrationRetries, url_request_context_getter_, - &recorder_); - pending_registration_requests_[app_id] = registration_request; + &recorder_, + source_to_record); + pending_registration_requests_[registration_info] = registration_request; registration_request->Start(); } void GCMClientImpl::OnRegisterCompleted( - const std::string& app_id, - const std::vector<std::string>& sender_ids, + const linked_ptr<RegistrationInfo>& registration_info, RegistrationRequest::Status status, const std::string& registration_id) { DCHECK(delegate_); Result result; PendingRegistrationRequests::iterator iter = - pending_registration_requests_.find(app_id); + pending_registration_requests_.find(registration_info); if (iter == pending_registration_requests_.end()) result = UNKNOWN_ERROR; else if (status == RegistrationRequest::INVALID_SENDER) @@ -779,21 +895,20 @@ void GCMClientImpl::OnRegisterCompleted( if (result == SUCCESS) { // Cache it. - linked_ptr<RegistrationInfo> registration(new RegistrationInfo); - registration->sender_ids = sender_ids; - registration->registration_id = registration_id; - registrations_[app_id] = registration; + registrations_[registration_info] = registration_id; // Save it in the persistent store. gcm_store_->AddRegistration( - app_id, - registration, + registration_info->GetSerializedKey(), + registration_info->GetSerializedValue(registration_id), base::Bind(&GCMClientImpl::UpdateRegistrationCallback, weak_ptr_factory_.GetWeakPtr())); } delegate_->OnRegisterFinished( - app_id, result == SUCCESS ? registration_id : std::string(), result); + registration_info, + result == SUCCESS ? registration_id : std::string(), + result); if (iter != pending_registration_requests_.end()) { delete iter->second; @@ -801,47 +916,71 @@ void GCMClientImpl::OnRegisterCompleted( } } -void GCMClientImpl::Unregister(const std::string& app_id) { +void GCMClientImpl::Unregister( + const linked_ptr<RegistrationInfo>& registration_info) { DCHECK_EQ(state_, READY); - if (pending_unregistration_requests_.count(app_id) == 1) + if (pending_unregistration_requests_.count(registration_info) == 1) return; // Remove from the cache and persistent store. - registrations_.erase(app_id); + registrations_.erase(registration_info); gcm_store_->RemoveRegistration( - app_id, + registration_info->GetSerializedKey(), base::Bind(&GCMClientImpl::UpdateRegistrationCallback, weak_ptr_factory_.GetWeakPtr())); + scoped_ptr<UnregistrationRequest::CustomRequestHandler> request_handler; + + const GCMRegistrationInfo* gcm_registration_info = + GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); + if (gcm_registration_info) { + request_handler.reset( + new GCMUnregistrationRequestHandler(registration_info->app_id)); + } + + const InstanceIDTokenInfo* instance_id_token_info = + InstanceIDTokenInfo::FromRegistrationInfo(registration_info.get()); + if (instance_id_token_info) { + auto instance_id_iter = instance_id_data_.find(registration_info->app_id); + DCHECK(instance_id_iter != instance_id_data_.end()); + + request_handler.reset(new InstanceIDDeleteTokenRequestHandler( + instance_id_iter->second.first, + instance_id_token_info->authorized_entity, + instance_id_token_info->scope, + ConstructGCMVersion(chrome_build_info_.version))); + } + UnregistrationRequest::RequestInfo request_info( device_checkin_info_.android_id, device_checkin_info_.secret, - app_id); + registration_info->app_id); UnregistrationRequest* unregistration_request = new UnregistrationRequest( gservices_settings_.GetRegistrationURL(), request_info, + request_handler.Pass(), GetGCMBackoffPolicy(), base::Bind(&GCMClientImpl::OnUnregisterCompleted, - weak_ptr_factory_.GetWeakPtr(), - app_id), + weak_ptr_factory_.GetWeakPtr(), + registration_info), url_request_context_getter_, &recorder_); - pending_unregistration_requests_[app_id] = unregistration_request; + pending_unregistration_requests_[registration_info] = unregistration_request; unregistration_request->Start(); } void GCMClientImpl::OnUnregisterCompleted( - const std::string& app_id, + const linked_ptr<RegistrationInfo>& registration_info, UnregistrationRequest::Status status) { - DVLOG(1) << "Unregister completed for app: " << app_id + DVLOG(1) << "Unregister completed for app: " << registration_info->app_id << " with " << (status ? "success." : "failure."); delegate_->OnUnregisterFinished( - app_id, + registration_info, status == UnregistrationRequest::SUCCESS ? SUCCESS : SERVER_ERROR); PendingUnregistrationRequests::iterator iter = - pending_unregistration_requests_.find(app_id); + pending_unregistration_requests_.find(registration_info); if (iter == pending_unregistration_requests_.end()) return; @@ -929,7 +1068,7 @@ GCMClient::GCMStatistics GCMClientImpl::GetStatistics() const { for (RegistrationInfoMap::const_iterator it = registrations_.begin(); it != registrations_.end(); ++it) { - stats.registered_app_ids.push_back(it->first); + stats.registered_app_ids.push_back(it->first->app_id); } return stats; } @@ -1047,21 +1186,46 @@ void GCMClientImpl::HandleIncomingDataMessage( const mcs_proto::DataMessageStanza& data_message_stanza, MessageData& message_data) { std::string app_id = data_message_stanza.category(); + std::string sender = data_message_stanza.from(); // Drop the message when the app is not registered for the sender of the // message. - RegistrationInfoMap::iterator iter = registrations_.find(app_id); - bool not_registered = - iter == registrations_.end() || - std::find(iter->second->sender_ids.begin(), - iter->second->sender_ids.end(), - data_message_stanza.from()) == iter->second->sender_ids.end(); - recorder_.RecordDataMessageReceived(app_id, data_message_stanza.from(), - data_message_stanza.ByteSize(), !not_registered, + bool registered = false; + + // First, find among all GCM registrations. + scoped_ptr<GCMRegistrationInfo> gcm_registration(new GCMRegistrationInfo); + gcm_registration->app_id = app_id; + auto gcm_registration_iter = registrations_.find( + make_linked_ptr<RegistrationInfo>(gcm_registration.release())); + if (gcm_registration_iter != registrations_.end()) { + GCMRegistrationInfo* cached_gcm_registration = + GCMRegistrationInfo::FromRegistrationInfo( + gcm_registration_iter->first.get()); + if (cached_gcm_registration && + std::find(cached_gcm_registration->sender_ids.begin(), + cached_gcm_registration->sender_ids.end(), + sender) != cached_gcm_registration->sender_ids.end()) { + registered = true; + } + } + + // Then, find among all InstanceID registrations. + if (!registered) { + scoped_ptr<InstanceIDTokenInfo> instance_id_token(new InstanceIDTokenInfo); + instance_id_token->app_id = app_id; + instance_id_token->authorized_entity = sender; + instance_id_token->scope = kGCMScope; + auto instance_id_token_iter = registrations_.find( + make_linked_ptr<RegistrationInfo>(instance_id_token.release())); + if (instance_id_token_iter != registrations_.end()) + registered = true; + } + + recorder_.RecordDataMessageReceived(app_id, sender, + data_message_stanza.ByteSize(), registered, GCMStatsRecorder::DATA_MESSAGE); - if (not_registered) { + if (!registered) return; - } IncomingMessage incoming_message; incoming_message.sender_id = data_message_stanza.from(); @@ -1099,7 +1263,7 @@ bool GCMClientImpl::HasStandaloneRegisteredApp() const { // Note that account mapper is not counted as a standalone app since it is // automatically started when other app uses GCM. return registrations_.size() > 1 || - !registrations_.count(kGCMAccountMapperAppId); + !ExistsGCMRegistrationInMap(registrations_, kGCMAccountMapperAppId); } } // namespace gcm diff --git a/components/gcm_driver/gcm_client_impl.h b/components/gcm_driver/gcm_client_impl.h index e9f2f14..a39ffdd 100644 --- a/components/gcm_driver/gcm_client_impl.h +++ b/components/gcm_driver/gcm_client_impl.h @@ -8,6 +8,7 @@ #include <map> #include <set> #include <string> +#include <utility> #include <vector> #include "base/compiler_specific.h" @@ -111,9 +112,9 @@ class GCMClientImpl GCMClient::Delegate* delegate) override; void Start(StartMode start_mode) override; void Stop() override; - void Register(const std::string& app_id, - const std::vector<std::string>& sender_ids) override; - void Unregister(const std::string& app_id) override; + void Register(const linked_ptr<RegistrationInfo>& registration_info) override; + void Unregister( + const linked_ptr<RegistrationInfo>& registration_info) override; void Send(const std::string& app_id, const std::string& receiver_id, const OutgoingMessage& message) override; @@ -127,9 +128,12 @@ class GCMClientImpl void SetLastTokenFetchTime(const base::Time& time) override; void UpdateHeartbeatTimer(scoped_ptr<base::Timer> timer) override; void AddInstanceIDData(const std::string& app_id, - const std::string& instance_id_data) override; + const std::string& instance_id, + const std::string& extra_data) override; void RemoveInstanceIDData(const std::string& app_id) override; - std::string GetInstanceIDData(const std::string& app_id) override; + void GetInstanceIDData(const std::string& app_id, + std::string* instance_id, + std::string* extra_data) override; void AddHeartbeatInterval(const std::string& scope, int interval_ms) override; void RemoveHeartbeatInterval(const std::string& scope) override; @@ -165,17 +169,19 @@ class GCMClientImpl std::set<std::string> last_checkin_accounts; }; - // Collection of pending registration requests. Keys are app IDs, while values - // are pending registration requests to obtain a registration ID for - // requesting application. - typedef std::map<std::string, RegistrationRequest*> - PendingRegistrationRequests; + // Collection of pending registration requests. Keys are RegistrationInfo + // instance, while values are pending registration requests to obtain a + // registration ID for requesting application. + typedef std::map<linked_ptr<RegistrationInfo>, + RegistrationRequest*, + RegistrationInfoComparer> PendingRegistrationRequests; - // Collection of pending unregistration requests. Keys are app IDs, while - // values are pending unregistration requests to disable the registration ID - // currently assigned to the application. - typedef std::map<std::string, UnregistrationRequest*> - PendingUnregistrationRequests; + // Collection of pending unregistration requests. Keys are RegistrationInfo + // instance, while values are pending unregistration requests to disable the + // registration ID currently assigned to the application. + typedef std::map<linked_ptr<RegistrationInfo>, + UnregistrationRequest*, + RegistrationInfoComparer> PendingUnregistrationRequests; friend class GCMClientImplTest; @@ -247,14 +253,15 @@ class GCMClientImpl void ResetStoreCallback(bool success); // Completes the registration request. - void OnRegisterCompleted(const std::string& app_id, - const std::vector<std::string>& sender_ids, - RegistrationRequest::Status status, - const std::string& registration_id); + void OnRegisterCompleted( + const linked_ptr<RegistrationInfo>& registration_info, + RegistrationRequest::Status status, + const std::string& registration_id); // Completes the unregistration request. - void OnUnregisterCompleted(const std::string& app_id, - UnregistrationRequest::Status status); + void OnUnregisterCompleted( + const linked_ptr<RegistrationInfo>& registration_info, + UnregistrationRequest::Status status); // Completes the GCM store destroy request. void OnGCMStoreDestroyed(bool success); @@ -347,8 +354,9 @@ class GCMClientImpl // Time of the last successful checkin. base::Time last_checkin_time_; - // Cached instance ID data, key is app id. - std::map<std::string, std::string> instance_id_data_; + // Cached instance ID data, key is app ID and value is pair of instance ID + // and extra data. + std::map<std::string, std::pair<std::string, std::string>> instance_id_data_; // Factory for creating references when scheduling periodic checkin. base::WeakPtrFactory<GCMClientImpl> periodic_checkin_ptr_factory_; diff --git a/components/gcm_driver/gcm_client_impl_unittest.cc b/components/gcm_driver/gcm_client_impl_unittest.cc index a9a2429..54d978e 100644 --- a/components/gcm_driver/gcm_client_impl_unittest.cc +++ b/components/gcm_driver/gcm_client_impl_unittest.cc @@ -248,6 +248,9 @@ class GCMClientImplTest : public testing::Test, void BuildGCMClient(base::TimeDelta clock_step); void InitializeGCMClient(); void StartGCMClient(); + void Register(const std::string& app_id, + const std::vector<std::string>& senders); + void Unregister(const std::string& app_id); void ReceiveMessageFromMCS(const MCSMessage& message); void ReceiveOnMessageSentToMCS( const std::string& app_id, @@ -267,11 +270,12 @@ class GCMClientImplTest : public testing::Test, const std::string& registration_id); // GCMClient::Delegate overrides (for verification). - void OnRegisterFinished(const std::string& app_id, + void OnRegisterFinished(const linked_ptr<RegistrationInfo>& registration_info, const std::string& registration_id, GCMClient::Result result) override; - void OnUnregisterFinished(const std::string& app_id, - GCMClient::Result result) override; + void OnUnregisterFinished( + const linked_ptr<RegistrationInfo>& registration_info, + GCMClient::Result result) override; void OnSendFinished(const std::string& app_id, const std::string& message_id, GCMClient::Result result) override {} @@ -494,17 +498,17 @@ void GCMClientImplTest::VerifyPendingRequestFetcherDeleted() { } bool GCMClientImplTest::ExistsRegistration(const std::string& app_id) const { - return gcm_client_->registrations_.count(app_id) > 0; + return ExistsGCMRegistrationInMap(gcm_client_->registrations_, app_id); } void GCMClientImplTest::AddRegistration( const std::string& app_id, const std::vector<std::string>& sender_ids, const std::string& registration_id) { - linked_ptr<RegistrationInfo> registration(new RegistrationInfo); + linked_ptr<GCMRegistrationInfo> registration(new GCMRegistrationInfo); + registration->app_id = app_id; registration->sender_ids = sender_ids; - registration->registration_id = registration_id; - gcm_client_->registrations_[app_id] = registration; + gcm_client_->registrations_[registration] = registration_id; } void GCMClientImplTest::InitializeGCMClient() { @@ -527,6 +531,21 @@ void GCMClientImplTest::StartGCMClient() { PumpLoopUntilIdle(); } +void GCMClientImplTest::Register(const std::string& app_id, + const std::vector<std::string>& senders) { + scoped_ptr<GCMRegistrationInfo> gcm_info(new GCMRegistrationInfo); + gcm_info->app_id = app_id; + gcm_info->sender_ids = senders; + gcm_client()->Register(make_linked_ptr<RegistrationInfo>(gcm_info.release())); +} + +void GCMClientImplTest::Unregister(const std::string& app_id) { + scoped_ptr<GCMRegistrationInfo> gcm_info(new GCMRegistrationInfo); + gcm_info->app_id = app_id; + gcm_client()->Unregister( + make_linked_ptr<RegistrationInfo>(gcm_info.release())); +} + void GCMClientImplTest::ReceiveMessageFromMCS(const MCSMessage& message) { gcm_client_->recorder_.RecordConnectionInitiated(std::string()); gcm_client_->recorder_.RecordConnectionSuccess(); @@ -558,19 +577,21 @@ void GCMClientImplTest::OnMessageReceived( QuitLoop(); } -void GCMClientImplTest::OnRegisterFinished(const std::string& app_id, - const std::string& registration_id, - GCMClient::Result result) { +void GCMClientImplTest::OnRegisterFinished( + const linked_ptr<RegistrationInfo>& registration_info, + const std::string& registration_id, + GCMClient::Result result) { last_event_ = REGISTRATION_COMPLETED; - last_app_id_ = app_id; + last_app_id_ = registration_info->app_id; last_registration_id_ = registration_id; last_result_ = result; } -void GCMClientImplTest::OnUnregisterFinished(const std::string& app_id, - GCMClient::Result result) { +void GCMClientImplTest::OnUnregisterFinished( + const linked_ptr<RegistrationInfo>& registration_info, + GCMClient::Result result) { last_event_ = UNREGISTRATION_COMPLETED; - last_app_id_ = app_id; + last_app_id_ = registration_info->app_id; last_result_ = result; } @@ -642,7 +663,7 @@ TEST_F(GCMClientImplTest, RegisterApp) { std::vector<std::string> senders; senders.push_back("sender"); - gcm_client()->Register(kAppId, senders); + Register(kAppId, senders); CompleteRegistration("reg_id"); EXPECT_EQ(REGISTRATION_COMPLETED, last_event()); @@ -657,7 +678,7 @@ TEST_F(GCMClientImplTest, DISABLED_RegisterAppFromCache) { std::vector<std::string> senders; senders.push_back("sender"); - gcm_client()->Register(kAppId, senders); + Register(kAppId, senders); CompleteRegistration("reg_id"); EXPECT_TRUE(ExistsRegistration(kAppId)); @@ -679,11 +700,11 @@ TEST_F(GCMClientImplTest, UnregisterApp) { std::vector<std::string> senders; senders.push_back("sender"); - gcm_client()->Register(kAppId, senders); + Register(kAppId, senders); CompleteRegistration("reg_id"); EXPECT_TRUE(ExistsRegistration(kAppId)); - gcm_client()->Unregister(kAppId); + Unregister(kAppId); CompleteUnregistration(kAppId); EXPECT_EQ(UNREGISTRATION_COMPLETED, last_event()); @@ -698,7 +719,7 @@ TEST_F(GCMClientImplTest, UnregisterApp) { TEST_F(GCMClientImplTest, DeletePendingRequestsWhenStopping) { std::vector<std::string> senders; senders.push_back("sender"); - gcm_client()->Register(kAppId, senders); + Register(kAppId, senders); gcm_client()->Stop(); VerifyPendingRequestFetcherDeleted(); @@ -1148,7 +1169,7 @@ TEST_F(GCMClientImplStartAndStopTest, DelayStart) { // Registration. std::vector<std::string> senders; senders.push_back("sender"); - gcm_client()->Register(kAppId, senders); + Register(kAppId, senders); CompleteRegistration("reg_id"); EXPECT_EQ(GCMClientImpl::READY, gcm_client_state()); diff --git a/components/gcm_driver/gcm_driver.cc b/components/gcm_driver/gcm_driver.cc index 1b115c0..10adbae 100644 --- a/components/gcm_driver/gcm_driver.cc +++ b/components/gcm_driver/gcm_driver.cc @@ -12,10 +12,14 @@ namespace gcm { -InstanceIDStore::InstanceIDStore() { +namespace { +const size_t kMaxSenders = 100; +} // namespace + +InstanceIDHandler::InstanceIDHandler() { } -InstanceIDStore::~InstanceIDStore() { +InstanceIDHandler::~InstanceIDHandler() { } GCMDriver::GCMDriver() : weak_ptr_factory_(this) { @@ -28,7 +32,7 @@ void GCMDriver::Register(const std::string& app_id, const std::vector<std::string>& sender_ids, const RegisterCallback& callback) { DCHECK(!app_id.empty()); - DCHECK(!sender_ids.empty()); + DCHECK(!sender_ids.empty() && sender_ids.size() <= kMaxSenders); DCHECK(!callback.is_null()); GCMClient::Result result = EnsureStarted(GCMClient::IMMEDIATE_START); diff --git a/components/gcm_driver/gcm_driver.h b/components/gcm_driver/gcm_driver.h index caae284..689d721 100644 --- a/components/gcm_driver/gcm_driver.h +++ b/components/gcm_driver/gcm_driver.h @@ -22,24 +22,41 @@ class GCMAppHandler; class GCMConnectionObserver; struct AccountMapping; -// Provides the capability to set/get InstanceID data in the GCM store. -class InstanceIDStore { +// Provides the InstanceID support via GCMDriver. +class InstanceIDHandler { public: - typedef base::Callback<void(const std::string& instance_id_data)> + typedef base::Callback<void(const std::string& token, + GCMClient::Result result)> GetTokenCallback; + typedef base::Callback<void(GCMClient::Result result)> DeleteTokenCallback; + typedef base::Callback<void(const std::string& instance_id, + const std::string& extra_data)> GetInstanceIDDataCallback; - InstanceIDStore(); - virtual ~InstanceIDStore(); - + InstanceIDHandler(); + virtual ~InstanceIDHandler(); + + // Token service. + virtual void GetToken(const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + const std::map<std::string, std::string>& options, + const GetTokenCallback& callback) = 0; + virtual void DeleteToken(const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + const DeleteTokenCallback& callback) = 0; + + // Persistence support. virtual void AddInstanceIDData(const std::string& app_id, - const std::string& instance_id_data) = 0; + const std::string& instance_id, + const std::string& extra_data) = 0; virtual void RemoveInstanceIDData(const std::string& app_id) = 0; virtual void GetInstanceIDData( const std::string& app_id, const GetInstanceIDDataCallback& callback) = 0; private: - DISALLOW_COPY_AND_ASSIGN(InstanceIDStore); + DISALLOW_COPY_AND_ASSIGN(InstanceIDHandler); }; // Bridge between GCM users in Chrome and the platform-specific implementation. @@ -166,8 +183,8 @@ class GCMDriver { // to send a heartbeat message. virtual void WakeFromSuspendForHeartbeat(bool wake) = 0; - // Supports saving the Instance ID data in the GCM store. - virtual InstanceIDStore* GetInstanceIDStore() = 0; + // Supports InstanceID handling. + virtual InstanceIDHandler* GetInstanceIDHandler() = 0; // Adds or removes a custom client requested heartbeat interval. If multiple // components set that setting, the lowest setting will be used. If the diff --git a/components/gcm_driver/gcm_driver_android.cc b/components/gcm_driver/gcm_driver_android.cc index b5960fc..6f8dd59 100644 --- a/components/gcm_driver/gcm_driver_android.cc +++ b/components/gcm_driver/gcm_driver_android.cc @@ -162,7 +162,7 @@ void GCMDriverAndroid::SetLastTokenFetchTime(const base::Time& time) { void GCMDriverAndroid::WakeFromSuspendForHeartbeat(bool wake) { } -InstanceIDStore* GCMDriverAndroid::GetInstanceIDStore() { +InstanceIDHandler* GCMDriverAndroid::GetInstanceIDHandler() { // Not supported for Android. return NULL; } diff --git a/components/gcm_driver/gcm_driver_android.h b/components/gcm_driver/gcm_driver_android.h index 205fb6e..2dd0e70 100644 --- a/components/gcm_driver/gcm_driver_android.h +++ b/components/gcm_driver/gcm_driver_android.h @@ -64,7 +64,7 @@ class GCMDriverAndroid : public GCMDriver { base::Time GetLastTokenFetchTime() override; void SetLastTokenFetchTime(const base::Time& time) override; void WakeFromSuspendForHeartbeat(bool wake) override; - InstanceIDStore* GetInstanceIDStore() override; + InstanceIDHandler* GetInstanceIDHandler() override; void AddHeartbeatInterval(const std::string& scope, int interval_ms) override; void RemoveHeartbeatInterval(const std::string& scope) override; diff --git a/components/gcm_driver/gcm_driver_desktop.cc b/components/gcm_driver/gcm_driver_desktop.cc index dcc5cb2..958ec13 100644 --- a/components/gcm_driver/gcm_driver_desktop.cc +++ b/components/gcm_driver/gcm_driver_desktop.cc @@ -20,6 +20,7 @@ #include "components/gcm_driver/gcm_channel_status_syncer.h" #include "components/gcm_driver/gcm_client_factory.h" #include "components/gcm_driver/gcm_delayed_task_controller.h" +#include "components/gcm_driver/instance_id/instance_id_impl.h" #include "components/gcm_driver/system_encryptor.h" #include "google_apis/gcm/engine/account_mapping.h" #include "net/base/ip_endpoint.h" @@ -40,11 +41,12 @@ class GCMDriverDesktop::IOWorker : public GCMClient::Delegate { // Overridden from GCMClient::Delegate: // Called on IO thread. - void OnRegisterFinished(const std::string& app_id, + void OnRegisterFinished(const linked_ptr<RegistrationInfo>& registration_info, const std::string& registration_id, GCMClient::Result result) override; - void OnUnregisterFinished(const std::string& app_id, - GCMClient::Result result) override; + void OnUnregisterFinished( + const linked_ptr<RegistrationInfo>& registration_info, + GCMClient::Result result) override; void OnSendFinished(const std::string& app_id, const std::string& message_id, GCMClient::Result result) override; @@ -87,12 +89,21 @@ class GCMDriverDesktop::IOWorker : public GCMClient::Delegate { void RemoveAccountMapping(const std::string& account_id); void SetLastTokenFetchTime(const base::Time& time); void WakeFromSuspendForHeartbeat(bool wake); + void AddHeartbeatInterval(const std::string& scope, int interval_ms); + void RemoveHeartbeatInterval(const std::string& scope); + void AddInstanceIDData(const std::string& app_id, - const std::string& instance_id_data); + const std::string& instance_id, + const std::string& extra_data); void RemoveInstanceIDData(const std::string& app_id); void GetInstanceIDData(const std::string& app_id); - void AddHeartbeatInterval(const std::string& scope, int interval_ms); - void RemoveHeartbeatInterval(const std::string& scope); + void GetToken(const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + const std::map<std::string, std::string>& options); + void DeleteToken(const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope); // For testing purpose. Can be called from UI thread. Use with care. GCMClient* gcm_client_for_testing() const { return gcm_client_.get(); } @@ -143,27 +154,66 @@ void GCMDriverDesktop::IOWorker::Initialize( } void GCMDriverDesktop::IOWorker::OnRegisterFinished( - const std::string& app_id, + const linked_ptr<RegistrationInfo>& registration_info, const std::string& registration_id, GCMClient::Result result) { DCHECK(io_thread_->RunsTasksOnCurrentThread()); - ui_thread_->PostTask( + const GCMRegistrationInfo* gcm_registration_info = + GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); + if (gcm_registration_info) { + ui_thread_->PostTask( + FROM_HERE, + base::Bind(&GCMDriverDesktop::RegisterFinished, + service_, + gcm_registration_info->app_id, + registration_id, + result)); + } + + const InstanceIDTokenInfo* instance_id_token_info = + InstanceIDTokenInfo::FromRegistrationInfo(registration_info.get()); + if (instance_id_token_info) { + ui_thread_->PostTask( FROM_HERE, - base::Bind(&GCMDriverDesktop::RegisterFinished, service_, app_id, - registration_id, result)); + base::Bind(&GCMDriverDesktop::GetTokenFinished, + service_, + instance_id_token_info->app_id, + instance_id_token_info->authorized_entity, + instance_id_token_info->scope, + registration_id, + result)); + } } void GCMDriverDesktop::IOWorker::OnUnregisterFinished( - const std::string& app_id, + const linked_ptr<RegistrationInfo>& registration_info, GCMClient::Result result) { DCHECK(io_thread_->RunsTasksOnCurrentThread()); - ui_thread_->PostTask(FROM_HERE, - base::Bind(&GCMDriverDesktop::UnregisterFinished, - service_, - app_id, - result)); + const GCMRegistrationInfo* gcm_registration_info = + GCMRegistrationInfo::FromRegistrationInfo(registration_info.get()); + if (gcm_registration_info) { + ui_thread_->PostTask( + FROM_HERE, + base::Bind(&GCMDriverDesktop::UnregisterFinished, + service_, + gcm_registration_info->app_id, + result)); + } + + const InstanceIDTokenInfo* instance_id_token_info = + InstanceIDTokenInfo::FromRegistrationInfo(registration_info.get()); + if (instance_id_token_info) { + ui_thread_->PostTask( + FROM_HERE, + base::Bind(&GCMDriverDesktop::DeleteTokenFinished, + service_, + instance_id_token_info->app_id, + instance_id_token_info->authorized_entity, + instance_id_token_info->scope, + result)); + } } void GCMDriverDesktop::IOWorker::OnSendFinished(const std::string& app_id, @@ -270,13 +320,19 @@ void GCMDriverDesktop::IOWorker::Register( const std::vector<std::string>& sender_ids) { DCHECK(io_thread_->RunsTasksOnCurrentThread()); - gcm_client_->Register(app_id, sender_ids); + scoped_ptr<GCMRegistrationInfo> gcm_info(new GCMRegistrationInfo); + gcm_info->app_id = app_id; + gcm_info->sender_ids = sender_ids; + gcm_client_->Register(make_linked_ptr<RegistrationInfo>(gcm_info.release())); } void GCMDriverDesktop::IOWorker::Unregister(const std::string& app_id) { DCHECK(io_thread_->RunsTasksOnCurrentThread()); - gcm_client_->Unregister(app_id); + scoped_ptr<GCMRegistrationInfo> gcm_info(new GCMRegistrationInfo); + gcm_info->app_id = app_id; + gcm_client_->Unregister( + make_linked_ptr<RegistrationInfo>(gcm_info.release())); } void GCMDriverDesktop::IOWorker::Send( @@ -351,11 +407,12 @@ void GCMDriverDesktop::IOWorker::SetLastTokenFetchTime(const base::Time& time) { void GCMDriverDesktop::IOWorker::AddInstanceIDData( const std::string& app_id, - const std::string& instance_id_data) { + const std::string& instance_id, + const std::string& extra_data) { DCHECK(io_thread_->RunsTasksOnCurrentThread()); if (gcm_client_.get()) - gcm_client_->AddInstanceIDData(app_id, instance_id_data); + gcm_client_->AddInstanceIDData(app_id, instance_id, extra_data); } void GCMDriverDesktop::IOWorker::RemoveInstanceIDData( @@ -370,14 +427,45 @@ void GCMDriverDesktop::IOWorker::GetInstanceIDData( const std::string& app_id) { DCHECK(io_thread_->RunsTasksOnCurrentThread()); - std::string instance_id_data; + std::string instance_id; + std::string extra_data; if (gcm_client_.get()) - instance_id_data = gcm_client_->GetInstanceIDData(app_id); + gcm_client_->GetInstanceIDData(app_id, &instance_id, &extra_data); ui_thread_->PostTask( FROM_HERE, base::Bind(&GCMDriverDesktop::GetInstanceIDDataFinished, - service_, app_id, instance_id_data)); + service_, app_id, instance_id, extra_data)); +} + +void GCMDriverDesktop::IOWorker::GetToken( + const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + const std::map<std::string, std::string>& options) { + DCHECK(io_thread_->RunsTasksOnCurrentThread()); + + scoped_ptr<InstanceIDTokenInfo> instance_id_token_info( + new InstanceIDTokenInfo); + instance_id_token_info->app_id = app_id; + instance_id_token_info->authorized_entity = authorized_entity; + instance_id_token_info->scope = scope; + instance_id_token_info->options = options; + gcm_client_->Register( + make_linked_ptr<RegistrationInfo>(instance_id_token_info.release())); +} + +void GCMDriverDesktop::IOWorker::DeleteToken( + const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope) { + scoped_ptr<InstanceIDTokenInfo> instance_id_token_info( + new InstanceIDTokenInfo); + instance_id_token_info->app_id = app_id; + instance_id_token_info->authorized_entity = authorized_entity; + instance_id_token_info->scope = scope; + gcm_client_->Unregister( + make_linked_ptr<RegistrationInfo>(instance_id_token_info.release())); } void GCMDriverDesktop::IOWorker::WakeFromSuspendForHeartbeat(bool wake) { @@ -708,13 +796,84 @@ void GCMDriverDesktop::SetLastTokenFetchTime(const base::Time& time) { time)); } -InstanceIDStore* GCMDriverDesktop::GetInstanceIDStore() { +InstanceIDHandler* GCMDriverDesktop::GetInstanceIDHandler() { return this; } +void GCMDriverDesktop::GetToken( + const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + const std::map<std::string, std::string>& options, + const GetTokenCallback& callback) { + DCHECK(!app_id.empty()); + DCHECK(!authorized_entity.empty()); + DCHECK(!scope.empty()); + DCHECK(!callback.is_null()); + + GCMClient::Result result = EnsureStarted(GCMClient::IMMEDIATE_START); + if (result != GCMClient::SUCCESS) { + callback.Run(std::string(), result); + return; + } + + // If previous GetToken operation is still in progress, bail out. + TokenTuple tuple_key(app_id, authorized_entity, scope); + if (get_token_callbacks_.find(tuple_key) != get_token_callbacks_.end()) { + callback.Run(std::string(), GCMClient::ASYNC_OPERATION_PENDING); + return; + } + + get_token_callbacks_[tuple_key] = callback; + + io_thread_->PostTask( + FROM_HERE, + base::Bind(&GCMDriverDesktop::IOWorker::GetToken, + base::Unretained(io_worker_.get()), + app_id, + authorized_entity, + scope, + options)); +} + +void GCMDriverDesktop::DeleteToken(const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + const DeleteTokenCallback& callback) { + DCHECK(!app_id.empty()); + DCHECK(!authorized_entity.empty()); + DCHECK(!scope.empty()); + DCHECK(!callback.is_null()); + + GCMClient::Result result = EnsureStarted(GCMClient::IMMEDIATE_START); + if (result != GCMClient::SUCCESS) { + callback.Run(result); + return; + } + + // If previous GetToken operation is still in progress, bail out. + TokenTuple tuple_key(app_id, authorized_entity, scope); + if (delete_token_callbacks_.find(tuple_key) != + delete_token_callbacks_.end()) { + callback.Run(GCMClient::ASYNC_OPERATION_PENDING); + return; + } + + delete_token_callbacks_[tuple_key] = callback; + + io_thread_->PostTask( + FROM_HERE, + base::Bind(&GCMDriverDesktop::IOWorker::DeleteToken, + base::Unretained(io_worker_.get()), + app_id, + authorized_entity, + scope)); +} + void GCMDriverDesktop::AddInstanceIDData( const std::string& app_id, - const std::string& instance_id_data) { + const std::string& instance_id, + const std::string& extra_data) { DCHECK(ui_thread_->RunsTasksOnCurrentThread()); io_thread_->PostTask( @@ -722,7 +881,8 @@ void GCMDriverDesktop::AddInstanceIDData( base::Bind(&GCMDriverDesktop::IOWorker::AddInstanceIDData, base::Unretained(io_worker_.get()), app_id, - instance_id_data)); + instance_id, + extra_data)); } void GCMDriverDesktop::RemoveInstanceIDData(const std::string& app_id) { @@ -749,12 +909,46 @@ void GCMDriverDesktop::GetInstanceIDData( void GCMDriverDesktop::GetInstanceIDDataFinished( const std::string& app_id, - const std::string& instance_id_data) { + const std::string& instance_id, + const std::string& extra_data) { DCHECK(get_instance_id_data_callbacks_.count(app_id)); - get_instance_id_data_callbacks_[app_id].Run(instance_id_data); + get_instance_id_data_callbacks_[app_id].Run(instance_id, extra_data); get_instance_id_data_callbacks_.erase(app_id); } +void GCMDriverDesktop::GetTokenFinished(const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + const std::string& token, + GCMClient::Result result) { + TokenTuple tuple_key(app_id, authorized_entity, scope); + auto callback_iter = get_token_callbacks_.find(tuple_key); + if (callback_iter == get_token_callbacks_.end()) { + // The callback could have been removed when the app is uninstalled. + return; + } + + GetTokenCallback callback = callback_iter->second; + get_token_callbacks_.erase(callback_iter); + callback.Run(token, result); +} + +void GCMDriverDesktop::DeleteTokenFinished(const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + GCMClient::Result result) { + TokenTuple tuple_key(app_id, authorized_entity, scope); + auto callback_iter = delete_token_callbacks_.find(tuple_key); + if (callback_iter == delete_token_callbacks_.end()) { + // The callback could have been removed when the app is uninstalled. + return; + } + + DeleteTokenCallback callback = callback_iter->second; + delete_token_callbacks_.erase(callback_iter); + callback.Run(result); +} + void GCMDriverDesktop::WakeFromSuspendForHeartbeat(bool wake) { DCHECK(ui_thread_->RunsTasksOnCurrentThread()); @@ -986,4 +1180,19 @@ void GCMDriverDesktop::GetGCMStatisticsFinished( LOG(WARNING) << "request_gcm_statistics_callback_ is NULL."; } +bool GCMDriverDesktop::TokenTupleComparer::operator()( + const TokenTuple& a, const TokenTuple& b) const { + if (get<0>(a) < get<0>(b)) + return true; + if (get<0>(a) > get<0>(b)) + return false; + + if (get<1>(a) < get<1>(b)) + return true; + if (get<1>(a) > get<1>(b)) + return false; + + return get<2>(a) < get<2>(b); +} + } // namespace gcm diff --git a/components/gcm_driver/gcm_driver_desktop.h b/components/gcm_driver/gcm_driver_desktop.h index 3206a91..eba1be1 100644 --- a/components/gcm_driver/gcm_driver_desktop.h +++ b/components/gcm_driver/gcm_driver_desktop.h @@ -15,6 +15,7 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" +#include "base/tuple.h" #include "components/gcm_driver/gcm_channel_status_syncer.h" #include "components/gcm_driver/gcm_client.h" #include "components/gcm_driver/gcm_connection_observer.h" @@ -44,7 +45,7 @@ class GCMDelayedTaskController; // GCMDriver implementation for desktop and Chrome OS, using GCMClient. class GCMDriverDesktop : public GCMDriver, - public InstanceIDStore { + public InstanceIDHandler { public: GCMDriverDesktop( scoped_ptr<GCMClientFactory> gcm_client_factory, @@ -84,13 +85,23 @@ class GCMDriverDesktop : public GCMDriver, base::Time GetLastTokenFetchTime() override; void SetLastTokenFetchTime(const base::Time& time) override; void WakeFromSuspendForHeartbeat(bool wake) override; - InstanceIDStore* GetInstanceIDStore() override; + InstanceIDHandler* GetInstanceIDHandler() override; void AddHeartbeatInterval(const std::string& scope, int interval_ms) override; void RemoveHeartbeatInterval(const std::string& scope) override; - // InstanceIDStore overrides: + // InstanceIDHandler overrides: + void GetToken(const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + const std::map<std::string, std::string>& options, + const GetTokenCallback& callback) override; + void DeleteToken(const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + const DeleteTokenCallback& callback) override; void AddInstanceIDData(const std::string& app_id, - const std::string& instance_id_data) override; + const std::string& instance_id, + const std::string& extra_data) override; void RemoveInstanceIDData(const std::string& app_id) override; void GetInstanceIDData(const std::string& app_id, const GetInstanceIDDataCallback& callback) override; @@ -114,6 +125,11 @@ class GCMDriverDesktop : public GCMDriver, private: class IOWorker; + typedef Tuple<std::string, std::string, std::string> TokenTuple; + struct TokenTupleComparer { + bool operator()(const TokenTuple& a, const TokenTuple& b) const; + }; + // Stops the GCM service. It can be restarted by calling EnsureStarted again. void Stop(); @@ -142,7 +158,17 @@ class GCMDriverDesktop : public GCMDriver, void GetGCMStatisticsFinished(const GCMClient::GCMStatistics& stats); void GetInstanceIDDataFinished(const std::string& app_id, - const std::string& instance_id_data); + const std::string& instance_id, + const std::string& extra_data); + void GetTokenFinished(const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + const std::string& token, + GCMClient::Result result); + void DeleteTokenFinished(const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + GCMClient::Result result); scoped_ptr<GCMChannelStatusSyncer> gcm_channel_status_syncer_; @@ -189,6 +215,12 @@ class GCMDriverDesktop : public GCMDriver, std::map<std::string, GetInstanceIDDataCallback> get_instance_id_data_callbacks_; + // Callbacks for GetToken/DeleteToken. + std::map<TokenTuple, GetTokenCallback, TokenTupleComparer> + get_token_callbacks_; + std::map<TokenTuple, DeleteTokenCallback, TokenTupleComparer> + delete_token_callbacks_; + // Used to pass a weak pointer to the IO worker. base::WeakPtrFactory<GCMDriverDesktop> weak_ptr_factory_; diff --git a/components/gcm_driver/gcm_stats_recorder_impl.cc b/components/gcm_driver/gcm_stats_recorder_impl.cc index 524372d..6bf6963 100644 --- a/components/gcm_driver/gcm_stats_recorder_impl.cc +++ b/components/gcm_driver/gcm_stats_recorder_impl.cc @@ -272,14 +272,14 @@ void GCMStatsRecorderImpl::RecordConnectionResetSignaled( void GCMStatsRecorderImpl::RecordRegistration( const std::string& app_id, - const std::string& sender_ids, + const std::string& senders, const std::string& event, const std::string& details) { RegistrationActivity data; RegistrationActivity* inserted_data = InsertCircularBuffer( ®istration_activities_, data); inserted_data->app_id = app_id; - inserted_data->sender_ids = sender_ids; + inserted_data->sender_ids = senders; inserted_data->event = event; inserted_data->details = details; NotifyActivityRecorded(); @@ -297,22 +297,22 @@ void GCMStatsRecorderImpl::RecordRegistrationSent( void GCMStatsRecorderImpl::RecordRegistrationResponse( const std::string& app_id, - const std::vector<std::string>& sender_ids, + const std::string& senders, RegistrationRequest::Status status) { if (!is_recording_) return; - RecordRegistration(app_id, JoinString(sender_ids, ","), + RecordRegistration(app_id, senders, "Registration response received", GetRegistrationStatusString(status)); } void GCMStatsRecorderImpl::RecordRegistrationRetryRequested( const std::string& app_id, - const std::vector<std::string>& sender_ids, + const std::string& senders, int retries_left) { if (!is_recording_) return; - RecordRegistration(app_id, JoinString(sender_ids, ","), + RecordRegistration(app_id, senders, "Registration retry requested", base::StringPrintf("Retries left: %d", retries_left)); } diff --git a/components/gcm_driver/gcm_stats_recorder_impl.h b/components/gcm_driver/gcm_stats_recorder_impl.h index a2836be..a8d3e3b 100644 --- a/components/gcm_driver/gcm_stats_recorder_impl.h +++ b/components/gcm_driver/gcm_stats_recorder_impl.h @@ -55,13 +55,13 @@ class GCMStatsRecorderImpl : public GCMStatsRecorder { void RecordConnectionResetSignaled( ConnectionFactory::ConnectionResetReason reason) override; void RecordRegistrationSent(const std::string& app_id, - const std::string& sender_ids) override; + const std::string& senders) override; void RecordRegistrationResponse(const std::string& app_id, - const std::vector<std::string>& sender_ids, + const std::string& senders, RegistrationRequest::Status status) override; void RecordRegistrationRetryRequested( const std::string& app_id, - const std::vector<std::string>& sender_ids, + const std::string& senders, int retries_left) override; void RecordUnregistrationSent(const std::string& app_id) override; void RecordUnregistrationResponse( diff --git a/components/gcm_driver/gcm_stats_recorder_impl_unittest.cc b/components/gcm_driver/gcm_stats_recorder_impl_unittest.cc index 1223f67..e45ab59 100644 --- a/components/gcm_driver/gcm_stats_recorder_impl_unittest.cc +++ b/components/gcm_driver/gcm_stats_recorder_impl_unittest.cc @@ -338,7 +338,7 @@ class GCMStatsRecorderImplTest : public testing::Test { EXPECT_EQ(details, queue.front().details) << remark; } - std::vector<std::string> sender_ids_; + std::string sender_ids_; GCMStatsRecorderImpl recorder_; }; @@ -348,8 +348,7 @@ GCMStatsRecorderImplTest::GCMStatsRecorderImplTest(){ GCMStatsRecorderImplTest::~GCMStatsRecorderImplTest() {} void GCMStatsRecorderImplTest::SetUp(){ - sender_ids_.push_back("s1"); - sender_ids_.push_back("s2"); + sender_ids_ = "s1,s2"; recorder_.SetRecording(true); } diff --git a/components/gcm_driver/instance_id/fake_gcm_driver_for_instance_id.cc b/components/gcm_driver/instance_id/fake_gcm_driver_for_instance_id.cc index 2a50a03..6b09da0 100644 --- a/components/gcm_driver/instance_id/fake_gcm_driver_for_instance_id.cc +++ b/components/gcm_driver/instance_id/fake_gcm_driver_for_instance_id.cc @@ -6,6 +6,9 @@ #include "base/bind.h" #include "base/message_loop/message_loop.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "components/gcm_driver/gcm_client.h" namespace instance_id { @@ -15,14 +18,15 @@ FakeGCMDriverForInstanceID::FakeGCMDriverForInstanceID() { FakeGCMDriverForInstanceID::~FakeGCMDriverForInstanceID() { } -gcm::InstanceIDStore* FakeGCMDriverForInstanceID::GetInstanceIDStore() { +gcm::InstanceIDHandler* FakeGCMDriverForInstanceID::GetInstanceIDHandler() { return this; } void FakeGCMDriverForInstanceID::AddInstanceIDData( const std::string& app_id, - const std::string& instance_id_data) { - instance_id_data_[app_id] = instance_id_data; + const std::string& instance_id, + const std::string& extra_data) { + instance_id_data_[app_id] = std::make_pair(instance_id, extra_data); } void FakeGCMDriverForInstanceID::RemoveInstanceIDData( @@ -32,14 +36,50 @@ void FakeGCMDriverForInstanceID::RemoveInstanceIDData( void FakeGCMDriverForInstanceID::GetInstanceIDData( const std::string& app_id, - const gcm::InstanceIDStore::GetInstanceIDDataCallback& callback) { - std::string data; + const GetInstanceIDDataCallback& callback) { auto iter = instance_id_data_.find(app_id); - if (iter != instance_id_data_.end()) - data = iter->second; + std::string instance_id; + std::string extra_data; + if (iter != instance_id_data_.end()) { + instance_id = iter->second.first; + extra_data = iter->second.second; + } base::MessageLoop::current()->PostTask( FROM_HERE, - base::Bind(callback, data)); + base::Bind(callback, instance_id, extra_data)); +} + +void FakeGCMDriverForInstanceID::GetToken( + const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + const std::map<std::string, std::string>& options, + const GetTokenCallback& callback) { + std::string token; + std::string key = app_id + authorized_entity + scope; + auto iter = tokens_.find(key); + if (iter != tokens_.end()) { + token = iter->second; + } else { + token = base::Uint64ToString(base::RandUint64()); + tokens_[key] = token; + } + + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(callback, token, gcm::GCMClient::SUCCESS)); +} + +void FakeGCMDriverForInstanceID::DeleteToken( + const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + const DeleteTokenCallback& callback) { + std::string key = app_id + authorized_entity + scope; + tokens_.erase(key); + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(callback, gcm::GCMClient::SUCCESS)); } } // namespace instance_id diff --git a/components/gcm_driver/instance_id/fake_gcm_driver_for_instance_id.h b/components/gcm_driver/instance_id/fake_gcm_driver_for_instance_id.h index d5f9dd1..9d6b77a 100644 --- a/components/gcm_driver/instance_id/fake_gcm_driver_for_instance_id.h +++ b/components/gcm_driver/instance_id/fake_gcm_driver_for_instance_id.h @@ -7,6 +7,7 @@ #include <map> #include <string> +#include <utility> #include "base/compiler_specific.h" #include "base/macros.h" @@ -15,24 +16,35 @@ namespace instance_id { class FakeGCMDriverForInstanceID : public gcm::FakeGCMDriver, - public gcm::InstanceIDStore { + public gcm::InstanceIDHandler { public: FakeGCMDriverForInstanceID(); ~FakeGCMDriverForInstanceID() override; // FakeGCMDriver overrides: - gcm::InstanceIDStore* GetInstanceIDStore() override; - - // InstanceIDStore overrides: + gcm::InstanceIDHandler* GetInstanceIDHandler() override; + + // InstanceIDHandler overrides: + void GetToken(const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + const std::map<std::string, std::string>& options, + const GetTokenCallback& callback) override; + void DeleteToken(const std::string& app_id, + const std::string& authorized_entity, + const std::string& scope, + const DeleteTokenCallback& callback) override; void AddInstanceIDData(const std::string& app_id, - const std::string& instance_id_data) override; + const std::string& instance_id, + const std::string& extra_data) override; void RemoveInstanceIDData(const std::string& app_id) override; void GetInstanceIDData( const std::string& app_id, - const gcm::InstanceIDStore::GetInstanceIDDataCallback& callback) override; + const GetInstanceIDDataCallback& callback) override; private: - std::map<std::string, std::string> instance_id_data_; + std::map<std::string, std::pair<std::string, std::string>> instance_id_data_; + std::map<std::string, std::string> tokens_; DISALLOW_COPY_AND_ASSIGN(FakeGCMDriverForInstanceID); }; diff --git a/components/gcm_driver/instance_id/instance_id.h b/components/gcm_driver/instance_id/instance_id.h index 8141a3a..343b9c5 100644 --- a/components/gcm_driver/instance_id/instance_id.h +++ b/components/gcm_driver/instance_id/instance_id.h @@ -30,6 +30,8 @@ class InstanceID { INVALID_PARAMETER, // Instance ID is disabled. DISABLED, + // Previous asynchronous operation is still pending to finish. + ASYNC_OPERATION_PENDING, // Network socket error. NETWORK_ERROR, // Problem at the server. diff --git a/components/gcm_driver/instance_id/instance_id_driver_unittest.cc b/components/gcm_driver/instance_id/instance_id_driver_unittest.cc index 41305b5..5e79331 100644 --- a/components/gcm_driver/instance_id/instance_id_driver_unittest.cc +++ b/components/gcm_driver/instance_id/instance_id_driver_unittest.cc @@ -19,6 +19,10 @@ namespace { const char kTestAppID1[] = "TestApp1"; const char kTestAppID2[] = "TestApp2"; +const char kAuthorizedEntity1[] = "Sender 1"; +const char kAuthorizedEntity2[] = "Sender 2"; +const char kScope1[] = "GCM1"; +const char kScope2[] = "FooBar"; bool VerifyInstanceID(const std::string& str) { // Checks the length. @@ -53,6 +57,15 @@ class InstanceIDDriverTest : public testing::Test { std::string GetID(InstanceID* instance_id); base::Time GetCreationTime(InstanceID* instance_id); InstanceID::Result DeleteID(InstanceID* instance_id); + std::string GetToken( + InstanceID* instance_id, + const std::string& authorized_entity, + const std::string& scope, + const std::map<std::string, std::string>& options); + InstanceID::Result DeleteToken( + InstanceID* instance_id, + const std::string& authorized_entity, + const std::string& scope); InstanceIDDriver* driver() const { return driver_.get(); } @@ -60,6 +73,8 @@ class InstanceIDDriverTest : public testing::Test { void GetIDCompleted(const std::string& id); void GetCreationTimeCompleted(const base::Time& creation_time); void DeleteIDCompleted(InstanceID::Result result); + void GetTokenCompleted(const std::string& token, InstanceID::Result result); + void DeleteTokenCompleted(InstanceID::Result result); base::MessageLoopForUI message_loop_; scoped_ptr<FakeGCMDriverForInstanceID> gcm_driver_; @@ -67,6 +82,7 @@ class InstanceIDDriverTest : public testing::Test { std::string id_; base::Time creation_time_; + std::string token_; InstanceID::Result result_; bool async_operation_completed_; @@ -129,7 +145,41 @@ InstanceID::Result InstanceIDDriverTest::DeleteID(InstanceID* instance_id) { return result_; } +std::string InstanceIDDriverTest::GetToken( + InstanceID* instance_id, + const std::string& authorized_entity, + const std::string& scope, + const std::map<std::string, std::string>& options) { + async_operation_completed_ = false; + token_.clear(); + result_ = InstanceID::UNKNOWN_ERROR;; + instance_id->GetToken( + authorized_entity, + scope, + options, + base::Bind(&InstanceIDDriverTest::GetTokenCompleted, + base::Unretained(this))); + WaitForAsyncOperation(); + return token_; +} + +InstanceID::Result InstanceIDDriverTest::DeleteToken( + InstanceID* instance_id, + const std::string& authorized_entity, + const std::string& scope) { + async_operation_completed_ = false; + result_ = InstanceID::UNKNOWN_ERROR;; + instance_id->DeleteToken( + authorized_entity, + scope, + base::Bind(&InstanceIDDriverTest::DeleteTokenCompleted, + base::Unretained(this))); + WaitForAsyncOperation(); + return result_; +} + void InstanceIDDriverTest::GetIDCompleted(const std::string& id) { + DCHECK(!async_operation_completed_); async_operation_completed_ = true; id_ = id; if (!async_operation_completed_callback_.is_null()) @@ -138,6 +188,7 @@ void InstanceIDDriverTest::GetIDCompleted(const std::string& id) { void InstanceIDDriverTest::GetCreationTimeCompleted( const base::Time& creation_time) { + DCHECK(!async_operation_completed_); async_operation_completed_ = true; creation_time_ = creation_time; if (!async_operation_completed_callback_.is_null()) @@ -145,6 +196,25 @@ void InstanceIDDriverTest::GetCreationTimeCompleted( } void InstanceIDDriverTest::DeleteIDCompleted(InstanceID::Result result) { + DCHECK(!async_operation_completed_); + async_operation_completed_ = true; + result_ = result; + if (!async_operation_completed_callback_.is_null()) + async_operation_completed_callback_.Run(); +} + +void InstanceIDDriverTest::GetTokenCompleted( + const std::string& token, InstanceID::Result result){ + DCHECK(!async_operation_completed_); + async_operation_completed_ = true; + token_ = token; + result_ = result; + if (!async_operation_completed_callback_.is_null()) + async_operation_completed_callback_.Run(); +} + +void InstanceIDDriverTest::DeleteTokenCompleted(InstanceID::Result result) { + DCHECK(!async_operation_completed_); async_operation_completed_ = true; result_ = result; if (!async_operation_completed_callback_.is_null()) @@ -220,4 +290,55 @@ TEST_F(InstanceIDDriverTest, DeleteID) { EXPECT_FALSE(GetCreationTime(instance_id).is_null()); } +TEST_F(InstanceIDDriverTest, GetToken) { + InstanceID* instance_id = driver()->GetInstanceID(kTestAppID1); + std::map<std::string, std::string> options; + std::string token1 = + GetToken(instance_id, kAuthorizedEntity1, kScope1, options); + EXPECT_FALSE(token1.empty()); + + // Same token is returned for same authorized entity and scope. + EXPECT_EQ(token1, + GetToken(instance_id, kAuthorizedEntity1, kScope1, options)); + + // Different token is returned for different authorized entity or scope. + std::string token2 = + GetToken(instance_id, kAuthorizedEntity1, kScope2, options); + EXPECT_FALSE(token2.empty()); + EXPECT_NE(token1, token2); + + std::string token3 = + GetToken(instance_id, kAuthorizedEntity2, kScope1, options); + EXPECT_FALSE(token3.empty()); + EXPECT_NE(token1, token3); + EXPECT_NE(token2, token3); +} + +TEST_F(InstanceIDDriverTest, DeleteToken) { + InstanceID* instance_id = driver()->GetInstanceID(kTestAppID1); + std::map<std::string, std::string> options; + + // Gets 2 tokens. + std::string token1 = + GetToken(instance_id, kAuthorizedEntity1, kScope1, options); + EXPECT_FALSE(token1.empty()); + std::string token2 = + GetToken(instance_id, kAuthorizedEntity2, kScope1, options); + EXPECT_FALSE(token1.empty()); + EXPECT_NE(token1, token2); + + // Different token is returned for same authorized entity and scope after + // deletion. + EXPECT_EQ(InstanceID::SUCCESS, + DeleteToken(instance_id, kAuthorizedEntity1, kScope1)); + std::string new_token1 = + GetToken(instance_id, kAuthorizedEntity1, kScope2, options); + EXPECT_FALSE(new_token1.empty()); + EXPECT_NE(token1, new_token1); + + // The other token is not affected by the deletion. + EXPECT_EQ(token2, + GetToken(instance_id, kAuthorizedEntity2, kScope1, options)); +} + } // instance_id diff --git a/components/gcm_driver/instance_id/instance_id_impl.cc b/components/gcm_driver/instance_id/instance_id_impl.cc index b77cf3f..e9c347e 100644 --- a/components/gcm_driver/instance_id/instance_id_impl.cc +++ b/components/gcm_driver/instance_id/instance_id_impl.cc @@ -15,6 +15,34 @@ namespace instance_id { +namespace { + +InstanceID::Result GCMClientResultToInstanceIDResult( + gcm::GCMClient::Result result) { + switch (result) { + case gcm::GCMClient::SUCCESS: + return InstanceID::SUCCESS; + case gcm::GCMClient::INVALID_PARAMETER: + return InstanceID::INVALID_PARAMETER; + case gcm::GCMClient::ASYNC_OPERATION_PENDING: + return InstanceID::ASYNC_OPERATION_PENDING; + case gcm::GCMClient::GCM_DISABLED: + return InstanceID::DISABLED; + case gcm::GCMClient::NETWORK_ERROR: + return InstanceID::NETWORK_ERROR; + case gcm::GCMClient::SERVER_ERROR: + return InstanceID::SERVER_ERROR; + case gcm::GCMClient::UNKNOWN_ERROR: + return InstanceID::UNKNOWN_ERROR; + default: + NOTREACHED() << "Unexpected value of result cannot be converted: " + << result; + } + return InstanceID::UNKNOWN_ERROR; +} + +} // namespace + // static InstanceID* InstanceID::Create(const std::string& app_id, gcm::GCMDriver* gcm_driver) { @@ -27,7 +55,7 @@ InstanceIDImpl::InstanceIDImpl(const std::string& app_id, gcm_driver_(gcm_driver), load_from_store_(false), weak_ptr_factory_(this) { - gcm_driver_->GetInstanceIDStore()->GetInstanceIDData( + GetInstanceIDHandler()->GetInstanceIDData( app_id, base::Bind(&InstanceIDImpl::GetInstanceIDDataCompleted, weak_ptr_factory_.GetWeakPtr())); @@ -75,32 +103,148 @@ void InstanceIDImpl::GetToken( const std::string& scope, const std::map<std::string, std::string>& options, const GetTokenCallback& callback) { - NOTIMPLEMENTED(); + if (!delayed_task_controller_.CanRunTaskWithoutDelay()) { + delayed_task_controller_.AddTask( + base::Bind(&InstanceIDImpl::DoGetToken, + weak_ptr_factory_.GetWeakPtr(), + authorized_entity, + scope, + options, + callback)); + return; + } + + DoGetToken(authorized_entity, scope, options, callback); +} + +void InstanceIDImpl::DoGetToken( + const std::string& authorized_entity, + const std::string& scope, + const std::map<std::string, std::string>& options, + const GetTokenCallback& callback) { + EnsureIDGenerated(); + + GetInstanceIDHandler()->GetToken( + app_id(), + authorized_entity, + scope, + options, + base::Bind(&InstanceIDImpl::OnGetTokenCompleted, + weak_ptr_factory_.GetWeakPtr(), + callback)); } void InstanceIDImpl::DeleteToken(const std::string& authorized_entity, const std::string& scope, const DeleteTokenCallback& callback) { - NOTIMPLEMENTED(); + if (!delayed_task_controller_.CanRunTaskWithoutDelay()) { + delayed_task_controller_.AddTask( + base::Bind(&InstanceIDImpl::DoDeleteToken, + weak_ptr_factory_.GetWeakPtr(), + authorized_entity, + scope, + callback)); + return; + } + + DoDeleteToken(authorized_entity, scope, callback); +} + +void InstanceIDImpl::DoDeleteToken( + const std::string& authorized_entity, + const std::string& scope, + const DeleteTokenCallback& callback) { + // Nothing to delete if the ID has not been generated. + if (id_.empty()) { + callback.Run(InstanceID::SUCCESS); + return; + } + + GetInstanceIDHandler()->DeleteToken( + app_id(), + authorized_entity, + scope, + base::Bind(&InstanceIDImpl::OnDeleteTokenCompleted, + weak_ptr_factory_.GetWeakPtr(), + callback)); } void InstanceIDImpl::DeleteID(const DeleteIDCallback& callback) { - gcm_driver_->GetInstanceIDStore()->RemoveInstanceIDData(app_id()); + if (!delayed_task_controller_.CanRunTaskWithoutDelay()) { + delayed_task_controller_.AddTask( + base::Bind(&InstanceIDImpl::DoDeleteID, + weak_ptr_factory_.GetWeakPtr(), + callback)); + return; + } + + DoDeleteID(callback); +} + +void InstanceIDImpl::DoDeleteID(const DeleteIDCallback& callback) { + // Nothing to do if ID has not been generated. + if (id_.empty()) { + callback.Run(InstanceID::SUCCESS); + return; + } + + GetInstanceIDHandler()->DeleteToken( + app_id(), + "*", + "*", + base::Bind(&InstanceIDImpl::OnDeleteIDCompleted, + weak_ptr_factory_.GetWeakPtr(), + callback)); + + GetInstanceIDHandler()->RemoveInstanceIDData(app_id()); id_.clear(); creation_time_ = base::Time(); +} + +void InstanceIDImpl::OnGetTokenCompleted(const GetTokenCallback& callback, + const std::string& token, + gcm::GCMClient::Result result) { + callback.Run(token, GCMClientResultToInstanceIDResult(result)); +} + +void InstanceIDImpl::OnDeleteTokenCompleted( + const DeleteTokenCallback& callback, + gcm::GCMClient::Result result) { + callback.Run(GCMClientResultToInstanceIDResult(result)); +} - base::MessageLoop::current()->PostTask( - FROM_HERE, - base::Bind(callback, InstanceID::SUCCESS)); +void InstanceIDImpl::OnDeleteIDCompleted( + const DeleteIDCallback& callback, + gcm::GCMClient::Result result) { + callback.Run(GCMClientResultToInstanceIDResult(result)); } void InstanceIDImpl::GetInstanceIDDataCompleted( - const std::string& instance_id_data) { - Deserialize(instance_id_data); + const std::string& instance_id, + const std::string& extra_data) { + id_ = instance_id; + + if (extra_data.empty()) { + creation_time_ = base::Time(); + } else { + int64 time_internal = 0LL; + if (!base::StringToInt64(extra_data, &time_internal)) { + DVLOG(1) << "Failed to parse the time data: " + extra_data; + return; + } + creation_time_ = base::Time::FromInternalValue(time_internal); + } + delayed_task_controller_.SetReady(); } +gcm::InstanceIDHandler* InstanceIDImpl::GetInstanceIDHandler() const { + gcm::InstanceIDHandler* handler = gcm_driver_->GetInstanceIDHandler(); + DCHECK(handler); + return handler; +} + void InstanceIDImpl::EnsureIDGenerated() { if (!id_.empty()) return; @@ -131,35 +275,10 @@ void InstanceIDImpl::EnsureIDGenerated() { creation_time_ = base::Time::Now(); // Save to the persistent store. - gcm_driver_->GetInstanceIDStore()->AddInstanceIDData( - app_id(), SerializeAsString()); -} - -std::string InstanceIDImpl::SerializeAsString() const { - std::string serialized_data; - serialized_data += id_; - serialized_data += ","; - serialized_data += base::Int64ToString(creation_time_.ToInternalValue()); - return serialized_data; -} - -void InstanceIDImpl::Deserialize(const std::string& serialized_data) { - if (serialized_data.empty()) - return; - std::size_t pos = serialized_data.find(','); - if (pos == std::string::npos) { - DVLOG(1) << "Failed to deserialize the InstanceID data: " + serialized_data; - return; - } - - id_ = serialized_data.substr(0, pos); - - int64 time_internal = 0LL; - if (!base::StringToInt64(serialized_data.substr(pos + 1), &time_internal)) { - DVLOG(1) << "Failed to deserialize the InstanceID data: " + serialized_data; - return; - } - creation_time_ = base::Time::FromInternalValue(time_internal); + GetInstanceIDHandler()->AddInstanceIDData( + app_id(), + id_, + base::Int64ToString(creation_time_.ToInternalValue())); } } // namespace instance_id diff --git a/components/gcm_driver/instance_id/instance_id_impl.h b/components/gcm_driver/instance_id/instance_id_impl.h index a5124c1..8b0d942 100644 --- a/components/gcm_driver/instance_id/instance_id_impl.h +++ b/components/gcm_driver/instance_id/instance_id_impl.h @@ -13,11 +13,13 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/time/time.h" +#include "components/gcm_driver/gcm_client.h" #include "components/gcm_driver/gcm_delayed_task_controller.h" #include "components/gcm_driver/instance_id/instance_id.h" namespace gcm { class GCMDriver; +class InstanceIDHandler; } // namespace gcm namespace instance_id { @@ -41,15 +43,31 @@ class InstanceIDImpl : public InstanceID { void DeleteID(const DeleteIDCallback& callback) override; private: + gcm::InstanceIDHandler* GetInstanceIDHandler() const; + void EnsureIDGenerated(); - void GetInstanceIDDataCompleted(const std::string& instance_id_data); + + void OnGetTokenCompleted(const GetTokenCallback& callback, + const std::string& token, + gcm::GCMClient::Result result); + void OnDeleteTokenCompleted(const DeleteTokenCallback& callback, + gcm::GCMClient::Result result); + void OnDeleteIDCompleted(const DeleteIDCallback& callback, + gcm::GCMClient::Result result); + void GetInstanceIDDataCompleted(const std::string& instance_id, + const std::string& extra_data); void DoGetID(const GetIDCallback& callback); void DoGetCreationTime(const GetCreationTimeCallback& callback); - - // Encodes/decodes the InstanceID data to work with the persistent store. - std::string SerializeAsString() const; - void Deserialize(const std::string& serialized_data); + void DoGetToken( + const std::string& authorized_entity, + const std::string& scope, + const std::map<std::string, std::string>& options, + const GetTokenCallback& callback); + void DoDeleteToken(const std::string& authorized_entity, + const std::string& scope, + const DeleteTokenCallback& callback); + void DoDeleteID(const DeleteIDCallback& callback); gcm::GCMDriver* gcm_driver_; // Not owned. diff --git a/components/gcm_driver/registration_info.cc b/components/gcm_driver/registration_info.cc new file mode 100644 index 0000000..31f412d --- /dev/null +++ b/components/gcm_driver/registration_info.cc @@ -0,0 +1,251 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/gcm_driver/registration_info.h" + +#include "base/strings/string_util.h" + +namespace gcm { + +namespace { +const char kInsanceIDSerializationPrefix[] = "iid-"; +const int kInsanceIDSerializationPrefixLength = + sizeof(kInsanceIDSerializationPrefix) / sizeof(char) - 1; +} // namespace + +// static +scoped_ptr<RegistrationInfo> RegistrationInfo::BuildFromString( + const std::string& serialzied_key, + const std::string& serialzied_value, + std::string* registration_id) { + scoped_ptr<RegistrationInfo> registration; + + if (StartsWithASCII(serialzied_key, kInsanceIDSerializationPrefix, true)) + registration.reset(new InstanceIDTokenInfo); + else + registration.reset(new GCMRegistrationInfo); + + if (!registration->Deserialize(serialzied_key, + serialzied_value, + registration_id)) { + registration.reset(); + } + return registration.Pass(); +} + +RegistrationInfo::RegistrationInfo() { +} + +RegistrationInfo::~RegistrationInfo() { +} + +// static +const GCMRegistrationInfo* GCMRegistrationInfo::FromRegistrationInfo( + const RegistrationInfo* registration_info) { + if (!registration_info || registration_info->GetType() != GCM_REGISTRATION) + return NULL; + return static_cast<const GCMRegistrationInfo*>(registration_info); +} + +// static +GCMRegistrationInfo* GCMRegistrationInfo::FromRegistrationInfo( + RegistrationInfo* registration_info) { + if (!registration_info || registration_info->GetType() != GCM_REGISTRATION) + return NULL; + return static_cast<GCMRegistrationInfo*>(registration_info); +} + +GCMRegistrationInfo::GCMRegistrationInfo() { +} + +GCMRegistrationInfo::~GCMRegistrationInfo() { +} + +RegistrationInfo::RegistrationType GCMRegistrationInfo::GetType() const { + return GCM_REGISTRATION; +} + +std::string GCMRegistrationInfo::GetSerializedKey() const { + // Multiple registrations are not supported for legacy GCM. So the key is + // purely based on the application id. + return app_id; +} + +std::string GCMRegistrationInfo::GetSerializedValue( + const std::string& registration_id) const { + if (sender_ids.empty() || registration_id.empty()) + return std::string(); + + // Serialize as: + // sender1,sender2,...=reg_id + std::string value; + for (std::vector<std::string>::const_iterator iter = sender_ids.begin(); + iter != sender_ids.end(); ++iter) { + DCHECK(!iter->empty() && + iter->find(',') == std::string::npos && + iter->find('=') == std::string::npos); + if (!value.empty()) + value += ","; + value += *iter; + } + + value += '='; + value += registration_id; + return value; +} + +bool GCMRegistrationInfo::Deserialize( + const std::string& serialzied_key, + const std::string& serialzied_value, + std::string* registration_id) { + if (serialzied_key.empty() || serialzied_value.empty()) + return false; + + // Application ID is same as the serialized key. + app_id = serialzied_key; + + // Sender IDs and registration ID are constructed from the serialized value. + size_t pos = serialzied_value.find('='); + if (pos == std::string::npos) + return false; + + std::string senders = serialzied_value.substr(0, pos); + std::string registration_id_str = serialzied_value.substr(pos + 1); + + Tokenize(senders, ",", &sender_ids); + + if (sender_ids.empty() || registration_id_str.empty()) { + sender_ids.clear(); + registration_id_str.clear(); + return false; + } + + if (registration_id) + *registration_id = registration_id_str; + + return true; +} + +// static +const InstanceIDTokenInfo* InstanceIDTokenInfo::FromRegistrationInfo( + const RegistrationInfo* registration_info) { + if (!registration_info || registration_info->GetType() != INSTANCE_ID_TOKEN) + return NULL; + return static_cast<const InstanceIDTokenInfo*>(registration_info); +} + +// static +InstanceIDTokenInfo* InstanceIDTokenInfo::FromRegistrationInfo( + RegistrationInfo* registration_info) { + if (!registration_info || registration_info->GetType() != INSTANCE_ID_TOKEN) + return NULL; + return static_cast<InstanceIDTokenInfo*>(registration_info); +} + +InstanceIDTokenInfo::InstanceIDTokenInfo() { +} + +InstanceIDTokenInfo::~InstanceIDTokenInfo() { +} + +RegistrationInfo::RegistrationType InstanceIDTokenInfo::GetType() const { + return INSTANCE_ID_TOKEN; +} + +std::string InstanceIDTokenInfo::GetSerializedKey() const { + DCHECK(authorized_entity.find(',') == std::string::npos && + scope.find(',') == std::string::npos); + + // Multiple registrations are supported for Instance ID. So the key is based + // on the combination of (app_id, authorized_entity, scope). + + // Adds a prefix to differentiate easily with GCM registration key. + std::string key(kInsanceIDSerializationPrefix); + key += app_id; + key += ","; + key += authorized_entity; + key += ","; + key += scope; + return key; +} + +std::string InstanceIDTokenInfo::GetSerializedValue( + const std::string& registration_id) const { + return registration_id; +} + +bool InstanceIDTokenInfo::Deserialize( + const std::string& serialized_key, + const std::string& serialized_value, + std::string* registration_id) { + if (serialized_key.empty() || serialized_value.empty()) + return false; + + if (!StartsWithASCII(serialized_key, kInsanceIDSerializationPrefix, true)) + return false; + + std::vector<std::string> fields; + Tokenize(serialized_key.substr(kInsanceIDSerializationPrefixLength), + ",", + &fields); + if (fields.size() != 3 || fields[0].empty() || + fields[1].empty() || fields[2].empty()) { + return false; + } + app_id = fields[0]; + authorized_entity = fields[1]; + scope = fields[2]; + + // Registration ID is same as the serialized value; + if (registration_id) + *registration_id = serialized_value; + + return true; +} + +bool RegistrationInfoComparer::operator()( + const linked_ptr<RegistrationInfo>& a, + const linked_ptr<RegistrationInfo>& b) const { + DCHECK(a.get() && b.get()); + + // For GCMRegistrationInfo, the comparison is based on app_id only. + // For InstanceIDTokenInfo, the comparison is bsaed on + // <app_id, authorized_entity, scope>. + if (a->app_id < b->app_id) + return true; + if (a->app_id > b->app_id) + return false; + + InstanceIDTokenInfo* iid_a = + InstanceIDTokenInfo::FromRegistrationInfo(a.get()); + InstanceIDTokenInfo* iid_b = + InstanceIDTokenInfo::FromRegistrationInfo(b.get()); + + // !iid_a && !iid_b => false. + // !iid_a && iid_b => true. + // This makes GCM record is sorted before InstanceID record. + if (!iid_a) + return iid_b != NULL; + + // iid_a && !iid_b => false. + if (!iid_b) + return false; + + // Otherwise, compare with authorized_entity and scope. + if (iid_a->authorized_entity < iid_b->authorized_entity) + return true; + if (iid_a->authorized_entity > iid_b->authorized_entity) + return false; + return iid_a->scope < iid_b->scope; +} + +bool ExistsGCMRegistrationInMap(const RegistrationInfoMap& map, + const std::string& app_id) { + scoped_ptr<GCMRegistrationInfo> gcm_registration(new GCMRegistrationInfo); + gcm_registration->app_id = app_id; + return map.count( + make_linked_ptr<RegistrationInfo>(gcm_registration.release())) > 0; +} + +} // namespace gcm diff --git a/components/gcm_driver/registration_info.h b/components/gcm_driver/registration_info.h new file mode 100644 index 0000000..346eca3 --- /dev/null +++ b/components/gcm_driver/registration_info.h @@ -0,0 +1,130 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_GCM_DRIVER_REGISTRATION_INFO_H_ +#define COMPONENTS_GCM_DRIVER_REGISTRATION_INFO_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/scoped_ptr.h" + +namespace gcm { + +// Encapsulates the information needed to register with the server. +struct RegistrationInfo { + enum RegistrationType { + GCM_REGISTRATION, + INSTANCE_ID_TOKEN + }; + + // Returns the appropriate RegistrationInfo instance based on the serialized + // key and value. + // |registration_id| can be NULL if no interest to it. + static scoped_ptr<RegistrationInfo> BuildFromString( + const std::string& serialzied_key, + const std::string& serialzied_value, + std::string* registration_id); + + RegistrationInfo(); + virtual ~RegistrationInfo(); + + // Returns the type of the registration info. + virtual RegistrationType GetType() const = 0; + + // For persisting to the store. Depending on the type, part of the + // registration info is written as key. The remaining of the registration + // info plus the registration ID are written as value. + virtual std::string GetSerializedKey() const = 0; + virtual std::string GetSerializedValue( + const std::string& registration_id) const = 0; + // |registration_id| can be NULL if it is of no interest to the caller. + virtual bool Deserialize(const std::string& serialzied_key, + const std::string& serialzied_value, + std::string* registration_id) = 0; + + // Every registration is associated with an application. + std::string app_id; +}; + +// For GCM registration. +struct GCMRegistrationInfo : public RegistrationInfo { + GCMRegistrationInfo(); + ~GCMRegistrationInfo() override; + + // Converts from the base type; + static const GCMRegistrationInfo* FromRegistrationInfo( + const RegistrationInfo* registration_info); + static GCMRegistrationInfo* FromRegistrationInfo( + RegistrationInfo* registration_info); + + // RegistrationInfo overrides: + RegistrationType GetType() const override; + std::string GetSerializedKey() const override; + std::string GetSerializedValue( + const std::string& registration_id) const override; + bool Deserialize(const std::string& serialzied_key, + const std::string& serialzied_value, + std::string* registration_id) override; + + // List of IDs of the servers that are allowed to send the messages to the + // application. These IDs are assigned by the Google API Console. + std::vector<std::string> sender_ids; +}; + +// For InstanceID token retrieval. +struct InstanceIDTokenInfo : public RegistrationInfo { + InstanceIDTokenInfo(); + ~InstanceIDTokenInfo() override; + + // Converts from the base type; + static const InstanceIDTokenInfo* FromRegistrationInfo( + const RegistrationInfo* registration_info); + static InstanceIDTokenInfo* FromRegistrationInfo( + RegistrationInfo* registration_info); + + // RegistrationInfo overrides: + RegistrationType GetType() const override; + std::string GetSerializedKey() const override; + std::string GetSerializedValue( + const std::string& registration_id) const override; + bool Deserialize(const std::string& serialzied_key, + const std::string& serialzied_value, + std::string* registration_id) override; + + // Entity that is authorized to access resources associated with the Instance + // ID. It can be another Instance ID or a project ID assigned by the Google + // API Console. + std::string authorized_entity; + + // Authorized actions that the authorized entity can take. + // E.g. for sending GCM messages, 'GCM' scope should be used. + std::string scope; + + // Allows including a small number of string key/value pairs that will be + // associated with the token and may be used in processing the request. + std::map<std::string, std::string> options; +}; + +struct RegistrationInfoComparer { + bool operator()(const linked_ptr<RegistrationInfo>& a, + const linked_ptr<RegistrationInfo>& b) const; +}; + +// Collection of registration info. +// Map from RegistrationInfo instance to registration ID. +typedef std::map<linked_ptr<RegistrationInfo>, + std::string, + RegistrationInfoComparer> RegistrationInfoMap; + +// Returns true if a GCM registration for |app_id| exists in |map|. +bool ExistsGCMRegistrationInMap(const RegistrationInfoMap& map, + const std::string& app_id); + +} // namespace gcm + +#endif // COMPONENTS_GCM_DRIVER_REGISTRATION_INFO_H_ diff --git a/google_apis/gcm/BUILD.gn b/google_apis/gcm/BUILD.gn index 83aa067..ee6a42f 100644 --- a/google_apis/gcm/BUILD.gn +++ b/google_apis/gcm/BUILD.gn @@ -7,6 +7,8 @@ import("//third_party/protobuf/proto_library.gni") component("gcm") { sources = [ + "base/gcm_util.cc", + "base/gcm_util.h", "base/mcs_message.cc", "base/mcs_message.h", "base/mcs_util.cc", @@ -25,18 +27,24 @@ component("gcm") { "engine/connection_handler.h", "engine/connection_handler_impl.cc", "engine/connection_handler_impl.h", + "engine/gcm_registration_request_handler.cc", + "engine/gcm_registration_request_handler.h", "engine/gcm_store.cc", "engine/gcm_store.h", "engine/gcm_store_impl.cc", "engine/gcm_store_impl.h", + "engine/gcm_unregistration_request_handler.cc", + "engine/gcm_unregistration_request_handler.h", "engine/gservices_settings.cc", "engine/gservices_settings.h", "engine/heartbeat_manager.cc", "engine/heartbeat_manager.h", + "engine/instance_id_delete_token_request_handler.cc", + "engine/instance_id_delete_token_request_handler.h", + "engine/instance_id_get_token_request_handler.cc", + "engine/instance_id_get_token_request_handler.h", "engine/mcs_client.cc", "engine/mcs_client.h", - "engine/registration_info.cc", - "engine/registration_info.h", "engine/registration_request.cc", "engine/registration_request.h", "engine/unregistration_request.cc", diff --git a/google_apis/gcm/base/gcm_util.cc b/google_apis/gcm/base/gcm_util.cc new file mode 100644 index 0000000..e51a498 --- /dev/null +++ b/google_apis/gcm/base/gcm_util.cc @@ -0,0 +1,19 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "google_apis/gcm/base/gcm_util.h" + +#include "net/base/escape.h" + +namespace gcm { + +void BuildFormEncoding(const std::string& key, + const std::string& value, + std::string* out) { + if (!out->empty()) + out->append("&"); + out->append(key + "=" + net::EscapeUrlEncodedData(value, true)); +} + +} // namespace gcm diff --git a/google_apis/gcm/base/gcm_util.h b/google_apis/gcm/base/gcm_util.h new file mode 100644 index 0000000..261950b --- /dev/null +++ b/google_apis/gcm/base/gcm_util.h @@ -0,0 +1,21 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef GOOGLE_APIS_GCM_BASE_GCM_UTIL_H_ +#define GOOGLE_APIS_GCM_BASE_GCM_UTIL_H_ + +#include <string> + +namespace gcm { + +// Encodes key-value pair into form format that could be submitted as http post +// data. Each key-value pair is separated by an '&', and each key is separated +// from its value by an '='. +void BuildFormEncoding(const std::string& key, + const std::string& value, + std::string* out); + +} // namespace gcm + +#endif // GOOGLE_APIS_GCM_BASE_GCM_UTIL_H_ diff --git a/google_apis/gcm/engine/gcm_registration_request_handler.cc b/google_apis/gcm/engine/gcm_registration_request_handler.cc new file mode 100644 index 0000000..c0213f3 --- /dev/null +++ b/google_apis/gcm/engine/gcm_registration_request_handler.cc @@ -0,0 +1,29 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "google_apis/gcm/engine/gcm_registration_request_handler.h" + +#include "google_apis/gcm/base/gcm_util.h" + +namespace gcm { + +namespace { + +// Request constants. +const char kSenderKey[] = "sender"; + +} // namespace + +GCMRegistrationRequestHandler::GCMRegistrationRequestHandler( + const std::string& senders) + : senders_(senders) { +} + +GCMRegistrationRequestHandler::~GCMRegistrationRequestHandler() {} + +void GCMRegistrationRequestHandler::BuildRequestBody(std::string* body){ + BuildFormEncoding(kSenderKey, senders_, body); +} + +} // namespace gcm diff --git a/google_apis/gcm/engine/gcm_registration_request_handler.h b/google_apis/gcm/engine/gcm_registration_request_handler.h new file mode 100644 index 0000000..26ba49d --- /dev/null +++ b/google_apis/gcm/engine/gcm_registration_request_handler.h @@ -0,0 +1,30 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef GOOGLE_APIS_GCM_ENGINE_GCM_REGISTRATION_REQUEST_HANDLER_H_ +#define GOOGLE_APIS_GCM_ENGINE_GCM_REGISTRATION_REQUEST_HANDLER_H_ + +#include "google_apis/gcm/engine/registration_request.h" + +namespace gcm { + +// Used to obtain the registration ID for applications that want to use GCM. +class GCM_EXPORT GCMRegistrationRequestHandler : + public RegistrationRequest::CustomRequestHandler { + public: + GCMRegistrationRequestHandler(const std::string& senders); + ~GCMRegistrationRequestHandler() override; + + // RegistrationRequest::RequestHandler overrides: + void BuildRequestBody(std::string* body) override; + + private: + std::string senders_; + + DISALLOW_COPY_AND_ASSIGN(GCMRegistrationRequestHandler); +}; + +} // namespace gcm + +#endif // GOOGLE_APIS_GCM_ENGINE_GCM_REGISTRATION_REQUEST_HANDLER_H_ diff --git a/google_apis/gcm/engine/gcm_store.h b/google_apis/gcm/engine/gcm_store.h index cfefe91..83ec73d 100644 --- a/google_apis/gcm/engine/gcm_store.h +++ b/google_apis/gcm/engine/gcm_store.h @@ -20,7 +20,6 @@ #include "base/time/time.h" #include "google_apis/gcm/base/gcm_export.h" #include "google_apis/gcm/engine/account_mapping.h" -#include "google_apis/gcm/engine/registration_info.h" namespace gcm { @@ -47,7 +46,7 @@ class GCM_EXPORT GCMStore { bool success; uint64 device_android_id; uint64 device_security_token; - RegistrationInfoMap registrations; + std::map<std::string, std::string> registrations; std::vector<std::string> incoming_messages; OutgoingMessageMap outgoing_messages; std::map<std::string, std::string> gservices_settings; @@ -82,11 +81,15 @@ class GCM_EXPORT GCMStore { uint64 device_security_token, const UpdateCallback& callback) = 0; - // Registration info. - virtual void AddRegistration(const std::string& app_id, - const linked_ptr<RegistrationInfo>& registration, + // Registration info for both GCM registrations and InstanceID tokens. + // For GCM, |serialized_key| is app_id and |serialized_value| is + // serialization of (senders, registration_id). For InstanceID, + // |serialized_key| is serialization of (app_id, authorized_entity, scope) + // and |serialized_value| is token. + virtual void AddRegistration(const std::string& serialized_key, + const std::string& serialized_value, const UpdateCallback& callback) = 0; - virtual void RemoveRegistration(const std::string& app_id, + virtual void RemoveRegistration(const std::string& serialized_key, const UpdateCallback& callback) = 0; // Unacknowledged incoming message handling. diff --git a/google_apis/gcm/engine/gcm_store_impl.cc b/google_apis/gcm/engine/gcm_store_impl.cc index 09bfb83..3436ba9 100644 --- a/google_apis/gcm/engine/gcm_store_impl.cc +++ b/google_apis/gcm/engine/gcm_store_impl.cc @@ -190,10 +190,10 @@ class GCMStoreImpl::Backend void SetDeviceCredentials(uint64 device_android_id, uint64 device_security_token, const UpdateCallback& callback); - void AddRegistration(const std::string& app_id, - const std::string& serialized_registration, + void AddRegistration(const std::string& serialized_key, + const std::string& serialized_value, const UpdateCallback& callback); - void RemoveRegistration(const std::string& app_id, + void RemoveRegistration(const std::string& serialized_key, const UpdateCallback& callback); void AddIncomingMessage(const std::string& persistent_id, const UpdateCallback& callback); @@ -244,7 +244,7 @@ class GCMStoreImpl::Backend LoadStatus OpenStoreAndLoadData(LoadResult* result); bool LoadDeviceCredentials(uint64* android_id, uint64* security_token); - bool LoadRegistrations(RegistrationInfoMap* registrations); + bool LoadRegistrations(std::map<std::string, std::string>* registrations); bool LoadIncomingMessages(std::vector<std::string>* incoming_messages); bool LoadOutgoingMessages(OutgoingMessageMap* outgoing_messages); bool LoadLastCheckinInfo(base::Time* last_checkin_time, @@ -417,10 +417,10 @@ void GCMStoreImpl::Backend::SetDeviceCredentials( } void GCMStoreImpl::Backend::AddRegistration( - const std::string& app_id, - const std::string& serialized_registration, + const std::string& serialized_key, + const std::string& serialized_value, const UpdateCallback& callback) { - DVLOG(1) << "Saving registration info for app: " << app_id; + DVLOG(1) << "Saving registration info for app: " << serialized_key; if (!db_.get()) { LOG(ERROR) << "GCMStore db doesn't exist."; foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); @@ -429,20 +429,19 @@ void GCMStoreImpl::Backend::AddRegistration( leveldb::WriteOptions write_options; write_options.sync = true; - std::string key = MakeRegistrationKey(app_id); - const leveldb::Status status = db_->Put(write_options, - MakeSlice(key), - MakeSlice(serialized_registration)); - if (status.ok()) { - foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true)); - return; - } - LOG(ERROR) << "LevelDB put failed: " << status.ToString(); - foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); + const leveldb::Status status = db_->Put( + write_options, + MakeSlice(MakeRegistrationKey(serialized_key)), + MakeSlice(serialized_value)); + if (!status.ok()) + LOG(ERROR) << "LevelDB put failed: " << status.ToString(); + foreground_task_runner_->PostTask( + FROM_HERE, base::Bind(callback, status.ok())); } -void GCMStoreImpl::Backend::RemoveRegistration(const std::string& app_id, - const UpdateCallback& callback) { +void GCMStoreImpl::Backend::RemoveRegistration( + const std::string& serialized_key, + const UpdateCallback& callback) { if (!db_.get()) { LOG(ERROR) << "GCMStore db doesn't exist."; foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); @@ -451,14 +450,12 @@ void GCMStoreImpl::Backend::RemoveRegistration(const std::string& app_id, leveldb::WriteOptions write_options; write_options.sync = true; - leveldb::Status status = - db_->Delete(write_options, MakeSlice(MakeRegistrationKey(app_id))); - if (status.ok()) { - foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true)); - return; - } - LOG(ERROR) << "LevelDB remove failed: " << status.ToString(); - foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); + leveldb::Status status = db_->Delete( + write_options, MakeSlice(MakeRegistrationKey(serialized_key))); + if (!status.ok()) + LOG(ERROR) << "LevelDB remove failed: " << status.ToString(); + foreground_task_runner_->PostTask( + FROM_HERE, base::Bind(callback, status.ok())); } void GCMStoreImpl::Backend::AddIncomingMessage(const std::string& persistent_id, @@ -902,7 +899,7 @@ bool GCMStoreImpl::Backend::LoadDeviceCredentials(uint64* android_id, } bool GCMStoreImpl::Backend::LoadRegistrations( - RegistrationInfoMap* registrations) { + std::map<std::string, std::string>* registrations) { leveldb::ReadOptions read_options; read_options.verify_checksums = true; @@ -916,13 +913,8 @@ bool GCMStoreImpl::Backend::LoadRegistrations( return false; } std::string app_id = ParseRegistrationKey(iter->key().ToString()); - linked_ptr<RegistrationInfo> registration(new RegistrationInfo); - if (!registration->ParseFromString(iter->value().ToString())) { - LOG(ERROR) << "Failed to parse registration with app id " << app_id; - return false; - } DVLOG(1) << "Found registration with app id " << app_id; - (*registrations)[app_id] = registration; + (*registrations)[app_id] = iter->value().ToString(); } return true; @@ -1182,16 +1174,15 @@ void GCMStoreImpl::SetDeviceCredentials(uint64 device_android_id, } void GCMStoreImpl::AddRegistration( - const std::string& app_id, - const linked_ptr<RegistrationInfo>& registration, + const std::string& serialized_key, + const std::string& serialized_value, const UpdateCallback& callback) { - std::string serialized_registration = registration->SerializeAsString(); blocking_task_runner_->PostTask( FROM_HERE, base::Bind(&GCMStoreImpl::Backend::AddRegistration, backend_, - app_id, - serialized_registration, + serialized_key, + serialized_value, callback)); } diff --git a/google_apis/gcm/engine/gcm_store_impl.h b/google_apis/gcm/engine/gcm_store_impl.h index 4815b20..8feebff 100644 --- a/google_apis/gcm/engine/gcm_store_impl.h +++ b/google_apis/gcm/engine/gcm_store_impl.h @@ -49,10 +49,10 @@ class GCM_EXPORT GCMStoreImpl : public GCMStore { const UpdateCallback& callback) override; // Registration info. - void AddRegistration(const std::string& app_id, - const linked_ptr<RegistrationInfo>& registration, + void AddRegistration(const std::string& serialized_key, + const std::string& serialized_value, const UpdateCallback& callback) override; - void RemoveRegistration(const std::string& app_id, + void RemoveRegistration(const std::string& serialized_key, const UpdateCallback& callback) override; // Unacknowledged incoming message handling. diff --git a/google_apis/gcm/engine/gcm_store_impl_unittest.cc b/google_apis/gcm/engine/gcm_store_impl_unittest.cc index b7bf75d..2fe64b4 100644 --- a/google_apis/gcm/engine/gcm_store_impl_unittest.cc +++ b/google_apis/gcm/engine/gcm_store_impl_unittest.cc @@ -232,22 +232,17 @@ TEST_F(GCMStoreImplTest, Registrations) { PumpLoop(); // Add one registration with one sender. - linked_ptr<RegistrationInfo> registration1(new RegistrationInfo); - registration1->sender_ids.push_back("sender1"); - registration1->registration_id = "registration1"; + std::string registration = "sender1=registration1"; gcm_store->AddRegistration( - "app1", - registration1, + kAppName, + registration, base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); PumpLoop(); // Add one registration with multiple senders. - linked_ptr<RegistrationInfo> registration2(new RegistrationInfo); - registration2->sender_ids.push_back("sender2_1"); - registration2->sender_ids.push_back("sender2_2"); - registration2->registration_id = "registration2"; + std::string registration2 = "sender1,sender2=registration2"; gcm_store->AddRegistration( - "app2", + kAppName2, registration2, base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); PumpLoop(); @@ -258,25 +253,15 @@ TEST_F(GCMStoreImplTest, Registrations) { PumpLoop(); ASSERT_EQ(2u, load_result->registrations.size()); - ASSERT_TRUE(load_result->registrations.find("app1") != + ASSERT_TRUE(load_result->registrations.find(kAppName) != load_result->registrations.end()); - EXPECT_EQ(registration1->registration_id, - load_result->registrations["app1"]->registration_id); - ASSERT_EQ(1u, load_result->registrations["app1"]->sender_ids.size()); - EXPECT_EQ(registration1->sender_ids[0], - load_result->registrations["app1"]->sender_ids[0]); - ASSERT_TRUE(load_result->registrations.find("app2") != + EXPECT_EQ(registration, load_result->registrations[kAppName]); + ASSERT_TRUE(load_result->registrations.find(kAppName2) != load_result->registrations.end()); - EXPECT_EQ(registration2->registration_id, - load_result->registrations["app2"]->registration_id); - ASSERT_EQ(2u, load_result->registrations["app2"]->sender_ids.size()); - EXPECT_EQ(registration2->sender_ids[0], - load_result->registrations["app2"]->sender_ids[0]); - EXPECT_EQ(registration2->sender_ids[1], - load_result->registrations["app2"]->sender_ids[1]); + EXPECT_EQ(registration2, load_result->registrations[kAppName2]); gcm_store->RemoveRegistration( - "app2", + kAppName2, base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); PumpLoop(); @@ -286,13 +271,9 @@ TEST_F(GCMStoreImplTest, Registrations) { PumpLoop(); ASSERT_EQ(1u, load_result->registrations.size()); - ASSERT_TRUE(load_result->registrations.find("app1") != + ASSERT_TRUE(load_result->registrations.find(kAppName) != load_result->registrations.end()); - EXPECT_EQ(registration1->registration_id, - load_result->registrations["app1"]->registration_id); - ASSERT_EQ(1u, load_result->registrations["app1"]->sender_ids.size()); - EXPECT_EQ(registration1->sender_ids[0], - load_result->registrations["app1"]->sender_ids[0]); + EXPECT_EQ(registration, load_result->registrations[kAppName]); } // Verify saving some incoming messages, reopening the directory, and then diff --git a/google_apis/gcm/engine/gcm_unregistration_request_handler.cc b/google_apis/gcm/engine/gcm_unregistration_request_handler.cc new file mode 100644 index 0000000..86f473b --- /dev/null +++ b/google_apis/gcm/engine/gcm_unregistration_request_handler.cc @@ -0,0 +1,68 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "google_apis/gcm/engine/gcm_unregistration_request_handler.h" + +#include "google_apis/gcm/base/gcm_util.h" +#include "net/url_request/url_fetcher.h" + +namespace gcm { + +namespace { + +// Request constants. +const char kUnregistrationCallerKey[] = "gcm_unreg_caller"; +// We are going to set the value to "false" in order to forcefully unregister +// the application. +const char kUnregistrationCallerValue[] = "false"; + +// Response constants. +const char kDeletedPrefix[] = "deleted="; +const char kErrorPrefix[] = "Error="; +const char kInvalidParameters[] = "INVALID_PARAMETERS"; + +} // namespace + +GCMUnregistrationRequestHandler::GCMUnregistrationRequestHandler( + const std::string& app_id) + : app_id_(app_id) { +} + +GCMUnregistrationRequestHandler::~GCMUnregistrationRequestHandler() {} + +void GCMUnregistrationRequestHandler::BuildRequestBody(std::string* body){ + BuildFormEncoding(kUnregistrationCallerKey, kUnregistrationCallerValue, body); +} + +UnregistrationRequest::Status GCMUnregistrationRequestHandler::ParseResponse( + const net::URLFetcher* source) { + std::string response; + if (!source->GetResponseAsString(&response)) { + DVLOG(1) << "Failed to get response body."; + return UnregistrationRequest::NO_RESPONSE_BODY; + } + + DVLOG(1) << "Parsing unregistration response."; + if (response.find(kDeletedPrefix) != std::string::npos) { + std::string deleted_app_id = response.substr( + response.find(kDeletedPrefix) + arraysize(kDeletedPrefix) - 1); + return deleted_app_id == app_id_ ? + UnregistrationRequest::SUCCESS : + UnregistrationRequest::INCORRECT_APP_ID; + } + + if (response.find(kErrorPrefix) != std::string::npos) { + std::string error = response.substr( + response.find(kErrorPrefix) + arraysize(kErrorPrefix) - 1); + return error == kInvalidParameters ? + UnregistrationRequest::INVALID_PARAMETERS : + UnregistrationRequest::UNKNOWN_ERROR; + } + + DVLOG(1) << "Not able to parse a meaningful output from response body." + << response; + return UnregistrationRequest::RESPONSE_PARSING_FAILED; +} + +} // namespace gcm diff --git a/google_apis/gcm/engine/gcm_unregistration_request_handler.h b/google_apis/gcm/engine/gcm_unregistration_request_handler.h new file mode 100644 index 0000000..ac2a5d4 --- /dev/null +++ b/google_apis/gcm/engine/gcm_unregistration_request_handler.h @@ -0,0 +1,33 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef GOOGLE_APIS_GCM_ENGINE_GCM_UNREGISTRATION_REQUEST_HANDLER_H_ +#define GOOGLE_APIS_GCM_ENGINE_GCM_UNREGISTRATION_REQUEST_HANDLER_H_ + +#include "google_apis/gcm/engine/unregistration_request.h" + +namespace gcm { + +// Used to revoke the registration ID when unregister is called or the +// application has been uninstalled. +class GCM_EXPORT GCMUnregistrationRequestHandler : + public UnregistrationRequest::CustomRequestHandler { + public: + GCMUnregistrationRequestHandler(const std::string& app_id); + ~GCMUnregistrationRequestHandler() override; + + // UnregistrationRequest::CustomRequestHandler overrides: + void BuildRequestBody(std::string* body) override; + UnregistrationRequest::Status ParseResponse( + const net::URLFetcher* source) override; + + private: + std::string app_id_; + + DISALLOW_COPY_AND_ASSIGN(GCMUnregistrationRequestHandler); +}; + +} // namespace gcm + +#endif // GOOGLE_APIS_GCM_ENGINE_GCM_UNREGISTRATION_REQUEST_HANDLER_H_ diff --git a/google_apis/gcm/engine/gservices_settings_unittest.cc b/google_apis/gcm/engine/gservices_settings_unittest.cc index 09977fa..fcf633e 100644 --- a/google_apis/gcm/engine/gservices_settings_unittest.cc +++ b/google_apis/gcm/engine/gservices_settings_unittest.cc @@ -4,7 +4,6 @@ #include "base/strings/string_number_conversions.h" #include "google_apis/gcm/engine/gservices_settings.h" -#include "google_apis/gcm/engine/registration_info.h" #include "testing/gtest/include/gtest/gtest.h" namespace gcm { diff --git a/google_apis/gcm/engine/instance_id_delete_token_request_handler.cc b/google_apis/gcm/engine/instance_id_delete_token_request_handler.cc new file mode 100644 index 0000000..1f3f2c3 --- /dev/null +++ b/google_apis/gcm/engine/instance_id_delete_token_request_handler.cc @@ -0,0 +1,65 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "google_apis/gcm/engine/instance_id_delete_token_request_handler.h" + +#include "base/strings/string_number_conversions.h" +#include "google_apis/gcm/base/gcm_util.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_request_context_getter.h" + +namespace gcm { + +namespace { + +// Request constants. +const char kGMSVersionKey[] = "gmsv"; +const char kInstanceIDKey[] = "appid"; +const char kSenderKey[] = "sender"; +const char kScopeKey[] = "scope"; + +// Response constants. +const char kTokenPrefix[] = "token="; + +} // namespace + +InstanceIDDeleteTokenRequestHandler::InstanceIDDeleteTokenRequestHandler( + const std::string& instance_id, + const std::string& authorized_entity, + const std::string& scope, + int gcm_version) + : instance_id_(instance_id), + authorized_entity_(authorized_entity), + scope_(scope), + gcm_version_(gcm_version) { + DCHECK(!instance_id.empty()); + DCHECK(!authorized_entity.empty()); + DCHECK(!scope.empty()); +} + +InstanceIDDeleteTokenRequestHandler::~InstanceIDDeleteTokenRequestHandler() {} + +void InstanceIDDeleteTokenRequestHandler::BuildRequestBody(std::string* body){ + BuildFormEncoding(kInstanceIDKey, instance_id_, body); + BuildFormEncoding(kSenderKey, authorized_entity_, body); + BuildFormEncoding(kScopeKey, scope_, body); + BuildFormEncoding(kGMSVersionKey, base::IntToString(gcm_version_), body); +} + +UnregistrationRequest::Status +InstanceIDDeleteTokenRequestHandler::ParseResponse( + const net::URLFetcher* source) { + std::string response; + if (!source->GetResponseAsString(&response)) { + DVLOG(1) << "Failed to get response body."; + return UnregistrationRequest::NO_RESPONSE_BODY; + } + + if (response.find(kTokenPrefix) == std::string::npos) + return UnregistrationRequest::RESPONSE_PARSING_FAILED; + + return UnregistrationRequest::SUCCESS; +} + +} // namespace gcm diff --git a/google_apis/gcm/engine/instance_id_delete_token_request_handler.h b/google_apis/gcm/engine/instance_id_delete_token_request_handler.h new file mode 100644 index 0000000..fec6e04 --- /dev/null +++ b/google_apis/gcm/engine/instance_id_delete_token_request_handler.h @@ -0,0 +1,41 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef GOOGLE_APIS_GCM_ENGINE_INSTANCE_ID_DELETE_TOKEN_REQUEST_HANDLER_H_ +#define GOOGLE_APIS_GCM_ENGINE_INSTANCE_ID_DELETE_TOKEN_REQUEST_HANDLER_H_ + +#include <string> + +#include "google_apis/gcm/engine/unregistration_request.h" + +namespace gcm { + +// Provides custom logic to handle DeleteToken request for InstanceID. +class GCM_EXPORT InstanceIDDeleteTokenRequestHandler : + public UnregistrationRequest::CustomRequestHandler { + public: + InstanceIDDeleteTokenRequestHandler( + const std::string& instance_id, + const std::string& authorized_entity, + const std::string& scope, + int gcm_version); + ~InstanceIDDeleteTokenRequestHandler() override; + + // UnregistrationRequest overrides: + void BuildRequestBody(std::string* body) override; + UnregistrationRequest::Status ParseResponse( + const net::URLFetcher* source) override; + + private: + std::string instance_id_; + std::string authorized_entity_; + std::string scope_; + int gcm_version_; + + DISALLOW_COPY_AND_ASSIGN(InstanceIDDeleteTokenRequestHandler); +}; + +} // namespace gcm + +#endif // GOOGLE_APIS_GCM_ENGINE_INSTANCE_ID_DELETE_TOKEN_REQUEST_HANDLER_H_ diff --git a/google_apis/gcm/engine/instance_id_get_token_request_handler.cc b/google_apis/gcm/engine/instance_id_get_token_request_handler.cc new file mode 100644 index 0000000..2e1c170 --- /dev/null +++ b/google_apis/gcm/engine/instance_id_get_token_request_handler.cc @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "google_apis/gcm/engine/instance_id_get_token_request_handler.h" + +#include "base/strings/string_number_conversions.h" +#include "google_apis/gcm/base/gcm_util.h" +#include "net/url_request/url_request_context_getter.h" + +namespace gcm { + +namespace { + +// Request constants. +const char kAuthorizedEntityKey[] = "sender"; +const char kGMSVersionKey[] = "gmsv"; +const char kInstanceIDKey[] = "appid"; +const char kScopeKey[] = "scope"; +// Prefix that needs to be added for each option key. +const char kOptionKeyPrefix[] = "X-"; + +} // namespace + +InstanceIDGetTokenRequestHandler::InstanceIDGetTokenRequestHandler( + const std::string& instance_id, + const std::string& authorized_entity, + const std::string& scope, + int gcm_version, + const std::map<std::string, std::string>& options) + : instance_id_(instance_id), + authorized_entity_(authorized_entity), + scope_(scope), + gcm_version_(gcm_version), + options_(options) { + DCHECK(!instance_id.empty()); + DCHECK(!authorized_entity.empty()); + DCHECK(!scope.empty()); +} + +InstanceIDGetTokenRequestHandler::~InstanceIDGetTokenRequestHandler() {} + +void InstanceIDGetTokenRequestHandler::BuildRequestBody(std::string* body){ + BuildFormEncoding(kScopeKey, scope_, body); + for (auto iter = options_.begin(); iter != options_.end(); ++iter) + BuildFormEncoding(kOptionKeyPrefix + iter->first, iter->second, body); + BuildFormEncoding(kGMSVersionKey, base::IntToString(gcm_version_), body); + BuildFormEncoding(kInstanceIDKey, instance_id_, body); + BuildFormEncoding(kAuthorizedEntityKey, authorized_entity_, body); +} + +} // namespace gcm diff --git a/google_apis/gcm/engine/instance_id_get_token_request_handler.h b/google_apis/gcm/engine/instance_id_get_token_request_handler.h new file mode 100644 index 0000000..c09b1ab --- /dev/null +++ b/google_apis/gcm/engine/instance_id_get_token_request_handler.h @@ -0,0 +1,42 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef GOOGLE_APIS_GCM_ENGINE_INSTANCE_ID_GET_TOKEN_REQUEST_HANDLER_H_ +#define GOOGLE_APIS_GCM_ENGINE_INSTANCE_ID_GET_TOKEN_REQUEST_HANDLER_H_ + +#include <map> +#include <string> + +#include "google_apis/gcm/engine/registration_request.h" + +namespace gcm { + +// Used to obtain a token based on Instance ID. +class GCM_EXPORT InstanceIDGetTokenRequestHandler : + public RegistrationRequest::CustomRequestHandler { + public: + InstanceIDGetTokenRequestHandler( + const std::string& instance_id, + const std::string& authorized_entity, + const std::string& scope, + int gcm_version, + const std::map<std::string, std::string>& options); + ~InstanceIDGetTokenRequestHandler() override; + + // RegistrationRequest overrides: + void BuildRequestBody(std::string* body) override; + + private: + std::string instance_id_; + std::string authorized_entity_; + std::string scope_; + int gcm_version_; + std::map<std::string, std::string> options_; + + DISALLOW_COPY_AND_ASSIGN(InstanceIDGetTokenRequestHandler); +}; + +} // namespace gcm + +#endif // GOOGLE_APIS_GCM_ENGINE_INSTANCE_ID_GET_TOKEN_REQUEST_HANDLER_H_ diff --git a/google_apis/gcm/engine/registration_info.cc b/google_apis/gcm/engine/registration_info.cc deleted file mode 100644 index 6a41c76..0000000 --- a/google_apis/gcm/engine/registration_info.cc +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "google_apis/gcm/engine/registration_info.h" - -#include "base/strings/string_util.h" - -namespace gcm { - -RegistrationInfo::RegistrationInfo() { -} - -RegistrationInfo::~RegistrationInfo() { -} - -std::string RegistrationInfo::SerializeAsString() const { - if (sender_ids.empty() || registration_id.empty()) - return std::string(); - - // Serialize as: - // sender1,sender2,...=reg_id - std::string value; - for (std::vector<std::string>::const_iterator iter = sender_ids.begin(); - iter != sender_ids.end(); ++iter) { - DCHECK(!iter->empty() && - iter->find(',') == std::string::npos && - iter->find('=') == std::string::npos); - if (!value.empty()) - value += ","; - value += *iter; - } - - DCHECK(registration_id.find('=') == std::string::npos); - value += '='; - value += registration_id; - return value; -} - -bool RegistrationInfo::ParseFromString(const std::string& value) { - if (value.empty()) - return true; - - size_t pos = value.find('='); - if (pos == std::string::npos) - return false; - - std::string senders = value.substr(0, pos); - registration_id = value.substr(pos + 1); - - Tokenize(senders, ",", &sender_ids); - - if (sender_ids.empty() || registration_id.empty()) { - sender_ids.clear(); - registration_id.clear(); - return false; - } - - return true; -} - -} // namespace gcm diff --git a/google_apis/gcm/engine/registration_info.h b/google_apis/gcm/engine/registration_info.h deleted file mode 100644 index c06a6aa..0000000 --- a/google_apis/gcm/engine/registration_info.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef GOOGLE_APIS_GCM_ENGINE_REGISTRATION_INFO_H_ -#define GOOGLE_APIS_GCM_ENGINE_REGISTRATION_INFO_H_ - -#include <map> -#include <string> -#include <vector> - -#include "base/basictypes.h" -#include "base/memory/linked_ptr.h" -#include "google_apis/gcm/base/gcm_export.h" - -namespace gcm { - -struct GCM_EXPORT RegistrationInfo { - RegistrationInfo(); - ~RegistrationInfo(); - - std::string SerializeAsString() const; - bool ParseFromString(const std::string& value); - - std::vector<std::string> sender_ids; - std::string registration_id; -}; - -// Map of app id to registration info. -typedef std::map<std::string, linked_ptr<RegistrationInfo> > -RegistrationInfoMap; - -} // namespace gcm - -#endif // GOOGLE_APIS_GCM_ENGINE_REGISTRATION_INFO_H_ diff --git a/google_apis/gcm/engine/registration_request.cc b/google_apis/gcm/engine/registration_request.cc index 2e52fba..b30bc4c 100644 --- a/google_apis/gcm/engine/registration_request.cc +++ b/google_apis/gcm/engine/registration_request.cc @@ -9,8 +9,8 @@ #include "base/metrics/histogram.h" #include "base/strings/string_number_conversions.h" #include "base/values.h" +#include "google_apis/gcm/base/gcm_util.h" #include "google_apis/gcm/monitoring/gcm_stats_recorder.h" -#include "net/base/escape.h" #include "net/base/load_flags.h" #include "net/http/http_request_headers.h" #include "net/http/http_status_code.h" @@ -30,10 +30,6 @@ const char kRegistrationRequestContentType[] = const char kAppIdKey[] = "app"; const char kDeviceIdKey[] = "device"; const char kLoginHeader[] = "AidLogin"; -const char kSenderKey[] = "sender"; - -// Request validation constants. -const size_t kMaxSenders = 100; // Response constants. const char kErrorPrefix[] = "Error="; @@ -43,14 +39,6 @@ const char kAuthenticationFailed[] = "AUTHENTICATION_FAILED"; const char kInvalidSender[] = "INVALID_SENDER"; const char kInvalidParameters[] = "INVALID_PARAMETERS"; -void BuildFormEncoding(const std::string& key, - const std::string& value, - std::string* out) { - if (!out->empty()) - out->append("&"); - out->append(key + "=" + net::EscapeUrlEncodedData(value, true)); -} - // Gets correct status from the error message. RegistrationRequest::Status GetStatusFromError(const std::string& error) { // TODO(fgorski): Improve error parsing in case there is nore then just an @@ -87,31 +75,39 @@ void RecordRegistrationStatusToUMA(RegistrationRequest::Status status) { RegistrationRequest::RequestInfo::RequestInfo( uint64 android_id, uint64 security_token, - const std::string& app_id, - const std::vector<std::string>& sender_ids) + const std::string& app_id) : android_id(android_id), security_token(security_token), - app_id(app_id), - sender_ids(sender_ids) { + app_id(app_id) { + DCHECK(android_id != 0UL); + DCHECK(security_token != 0UL); } RegistrationRequest::RequestInfo::~RequestInfo() {} +RegistrationRequest::CustomRequestHandler::CustomRequestHandler() {} + +RegistrationRequest::CustomRequestHandler::~CustomRequestHandler() {} + RegistrationRequest::RegistrationRequest( const GURL& registration_url, const RequestInfo& request_info, + scoped_ptr<CustomRequestHandler> custom_request_handler, const net::BackoffEntry::Policy& backoff_policy, const RegistrationCallback& callback, int max_retry_count, scoped_refptr<net::URLRequestContextGetter> request_context_getter, - GCMStatsRecorder* recorder) + GCMStatsRecorder* recorder, + const std::string& source_to_record) : callback_(callback), request_info_(request_info), + custom_request_handler_(custom_request_handler.Pass()), registration_url_(registration_url), backoff_entry_(&backoff_policy), request_context_getter_(request_context_getter), retries_left_(max_retry_count), recorder_(recorder), + source_to_record_(source_to_record), weak_ptr_factory_(this) { DCHECK_GE(max_retry_count, 0); } @@ -120,51 +116,49 @@ RegistrationRequest::~RegistrationRequest() {} void RegistrationRequest::Start() { DCHECK(!callback_.is_null()); - DCHECK(request_info_.android_id != 0UL); - DCHECK(request_info_.security_token != 0UL); - DCHECK(0 < request_info_.sender_ids.size() && - request_info_.sender_ids.size() <= kMaxSenders); - DCHECK(!url_fetcher_.get()); + url_fetcher_ = net::URLFetcher::Create(registration_url_, net::URLFetcher::POST, this); url_fetcher_->SetRequestContext(request_context_getter_.get()); url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES); - std::string android_id = base::Uint64ToString(request_info_.android_id); - std::string auth_header = - std::string(net::HttpRequestHeaders::kAuthorization) + ": " + - kLoginHeader + " " + android_id + ":" + - base::Uint64ToString(request_info_.security_token); - url_fetcher_->SetExtraRequestHeaders(auth_header); + std::string extra_headers; + BuildRequestHeaders(&extra_headers); + url_fetcher_->SetExtraRequestHeaders(extra_headers); std::string body; - BuildFormEncoding(kAppIdKey, request_info_.app_id, &body); - BuildFormEncoding(kDeviceIdKey, android_id, &body); - - std::string senders; - for (std::vector<std::string>::const_iterator iter = - request_info_.sender_ids.begin(); - iter != request_info_.sender_ids.end(); - ++iter) { - DCHECK(!iter->empty()); - if (!senders.empty()) - senders.append(","); - senders.append(*iter); - } - BuildFormEncoding(kSenderKey, senders, &body); - UMA_HISTOGRAM_COUNTS("GCM.RegistrationSenderIdCount", - request_info_.sender_ids.size()); + BuildRequestBody(&body); DVLOG(1) << "Performing registration for: " << request_info_.app_id; DVLOG(1) << "Registration request: " << body; url_fetcher_->SetUploadData(kRegistrationRequestContentType, body); - recorder_->RecordRegistrationSent(request_info_.app_id, senders); + recorder_->RecordRegistrationSent(request_info_.app_id, source_to_record_); request_start_time_ = base::TimeTicks::Now(); url_fetcher_->Start(); } +void RegistrationRequest::BuildRequestHeaders(std::string* extra_headers) { + net::HttpRequestHeaders headers; + headers.SetHeader( + net::HttpRequestHeaders::kAuthorization, + std::string(kLoginHeader) + " " + + base::Uint64ToString(request_info_.android_id) + ":" + + base::Uint64ToString(request_info_.security_token)); + *extra_headers = headers.ToString(); +} + +void RegistrationRequest::BuildRequestBody(std::string* body) { + BuildFormEncoding(kAppIdKey, request_info_.app_id, body); + BuildFormEncoding(kDeviceIdKey, + base::Uint64ToString(request_info_.android_id), + body); + + DCHECK(custom_request_handler_.get()); + custom_request_handler_->BuildRequestBody(body); +} + void RegistrationRequest::RetryWithBackoff(bool update_backoff) { if (update_backoff) { DCHECK_GT(retries_left_, 0); @@ -237,14 +231,14 @@ void RegistrationRequest::OnURLFetchComplete(const net::URLFetcher* source) { RecordRegistrationStatusToUMA(status); recorder_->RecordRegistrationResponse( request_info_.app_id, - request_info_.sender_ids, + source_to_record_, status); if (ShouldRetryWithStatus(status)) { if (retries_left_ > 0) { recorder_->RecordRegistrationRetryRequested( request_info_.app_id, - request_info_.sender_ids, + source_to_record_, retries_left_); RetryWithBackoff(true); return; @@ -253,7 +247,7 @@ void RegistrationRequest::OnURLFetchComplete(const net::URLFetcher* source) { status = REACHED_MAX_RETRIES; recorder_->RecordRegistrationResponse( request_info_.app_id, - request_info_.sender_ids, + source_to_record_, status); RecordRegistrationStatusToUMA(status); } diff --git a/google_apis/gcm/engine/registration_request.h b/google_apis/gcm/engine/registration_request.h index 1f707b9..c4c3ae7 100644 --- a/google_apis/gcm/engine/registration_request.h +++ b/google_apis/gcm/engine/registration_request.h @@ -6,6 +6,7 @@ #define GOOGLE_APIS_GCM_ENGINE_REGISTRATION_REQUEST_H_ #include <map> +#include <string> #include <vector> #include "base/basictypes.h" @@ -59,14 +60,12 @@ class GCM_EXPORT RegistrationRequest : public net::URLFetcherDelegate { const std::string& registration_id)> RegistrationCallback; - // Details of the of the Registration Request. Only user's android ID and - // its serial number are optional and can be set to 0. All other parameters - // have to be specified to successfully complete the call. + // Defines the common info about a registration/token request. All parameters + // are mandatory. struct GCM_EXPORT RequestInfo { RequestInfo(uint64 android_id, uint64 security_token, - const std::string& app_id, - const std::vector<std::string>& sender_ids); + const std::string& app_id); ~RequestInfo(); // Android ID of the device. @@ -75,20 +74,31 @@ class GCM_EXPORT RegistrationRequest : public net::URLFetcherDelegate { uint64 security_token; // Application ID. std::string app_id; - // Certificate of the application. - std::string cert; - // List of IDs of senders. Allowed up to 100. - std::vector<std::string> sender_ids; + }; + + // Encapsulates the custom logic that is needed to build and process the + // registration request. + class GCM_EXPORT CustomRequestHandler { + public: + CustomRequestHandler(); + virtual ~CustomRequestHandler(); + + // Builds the HTTP request body data. It is called after + // RegistrationRequest::BuildRequestBody to append more custom info to + // |body|. Note that the request body is encoded in HTTP form format. + virtual void BuildRequestBody(std::string* body) = 0; }; RegistrationRequest( const GURL& registration_url, const RequestInfo& request_info, + scoped_ptr<CustomRequestHandler> custom_request_handler, const net::BackoffEntry::Policy& backoff_policy, const RegistrationCallback& callback, int max_retry_count, scoped_refptr<net::URLRequestContextGetter> request_context_getter, - GCMStatsRecorder* recorder); + GCMStatsRecorder* recorder, + const std::string& source_to_record); ~RegistrationRequest() override; void Start(); @@ -101,12 +111,16 @@ class GCM_EXPORT RegistrationRequest : public net::URLFetcherDelegate { // failure, when |update_backoff| is true. void RetryWithBackoff(bool update_backoff); + void BuildRequestHeaders(std::string* extra_headers); + void BuildRequestBody(std::string* body); + // Parse the response returned by the URL fetcher into token, and returns the // status. Status ParseResponse(const net::URLFetcher* source, std::string* token); RegistrationCallback callback_; RequestInfo request_info_; + scoped_ptr<CustomRequestHandler> custom_request_handler_; GURL registration_url_; net::BackoffEntry backoff_entry_; @@ -117,6 +131,7 @@ class GCM_EXPORT RegistrationRequest : public net::URLFetcherDelegate { // Recorder that records GCM activities for debugging purpose. Not owned. GCMStatsRecorder* recorder_; + std::string source_to_record_; base::WeakPtrFactory<RegistrationRequest> weak_ptr_factory_; diff --git a/google_apis/gcm/engine/registration_request_unittest.cc b/google_apis/gcm/engine/registration_request_unittest.cc index b808f39..d85eeea 100644 --- a/google_apis/gcm/engine/registration_request_unittest.cc +++ b/google_apis/gcm/engine/registration_request_unittest.cc @@ -8,7 +8,8 @@ #include "base/strings/string_number_conversions.h" #include "base/strings/string_tokenizer.h" -#include "google_apis/gcm/engine/registration_request.h" +#include "google_apis/gcm/engine/gcm_registration_request_handler.h" +#include "google_apis/gcm/engine/instance_id_get_token_request_handler.h" #include "google_apis/gcm/monitoring/fake_gcm_stats_recorder.h" #include "net/url_request/test_url_fetcher_factory.h" #include "net/url_request/url_request_status.h" @@ -24,6 +25,9 @@ const char kDeveloperId[] = "Project1"; const char kLoginHeader[] = "AidLogin"; const char kRegistrationURL[] = "http://foo.bar/register"; const uint64 kSecurityToken = 77UL; +const int kGCMVersion = 40; +const char kInstanceId[] = "IID1"; +const char kScope[] = "GCM"; // Backoff policy for testing registration request. const net::BackoffEntry::Policy kDefaultBackoffPolicy = { @@ -64,7 +68,6 @@ class RegistrationRequestTest : public testing::Test { void RegistrationCallback(RegistrationRequest::Status status, const std::string& registration_id); - void CreateRequest(const std::string& sender_ids); void SetResponseStatusAndString(net::HttpStatusCode status_code, const std::string& response_body); void CompleteFetch(); @@ -102,26 +105,6 @@ void RegistrationRequestTest::RegistrationCallback( callback_called_ = true; } -void RegistrationRequestTest::CreateRequest(const std::string& sender_ids) { - std::vector<std::string> senders; - base::StringTokenizer tokenizer(sender_ids, ","); - while (tokenizer.GetNext()) - senders.push_back(tokenizer.token()); - - request_.reset(new RegistrationRequest( - GURL(kRegistrationURL), - RegistrationRequest::RequestInfo(kAndroidId, - kSecurityToken, - kAppId, - senders), - kDefaultBackoffPolicy, - base::Bind(&RegistrationRequestTest::RegistrationCallback, - base::Unretained(this)), - max_retry_count_, - url_request_context_getter_.get(), - &recorder_)); -} - void RegistrationRequestTest::SetResponseStatusAndString( net::HttpStatusCode status_code, const std::string& response_body) { @@ -141,7 +124,39 @@ void RegistrationRequestTest::CompleteFetch() { fetcher->delegate()->OnURLFetchComplete(fetcher); } -TEST_F(RegistrationRequestTest, RequestSuccessful) { +class GCMRegistrationRequestTest : public RegistrationRequestTest { + public: + GCMRegistrationRequestTest(); + ~GCMRegistrationRequestTest() override; + + void CreateRequest(const std::string& sender_ids); +}; + +GCMRegistrationRequestTest::GCMRegistrationRequestTest() { +} + +GCMRegistrationRequestTest::~GCMRegistrationRequestTest() { +} + +void GCMRegistrationRequestTest::CreateRequest(const std::string& sender_ids) { + RegistrationRequest::RequestInfo request_info( + kAndroidId, kSecurityToken, kAppId); + scoped_ptr<GCMRegistrationRequestHandler> request_handler( + new GCMRegistrationRequestHandler(sender_ids)); + request_.reset(new RegistrationRequest( + GURL(kRegistrationURL), + request_info, + request_handler.Pass(), + kDefaultBackoffPolicy, + base::Bind(&RegistrationRequestTest::RegistrationCallback, + base::Unretained(this)), + max_retry_count_, + url_request_context_getter_.get(), + &recorder_, + sender_ids)); +} + +TEST_F(GCMRegistrationRequestTest, RequestSuccessful) { set_max_retry_count(0); CreateRequest("sender1,sender2"); request_->Start(); @@ -154,7 +169,7 @@ TEST_F(RegistrationRequestTest, RequestSuccessful) { EXPECT_EQ("2501", registration_id_); } -TEST_F(RegistrationRequestTest, RequestDataAndURL) { +TEST_F(GCMRegistrationRequestTest, RequestDataAndURL) { CreateRequest(kDeveloperId); request_->Start(); @@ -198,7 +213,7 @@ TEST_F(RegistrationRequestTest, RequestDataAndURL) { EXPECT_EQ(0UL, expected_pairs.size()); } -TEST_F(RegistrationRequestTest, RequestRegistrationWithMultipleSenderIds) { +TEST_F(GCMRegistrationRequestTest, RequestRegistrationWithMultipleSenderIds) { CreateRequest("sender1,sender2@gmail.com"); request_->Start(); @@ -223,7 +238,7 @@ TEST_F(RegistrationRequestTest, RequestRegistrationWithMultipleSenderIds) { EXPECT_EQ("sender2@gmail.com", sender_tokenizer.token()); } -TEST_F(RegistrationRequestTest, ResponseParsing) { +TEST_F(GCMRegistrationRequestTest, ResponseParsing) { CreateRequest("sender1,sender2"); request_->Start(); @@ -235,7 +250,7 @@ TEST_F(RegistrationRequestTest, ResponseParsing) { EXPECT_EQ("2501", registration_id_); } -TEST_F(RegistrationRequestTest, ResponseHttpStatusNotOK) { +TEST_F(GCMRegistrationRequestTest, ResponseHttpStatusNotOK) { CreateRequest("sender1,sender2"); request_->Start(); @@ -252,7 +267,7 @@ TEST_F(RegistrationRequestTest, ResponseHttpStatusNotOK) { EXPECT_EQ("2501", registration_id_); } -TEST_F(RegistrationRequestTest, ResponseMissingRegistrationId) { +TEST_F(GCMRegistrationRequestTest, ResponseMissingRegistrationId) { CreateRequest("sender1,sender2"); request_->Start(); @@ -275,7 +290,7 @@ TEST_F(RegistrationRequestTest, ResponseMissingRegistrationId) { EXPECT_EQ("2501", registration_id_); } -TEST_F(RegistrationRequestTest, ResponseDeviceRegistrationError) { +TEST_F(GCMRegistrationRequestTest, ResponseDeviceRegistrationError) { CreateRequest("sender1,sender2"); request_->Start(); @@ -293,7 +308,7 @@ TEST_F(RegistrationRequestTest, ResponseDeviceRegistrationError) { EXPECT_EQ("2501", registration_id_); } -TEST_F(RegistrationRequestTest, ResponseAuthenticationError) { +TEST_F(GCMRegistrationRequestTest, ResponseAuthenticationError) { CreateRequest("sender1,sender2"); request_->Start(); @@ -312,7 +327,7 @@ TEST_F(RegistrationRequestTest, ResponseAuthenticationError) { EXPECT_EQ("2501", registration_id_); } -TEST_F(RegistrationRequestTest, ResponseInvalidParameters) { +TEST_F(GCMRegistrationRequestTest, ResponseInvalidParameters) { CreateRequest("sender1,sender2"); request_->Start(); @@ -324,7 +339,7 @@ TEST_F(RegistrationRequestTest, ResponseInvalidParameters) { EXPECT_EQ(std::string(), registration_id_); } -TEST_F(RegistrationRequestTest, ResponseInvalidSender) { +TEST_F(GCMRegistrationRequestTest, ResponseInvalidSender) { CreateRequest("sender1,sender2"); request_->Start(); @@ -336,7 +351,7 @@ TEST_F(RegistrationRequestTest, ResponseInvalidSender) { EXPECT_EQ(std::string(), registration_id_); } -TEST_F(RegistrationRequestTest, ResponseInvalidSenderBadRequest) { +TEST_F(GCMRegistrationRequestTest, ResponseInvalidSenderBadRequest) { CreateRequest("sender1"); request_->Start(); @@ -348,7 +363,7 @@ TEST_F(RegistrationRequestTest, ResponseInvalidSenderBadRequest) { EXPECT_EQ(std::string(), registration_id_); } -TEST_F(RegistrationRequestTest, RequestNotSuccessful) { +TEST_F(GCMRegistrationRequestTest, RequestNotSuccessful) { CreateRequest("sender1,sender2"); request_->Start(); @@ -371,7 +386,7 @@ TEST_F(RegistrationRequestTest, RequestNotSuccessful) { EXPECT_EQ("2501", registration_id_); } -TEST_F(RegistrationRequestTest, ResponseHttpNotOk) { +TEST_F(GCMRegistrationRequestTest, ResponseHttpNotOk) { CreateRequest("sender1,sender2"); request_->Start(); @@ -389,7 +404,7 @@ TEST_F(RegistrationRequestTest, ResponseHttpNotOk) { EXPECT_EQ("2501", registration_id_); } -TEST_F(RegistrationRequestTest, MaximumAttemptsReachedWithZeroRetries) { +TEST_F(GCMRegistrationRequestTest, MaximumAttemptsReachedWithZeroRetries) { set_max_retry_count(0); CreateRequest("sender1,sender2"); request_->Start(); @@ -402,7 +417,7 @@ TEST_F(RegistrationRequestTest, MaximumAttemptsReachedWithZeroRetries) { EXPECT_EQ(std::string(), registration_id_); } -TEST_F(RegistrationRequestTest, MaximumAttemptsReached) { +TEST_F(GCMRegistrationRequestTest, MaximumAttemptsReached) { CreateRequest("sender1,sender2"); request_->Start(); @@ -424,4 +439,128 @@ TEST_F(RegistrationRequestTest, MaximumAttemptsReached) { EXPECT_EQ(std::string(), registration_id_); } +class InstanceIDGetTokenRequestTest : public RegistrationRequestTest { + public: + InstanceIDGetTokenRequestTest(); + ~InstanceIDGetTokenRequestTest() override; + + void CreateRequest(const std::string& instance_id, + const std::string& authorized_entity, + const std::string& scope, + const std::map<std::string, std::string>& options); +}; + +InstanceIDGetTokenRequestTest::InstanceIDGetTokenRequestTest() { +} + +InstanceIDGetTokenRequestTest::~InstanceIDGetTokenRequestTest() { +} + +void InstanceIDGetTokenRequestTest::CreateRequest( + const std::string& instance_id, + const std::string& authorized_entity, + const std::string& scope, + const std::map<std::string, std::string>& options) { + RegistrationRequest::RequestInfo request_info( + kAndroidId, kSecurityToken, kAppId); + scoped_ptr<InstanceIDGetTokenRequestHandler> request_handler( + new InstanceIDGetTokenRequestHandler( + instance_id, authorized_entity, scope, kGCMVersion, options)); + request_.reset(new RegistrationRequest( + GURL(kRegistrationURL), + request_info, + request_handler.Pass(), + kDefaultBackoffPolicy, + base::Bind(&RegistrationRequestTest::RegistrationCallback, + base::Unretained(this)), + max_retry_count_, + url_request_context_getter_.get(), + &recorder_, + authorized_entity)); +} + +TEST_F(InstanceIDGetTokenRequestTest, RequestSuccessful) { + std::map<std::string, std::string> options; + options["Foo"] = "Bar"; + + set_max_retry_count(0); + CreateRequest(kInstanceId, kDeveloperId, kScope, options); + request_->Start(); + + SetResponseStatusAndString(net::HTTP_OK, "token=2501"); + CompleteFetch(); + + EXPECT_TRUE(callback_called_); + EXPECT_EQ(RegistrationRequest::SUCCESS, status_); + EXPECT_EQ("2501", registration_id_); +} + +TEST_F(InstanceIDGetTokenRequestTest, RequestDataAndURL) { + std::map<std::string, std::string> options; + options["Foo"] = "Bar"; + CreateRequest(kInstanceId, kDeveloperId, kScope, options); + request_->Start(); + + // Get data sent by request. + net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + + EXPECT_EQ(GURL(kRegistrationURL), fetcher->GetOriginalURL()); + + // Verify that authorization header was put together properly. + net::HttpRequestHeaders headers; + fetcher->GetExtraRequestHeaders(&headers); + std::string auth_header; + headers.GetHeader(net::HttpRequestHeaders::kAuthorization, &auth_header); + base::StringTokenizer auth_tokenizer(auth_header, " :"); + ASSERT_TRUE(auth_tokenizer.GetNext()); + EXPECT_EQ(kLoginHeader, auth_tokenizer.token()); + ASSERT_TRUE(auth_tokenizer.GetNext()); + EXPECT_EQ(base::Uint64ToString(kAndroidId), auth_tokenizer.token()); + ASSERT_TRUE(auth_tokenizer.GetNext()); + EXPECT_EQ(base::Uint64ToString(kSecurityToken), auth_tokenizer.token()); + + std::map<std::string, std::string> expected_pairs; + expected_pairs["gmsv"] = base::IntToString(kGCMVersion); + expected_pairs["app"] = kAppId; + expected_pairs["sender"] = kDeveloperId; + expected_pairs["device"] = base::Uint64ToString(kAndroidId); + expected_pairs["appid"] = kInstanceId; + expected_pairs["scope"] = kScope; + expected_pairs["X-Foo"] = "Bar"; + + // Verify data was formatted properly. + std::string upload_data = fetcher->upload_data(); + base::StringTokenizer data_tokenizer(upload_data, "&="); + while (data_tokenizer.GetNext()) { + std::map<std::string, std::string>::iterator iter = + expected_pairs.find(data_tokenizer.token()); + ASSERT_TRUE(iter != expected_pairs.end()); + ASSERT_TRUE(data_tokenizer.GetNext()); + EXPECT_EQ(iter->second, data_tokenizer.token()); + // Ensure that none of the keys appears twice. + expected_pairs.erase(iter); + } + + EXPECT_EQ(0UL, expected_pairs.size()); +} + +TEST_F(InstanceIDGetTokenRequestTest, ResponseHttpStatusNotOK) { + std::map<std::string, std::string> options; + CreateRequest(kInstanceId, kDeveloperId, kScope, options); + request_->Start(); + + SetResponseStatusAndString(net::HTTP_UNAUTHORIZED, "token=2501"); + CompleteFetch(); + + EXPECT_FALSE(callback_called_); + + SetResponseStatusAndString(net::HTTP_OK, "token=2501"); + CompleteFetch(); + + EXPECT_TRUE(callback_called_); + EXPECT_EQ(RegistrationRequest::SUCCESS, status_); + EXPECT_EQ("2501", registration_id_); +} + } // namespace gcm diff --git a/google_apis/gcm/engine/unregistration_request.cc b/google_apis/gcm/engine/unregistration_request.cc index 9b33778..931a5e9 100644 --- a/google_apis/gcm/engine/unregistration_request.cc +++ b/google_apis/gcm/engine/unregistration_request.cc @@ -10,6 +10,7 @@ #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" #include "base/values.h" +#include "google_apis/gcm/base/gcm_util.h" #include "google_apis/gcm/monitoring/gcm_stats_recorder.h" #include "net/base/escape.h" #include "net/http/http_request_headers.h" @@ -30,71 +31,6 @@ const char kDeleteKey[] = "delete"; const char kDeleteValue[] = "true"; const char kDeviceIdKey[] = "device"; const char kLoginHeader[] = "AidLogin"; -const char kUnregistrationCallerKey[] = "gcm_unreg_caller"; -// We are going to set the value to "false" in order to forcefully unregister -// the application. -const char kUnregistrationCallerValue[] = "false"; - -// Response constants. -const char kDeletedPrefix[] = "deleted="; -const char kErrorPrefix[] = "Error="; -const char kInvalidParameters[] = "INVALID_PARAMETERS"; - - -void BuildFormEncoding(const std::string& key, - const std::string& value, - std::string* out) { - if (!out->empty()) - out->append("&"); - out->append(key + "=" + net::EscapeUrlEncodedData(value, true)); -} - -UnregistrationRequest::Status ParseFetcherResponse( - const net::URLFetcher* source, - std::string request_app_id) { - if (!source->GetStatus().is_success()) { - DVLOG(1) << "Fetcher failed"; - return UnregistrationRequest::URL_FETCHING_FAILED; - } - - net::HttpStatusCode response_status = static_cast<net::HttpStatusCode>( - source->GetResponseCode()); - if (response_status != net::HTTP_OK) { - DVLOG(1) << "HTTP Status code is not OK, but: " << response_status; - if (response_status == net::HTTP_SERVICE_UNAVAILABLE) - return UnregistrationRequest::SERVICE_UNAVAILABLE; - else if (response_status == net::HTTP_INTERNAL_SERVER_ERROR) - return UnregistrationRequest::INTERNAL_SERVER_ERROR; - return UnregistrationRequest::HTTP_NOT_OK; - } - - std::string response; - if (!source->GetResponseAsString(&response)) { - DVLOG(1) << "Failed to get response body."; - return UnregistrationRequest::NO_RESPONSE_BODY; - } - - DVLOG(1) << "Parsing unregistration response."; - if (response.find(kDeletedPrefix) != std::string::npos) { - std::string app_id = response.substr( - response.find(kDeletedPrefix) + arraysize(kDeletedPrefix) - 1); - if (app_id == request_app_id) - return UnregistrationRequest::SUCCESS; - return UnregistrationRequest::INCORRECT_APP_ID; - } - - if (response.find(kErrorPrefix) != std::string::npos) { - std::string error = response.substr( - response.find(kErrorPrefix) + arraysize(kErrorPrefix) - 1); - if (error == kInvalidParameters) - return UnregistrationRequest::INVALID_PARAMETERS; - return UnregistrationRequest::UNKNOWN_ERROR; - } - - DVLOG(1) << "Not able to parse a meaningful output from response body." - << response; - return UnregistrationRequest::RESPONSE_PARSING_FAILED; -} } // namespace @@ -105,19 +41,27 @@ UnregistrationRequest::RequestInfo::RequestInfo( : android_id(android_id), security_token(security_token), app_id(app_id) { + DCHECK(android_id != 0UL); + DCHECK(security_token != 0UL); } UnregistrationRequest::RequestInfo::~RequestInfo() {} +UnregistrationRequest::CustomRequestHandler::CustomRequestHandler() {} + +UnregistrationRequest::CustomRequestHandler::~CustomRequestHandler() {} + UnregistrationRequest::UnregistrationRequest( const GURL& registration_url, const RequestInfo& request_info, + scoped_ptr<CustomRequestHandler> custom_request_handler, const net::BackoffEntry::Policy& backoff_policy, const UnregistrationCallback& callback, scoped_refptr<net::URLRequestContextGetter> request_context_getter, GCMStatsRecorder* recorder) : callback_(callback), request_info_(request_info), + custom_request_handler_(custom_request_handler.Pass()), registration_url_(registration_url), backoff_entry_(&backoff_policy), request_context_getter_(request_context_getter), @@ -129,30 +73,18 @@ UnregistrationRequest::~UnregistrationRequest() {} void UnregistrationRequest::Start() { DCHECK(!callback_.is_null()); - DCHECK(request_info_.android_id != 0UL); - DCHECK(request_info_.security_token != 0UL); DCHECK(!url_fetcher_.get()); url_fetcher_ = net::URLFetcher::Create(registration_url_, net::URLFetcher::POST, this); url_fetcher_->SetRequestContext(request_context_getter_.get()); - std::string android_id = base::Uint64ToString(request_info_.android_id); - std::string auth_header = - std::string(kLoginHeader) + " " + android_id + ":" + - base::Uint64ToString(request_info_.security_token); - net::HttpRequestHeaders headers; - headers.SetHeader(net::HttpRequestHeaders::kAuthorization, auth_header); - headers.SetHeader(kAppIdKey, request_info_.app_id); - url_fetcher_->SetExtraRequestHeaders(headers.ToString()); + std::string extra_headers; + BuildRequestHeaders(&extra_headers); + url_fetcher_->SetExtraRequestHeaders(extra_headers); std::string body; - BuildFormEncoding(kAppIdKey, request_info_.app_id, &body); - BuildFormEncoding(kDeviceIdKey, android_id, &body); - BuildFormEncoding(kDeleteKey, kDeleteValue, &body); - BuildFormEncoding(kUnregistrationCallerKey, - kUnregistrationCallerValue, - &body); + BuildRequestBody(&body); DVLOG(1) << "Unregistration request: " << body; url_fetcher_->SetUploadData(kRequestContentType, body); @@ -163,6 +95,50 @@ void UnregistrationRequest::Start() { url_fetcher_->Start(); } +void UnregistrationRequest::BuildRequestHeaders(std::string* extra_headers) { + net::HttpRequestHeaders headers; + headers.SetHeader( + net::HttpRequestHeaders::kAuthorization, + std::string(kLoginHeader) + " " + + base::Uint64ToString(request_info_.android_id) + ":" + + base::Uint64ToString(request_info_.security_token)); + headers.SetHeader(kAppIdKey, request_info_.app_id); + *extra_headers = headers.ToString(); +} + +void UnregistrationRequest::BuildRequestBody(std::string* body) { + BuildFormEncoding(kAppIdKey, request_info_.app_id, body); + BuildFormEncoding(kDeviceIdKey, + base::Uint64ToString(request_info_.android_id), + body); + BuildFormEncoding(kDeleteKey, kDeleteValue, body); + + DCHECK(custom_request_handler_.get()); + custom_request_handler_->BuildRequestBody(body); +} + +UnregistrationRequest::Status UnregistrationRequest::ParseResponse( + const net::URLFetcher* source) { + if (!source->GetStatus().is_success()) { + DVLOG(1) << "Fetcher failed"; + return URL_FETCHING_FAILED; + } + + net::HttpStatusCode response_status = static_cast<net::HttpStatusCode>( + source->GetResponseCode()); + if (response_status != net::HTTP_OK) { + DVLOG(1) << "HTTP Status code is not OK, but: " << response_status; + if (response_status == net::HTTP_SERVICE_UNAVAILABLE) + return SERVICE_UNAVAILABLE; + if (response_status == net::HTTP_INTERNAL_SERVER_ERROR) + return INTERNAL_SERVER_ERROR; + return HTTP_NOT_OK; + } + + DCHECK(custom_request_handler_.get()); + return custom_request_handler_->ParseResponse(source); +} + void UnregistrationRequest::RetryWithBackoff(bool update_backoff) { if (update_backoff) { url_fetcher_.reset(); @@ -190,8 +166,7 @@ void UnregistrationRequest::RetryWithBackoff(bool update_backoff) { } void UnregistrationRequest::OnURLFetchComplete(const net::URLFetcher* source) { - UnregistrationRequest::Status status = - ParseFetcherResponse(source, request_info_.app_id); + UnregistrationRequest::Status status = ParseResponse(source); DVLOG(1) << "UnregistrationRequestStauts: " << status; UMA_HISTOGRAM_ENUMERATION("GCM.UnregistrationRequestStatus", diff --git a/google_apis/gcm/engine/unregistration_request.h b/google_apis/gcm/engine/unregistration_request.h index c5c0eb5..61d9bff2 100644 --- a/google_apis/gcm/engine/unregistration_request.h +++ b/google_apis/gcm/engine/unregistration_request.h @@ -24,9 +24,9 @@ namespace gcm { class GCMStatsRecorder; -// Unregistration request is used to revoke registration IDs for applications -// that were uninstalled and should no longer receive GCM messages. In case an -// attempt to unregister fails, it will retry using the backoff policy. +// Encapsulates the common logic applying to both GCM unregistration requests +// and InstanceID delete-token requests. In case an attempt fails, it will retry +// using the backoff policy. // TODO(fgorski): Consider sharing code with RegistrationRequest if possible. class GCM_EXPORT UnregistrationRequest : public net::URLFetcherDelegate { public: @@ -55,7 +55,8 @@ class GCM_EXPORT UnregistrationRequest : public net::URLFetcherDelegate { // Callback completing the unregistration request. typedef base::Callback<void(Status success)> UnregistrationCallback; - // Details of the of the Unregistration Request. All parameters are mandatory. + // Defines the common info about an unregistration/token-deletion request. + // All parameters are mandatory. struct GCM_EXPORT RequestInfo { RequestInfo(uint64 android_id, uint64 security_token, @@ -70,12 +71,30 @@ class GCM_EXPORT UnregistrationRequest : public net::URLFetcherDelegate { std::string app_id; }; + // Encapsulates the custom logic that is needed to build and process the + // unregistration request. + class GCM_EXPORT CustomRequestHandler { + public: + CustomRequestHandler(); + virtual ~CustomRequestHandler(); + + // Builds the HTTP request body data. It is called after + // UnregistrationRequest::BuildRequestBody to append more custom info to + // |body|. Note that the request body is encoded in HTTP form format. + virtual void BuildRequestBody(std::string* body) = 0; + + // Parses the HTTP response. It is called after + // UnregistrationRequest::ParseResponse to proceed the parsing. + virtual Status ParseResponse(const net::URLFetcher* source) = 0; + }; + // Creates an instance of UnregistrationRequest. |callback| will be called // once registration has been revoked or there has been an error that makes // further retries pointless. UnregistrationRequest( const GURL& registration_url, const RequestInfo& request_info, + scoped_ptr<CustomRequestHandler> custom_request_handler, const net::BackoffEntry::Policy& backoff_policy, const UnregistrationCallback& callback, scoped_refptr<net::URLRequestContextGetter> request_context_getter, @@ -85,16 +104,21 @@ class GCM_EXPORT UnregistrationRequest : public net::URLFetcherDelegate { // Starts an unregistration request. void Start(); + private: // URLFetcherDelegate implementation. void OnURLFetchComplete(const net::URLFetcher* source) override; - private: + void BuildRequestHeaders(std::string* extra_headers); + void BuildRequestBody(std::string* body); + Status ParseResponse(const net::URLFetcher* source); + // Schedules a retry attempt and informs the backoff of previous request's // failure, when |update_backoff| is true. void RetryWithBackoff(bool update_backoff); UnregistrationCallback callback_; RequestInfo request_info_; + scoped_ptr<CustomRequestHandler> custom_request_handler_; GURL registration_url_; net::BackoffEntry backoff_entry_; diff --git a/google_apis/gcm/engine/unregistration_request_unittest.cc b/google_apis/gcm/engine/unregistration_request_unittest.cc index 1c736de..9e60297 100644 --- a/google_apis/gcm/engine/unregistration_request_unittest.cc +++ b/google_apis/gcm/engine/unregistration_request_unittest.cc @@ -8,7 +8,8 @@ #include "base/strings/string_number_conversions.h" #include "base/strings/string_tokenizer.h" -#include "google_apis/gcm/engine/unregistration_request.h" +#include "google_apis/gcm/engine/gcm_unregistration_request_handler.h" +#include "google_apis/gcm/engine/instance_id_delete_token_request_handler.h" #include "google_apis/gcm/monitoring/fake_gcm_stats_recorder.h" #include "net/url_request/test_url_fetcher_factory.h" #include "net/url_request/url_request_test_util.h" @@ -21,8 +22,13 @@ const uint64 kAndroidId = 42UL; const char kLoginHeader[] = "AidLogin"; const char kAppId[] = "TestAppId"; const char kDeletedAppId[] = "deleted=TestAppId"; +const char kDeletedToken[] = "token=SomeToken"; const char kRegistrationURL[] = "http://foo.bar/register"; const uint64 kSecurityToken = 77UL; +const int kGCMVersion = 40; +const char kInstanceId[] = "IID1"; +const char kDeveloperId[] = "Project1"; +const char kScope[] = "GCM"; // Backoff policy for testing registration request. const net::BackoffEntry::Policy kDefaultBackoffPolicy = { @@ -61,7 +67,6 @@ class UnregistrationRequestTest : public testing::Test { void UnregistrationCallback(UnregistrationRequest::Status status); - void CreateRequest(); void SetResponseStatusAndString(net::HttpStatusCode status_code, const std::string& response_body); void CompleteFetch(); @@ -90,19 +95,6 @@ void UnregistrationRequestTest::UnregistrationCallback( status_ = status; } -void UnregistrationRequestTest::CreateRequest() { - request_.reset(new UnregistrationRequest( - GURL(kRegistrationURL), - UnregistrationRequest::RequestInfo(kAndroidId, - kSecurityToken, - kAppId), - kDefaultBackoffPolicy, - base::Bind(&UnregistrationRequestTest::UnregistrationCallback, - base::Unretained(this)), - url_request_context_getter_.get(), - &recorder_)); -} - void UnregistrationRequestTest::SetResponseStatusAndString( net::HttpStatusCode status_code, const std::string& response_body) { @@ -120,7 +112,37 @@ void UnregistrationRequestTest::CompleteFetch() { fetcher->delegate()->OnURLFetchComplete(fetcher); } -TEST_F(UnregistrationRequestTest, RequestDataPassedToFetcher) { +class GCMUnregistrationRequestTest : public UnregistrationRequestTest { + public: + GCMUnregistrationRequestTest(); + ~GCMUnregistrationRequestTest() override; + + void CreateRequest(); +}; + +GCMUnregistrationRequestTest::GCMUnregistrationRequestTest() { +} + +GCMUnregistrationRequestTest::~GCMUnregistrationRequestTest() { +} + +void GCMUnregistrationRequestTest::CreateRequest() { + UnregistrationRequest::RequestInfo request_info( + kAndroidId, kSecurityToken, kAppId); + scoped_ptr<GCMUnregistrationRequestHandler> request_handler( + new GCMUnregistrationRequestHandler(kAppId)); + request_.reset(new UnregistrationRequest( + GURL(kRegistrationURL), + request_info, + request_handler.Pass(), + kDefaultBackoffPolicy, + base::Bind(&UnregistrationRequestTest::UnregistrationCallback, + base::Unretained(this)), + url_request_context_getter_.get(), + &recorder_)); +} + +TEST_F(GCMUnregistrationRequestTest, RequestDataPassedToFetcher) { CreateRequest(); request_->Start(); @@ -168,7 +190,7 @@ TEST_F(UnregistrationRequestTest, RequestDataPassedToFetcher) { EXPECT_EQ(0UL, expected_pairs.size()); } -TEST_F(UnregistrationRequestTest, SuccessfulUnregistration) { +TEST_F(GCMUnregistrationRequestTest, SuccessfulUnregistration) { CreateRequest(); request_->Start(); @@ -179,7 +201,7 @@ TEST_F(UnregistrationRequestTest, SuccessfulUnregistration) { EXPECT_EQ(UnregistrationRequest::SUCCESS, status_); } -TEST_F(UnregistrationRequestTest, ResponseHttpStatusNotOK) { +TEST_F(GCMUnregistrationRequestTest, ResponseHttpStatusNotOK) { CreateRequest(); request_->Start(); @@ -190,7 +212,7 @@ TEST_F(UnregistrationRequestTest, ResponseHttpStatusNotOK) { EXPECT_EQ(UnregistrationRequest::HTTP_NOT_OK, status_); } -TEST_F(UnregistrationRequestTest, ResponseEmpty) { +TEST_F(GCMUnregistrationRequestTest, ResponseEmpty) { CreateRequest(); request_->Start(); @@ -206,7 +228,7 @@ TEST_F(UnregistrationRequestTest, ResponseEmpty) { EXPECT_EQ(UnregistrationRequest::SUCCESS, status_); } -TEST_F(UnregistrationRequestTest, InvalidParametersError) { +TEST_F(GCMUnregistrationRequestTest, InvalidParametersError) { CreateRequest(); request_->Start(); @@ -217,7 +239,7 @@ TEST_F(UnregistrationRequestTest, InvalidParametersError) { EXPECT_EQ(UnregistrationRequest::INVALID_PARAMETERS, status_); } -TEST_F(UnregistrationRequestTest, UnkwnownError) { +TEST_F(GCMUnregistrationRequestTest, UnkwnownError) { CreateRequest(); request_->Start(); @@ -228,7 +250,7 @@ TEST_F(UnregistrationRequestTest, UnkwnownError) { EXPECT_EQ(UnregistrationRequest::UNKNOWN_ERROR, status_); } -TEST_F(UnregistrationRequestTest, ServiceUnavailable) { +TEST_F(GCMUnregistrationRequestTest, ServiceUnavailable) { CreateRequest(); request_->Start(); @@ -244,7 +266,7 @@ TEST_F(UnregistrationRequestTest, ServiceUnavailable) { EXPECT_EQ(UnregistrationRequest::SUCCESS, status_); } -TEST_F(UnregistrationRequestTest, InternalServerError) { +TEST_F(GCMUnregistrationRequestTest, InternalServerError) { CreateRequest(); request_->Start(); @@ -260,7 +282,7 @@ TEST_F(UnregistrationRequestTest, InternalServerError) { EXPECT_EQ(UnregistrationRequest::SUCCESS, status_); } -TEST_F(UnregistrationRequestTest, IncorrectAppId) { +TEST_F(GCMUnregistrationRequestTest, IncorrectAppId) { CreateRequest(); request_->Start(); @@ -276,7 +298,7 @@ TEST_F(UnregistrationRequestTest, IncorrectAppId) { EXPECT_EQ(UnregistrationRequest::SUCCESS, status_); } -TEST_F(UnregistrationRequestTest, ResponseParsingFailed) { +TEST_F(GCMUnregistrationRequestTest, ResponseParsingFailed) { CreateRequest(); request_->Start(); @@ -292,4 +314,113 @@ TEST_F(UnregistrationRequestTest, ResponseParsingFailed) { EXPECT_EQ(UnregistrationRequest::SUCCESS, status_); } +class InstaceIDDeleteTokenRequestTest : public UnregistrationRequestTest { + public: + InstaceIDDeleteTokenRequestTest(); + ~InstaceIDDeleteTokenRequestTest() override; + + void CreateRequest(const std::string& instance_id, + const std::string& authorized_entity, + const std::string& scope); +}; + +InstaceIDDeleteTokenRequestTest::InstaceIDDeleteTokenRequestTest() { +} + +InstaceIDDeleteTokenRequestTest::~InstaceIDDeleteTokenRequestTest() { +} + +void InstaceIDDeleteTokenRequestTest::CreateRequest( + const std::string& instance_id, + const std::string& authorized_entity, + const std::string& scope) { + UnregistrationRequest::RequestInfo request_info( + kAndroidId, kSecurityToken, kAppId); + scoped_ptr<InstanceIDDeleteTokenRequestHandler> request_handler( + new InstanceIDDeleteTokenRequestHandler( + instance_id, authorized_entity, scope, kGCMVersion)); + request_.reset(new UnregistrationRequest( + GURL(kRegistrationURL), + request_info, + request_handler.Pass(), + kDefaultBackoffPolicy, + base::Bind(&UnregistrationRequestTest::UnregistrationCallback, + base::Unretained(this)), + url_request_context_getter_.get(), + &recorder_)); +} + +TEST_F(InstaceIDDeleteTokenRequestTest, RequestDataPassedToFetcher) { + CreateRequest(kInstanceId, kDeveloperId, kScope); + request_->Start(); + + // Get data sent by request. + net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + + EXPECT_EQ(GURL(kRegistrationURL), fetcher->GetOriginalURL()); + + // Verify that authorization header was put together properly. + net::HttpRequestHeaders headers; + fetcher->GetExtraRequestHeaders(&headers); + std::string auth_header; + headers.GetHeader(net::HttpRequestHeaders::kAuthorization, &auth_header); + base::StringTokenizer auth_tokenizer(auth_header, " :"); + ASSERT_TRUE(auth_tokenizer.GetNext()); + EXPECT_EQ(kLoginHeader, auth_tokenizer.token()); + ASSERT_TRUE(auth_tokenizer.GetNext()); + EXPECT_EQ(base::Uint64ToString(kAndroidId), auth_tokenizer.token()); + ASSERT_TRUE(auth_tokenizer.GetNext()); + EXPECT_EQ(base::Uint64ToString(kSecurityToken), auth_tokenizer.token()); + std::string app_id_header; + headers.GetHeader("app", &app_id_header); + EXPECT_EQ(kAppId, app_id_header); + + std::map<std::string, std::string> expected_pairs; + expected_pairs["gmsv"] = base::IntToString(kGCMVersion); + expected_pairs["app"] = kAppId; + expected_pairs["device"] = base::Uint64ToString(kAndroidId); + expected_pairs["delete"] = "true"; + expected_pairs["appid"] = kInstanceId; + expected_pairs["sender"] = kDeveloperId; + expected_pairs["scope"] = kScope; + + // Verify data was formatted properly. + std::string upload_data = fetcher->upload_data(); + base::StringTokenizer data_tokenizer(upload_data, "&="); + while (data_tokenizer.GetNext()) { + std::map<std::string, std::string>::iterator iter = + expected_pairs.find(data_tokenizer.token()); + ASSERT_TRUE(iter != expected_pairs.end()) << data_tokenizer.token(); + ASSERT_TRUE(data_tokenizer.GetNext()) << data_tokenizer.token(); + EXPECT_EQ(iter->second, data_tokenizer.token()); + // Ensure that none of the keys appears twice. + expected_pairs.erase(iter); + } + + EXPECT_EQ(0UL, expected_pairs.size()); +} + +TEST_F(InstaceIDDeleteTokenRequestTest, SuccessfulUnregistration) { + CreateRequest(kInstanceId, kDeveloperId, kScope); + request_->Start(); + + SetResponseStatusAndString(net::HTTP_OK, kDeletedToken); + CompleteFetch(); + + EXPECT_TRUE(callback_called_); + EXPECT_EQ(UnregistrationRequest::SUCCESS, status_); +} + +TEST_F(InstaceIDDeleteTokenRequestTest, ResponseHttpStatusNotOK) { + CreateRequest(kInstanceId, kDeveloperId, kScope); + request_->Start(); + + SetResponseStatusAndString(net::HTTP_UNAUTHORIZED, ""); + CompleteFetch(); + + EXPECT_TRUE(callback_called_); + EXPECT_EQ(UnregistrationRequest::HTTP_NOT_OK, status_); +} + } // namespace gcm diff --git a/google_apis/gcm/gcm.gyp b/google_apis/gcm/gcm.gyp index 20a63b9..aa9df60 100644 --- a/google_apis/gcm/gcm.gyp +++ b/google_apis/gcm/gcm.gyp @@ -39,6 +39,8 @@ ], 'sources': [ # Note: sources list duplicated in GN build. + 'base/gcm_util.cc', + 'base/gcm_util.h', 'base/mcs_message.cc', 'base/mcs_message.h', 'base/mcs_util.cc', @@ -57,18 +59,24 @@ 'engine/connection_handler.h', 'engine/connection_handler_impl.cc', 'engine/connection_handler_impl.h', + 'engine/gcm_registration_request_handler.cc', + 'engine/gcm_registration_request_handler.h', 'engine/gcm_store.cc', 'engine/gcm_store.h', 'engine/gcm_store_impl.cc', 'engine/gcm_store_impl.h', + 'engine/gcm_unregistration_request_handler.cc', + 'engine/gcm_unregistration_request_handler.h', 'engine/gservices_settings.cc', 'engine/gservices_settings.h', 'engine/heartbeat_manager.cc', 'engine/heartbeat_manager.h', + 'engine/instance_id_delete_token_request_handler.cc', + 'engine/instance_id_delete_token_request_handler.h', + 'engine/instance_id_get_token_request_handler.cc', + 'engine/instance_id_get_token_request_handler.h', 'engine/mcs_client.cc', 'engine/mcs_client.h', - 'engine/registration_info.cc', - 'engine/registration_info.h', 'engine/registration_request.cc', 'engine/registration_request.h', 'engine/unregistration_request.cc', diff --git a/google_apis/gcm/monitoring/fake_gcm_stats_recorder.cc b/google_apis/gcm/monitoring/fake_gcm_stats_recorder.cc index b324727..0388d24 100644 --- a/google_apis/gcm/monitoring/fake_gcm_stats_recorder.cc +++ b/google_apis/gcm/monitoring/fake_gcm_stats_recorder.cc @@ -44,18 +44,18 @@ void FakeGCMStatsRecorder::RecordConnectionResetSignaled( void FakeGCMStatsRecorder::RecordRegistrationSent( const std::string& app_id, - const std::string& sender_ids) { + const std::string& senders) { } void FakeGCMStatsRecorder::RecordRegistrationResponse( const std::string& app_id, - const std::vector<std::string>& sender_ids, + const std::string& senders, RegistrationRequest::Status status) { } void FakeGCMStatsRecorder::RecordRegistrationRetryRequested( const std::string& app_id, - const std::vector<std::string>& sender_ids, + const std::string& senders, int retries_left) { } diff --git a/google_apis/gcm/monitoring/fake_gcm_stats_recorder.h b/google_apis/gcm/monitoring/fake_gcm_stats_recorder.h index d4503dd..5dcd448 100644 --- a/google_apis/gcm/monitoring/fake_gcm_stats_recorder.h +++ b/google_apis/gcm/monitoring/fake_gcm_stats_recorder.h @@ -26,13 +26,13 @@ class FakeGCMStatsRecorder : public GCMStatsRecorder { void RecordConnectionResetSignaled( ConnectionFactory::ConnectionResetReason reason) override; void RecordRegistrationSent(const std::string& app_id, - const std::string& sender_ids) override; + const std::string& senders) override; void RecordRegistrationResponse(const std::string& app_id, - const std::vector<std::string>& sender_ids, + const std::string& senders, RegistrationRequest::Status status) override; void RecordRegistrationRetryRequested( const std::string& app_id, - const std::vector<std::string>& sender_ids, + const std::string& senders, int retries_left) override; void RecordUnregistrationSent(const std::string& app_id) override; void RecordUnregistrationResponse( diff --git a/google_apis/gcm/monitoring/gcm_stats_recorder.h b/google_apis/gcm/monitoring/gcm_stats_recorder.h index 7191569..83367d8 100644 --- a/google_apis/gcm/monitoring/gcm_stats_recorder.h +++ b/google_apis/gcm/monitoring/gcm_stats_recorder.h @@ -32,7 +32,7 @@ class GCM_EXPORT GCMStatsRecorder { // A delegate interface that allows the GCMStatsRecorderImpl instance to // interact with its container. class Delegate { - public: + public: // Called when the GCMStatsRecorderImpl is recording activities and a new // activity has just been recorded. virtual void OnActivityRecorded() = 0; @@ -73,19 +73,19 @@ class GCM_EXPORT GCMStatsRecorder { // Records that a registration request has been sent. This could be initiated // directly from API, or from retry logic. virtual void RecordRegistrationSent(const std::string& app_id, - const std::string& sender_ids) = 0; + const std::string& senders) = 0; // Records that a registration response has been received from server. virtual void RecordRegistrationResponse( const std::string& app_id, - const std::vector<std::string>& sender_ids, + const std::string& senders, RegistrationRequest::Status status) = 0; // Records that a registration retry has been requested. The actual retry // action may not occur until some time later according to backoff logic. virtual void RecordRegistrationRetryRequested( const std::string& app_id, - const std::vector<std::string>& sender_ids, + const std::string& senders, int retries_left) = 0; // Records that an unregistration request has been sent. This could be |