123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- // Copyright 2018 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/invalidation/impl/fcm_network_handler.h"
- #include <memory>
- #include <string>
- #include "base/base64url.h"
- #include "base/bind.h"
- #include "base/callback.h"
- #include "base/feature_list.h"
- #include "base/i18n/time_formatting.h"
- #include "base/metrics/histogram_functions.h"
- #include "base/metrics/histogram_macros.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/string_piece.h"
- #include "base/strings/string_util.h"
- #include "build/build_config.h"
- #include "components/gcm_driver/gcm_driver.h"
- #include "components/gcm_driver/gcm_profile_service.h"
- #include "components/gcm_driver/instance_id/instance_id.h"
- #include "components/gcm_driver/instance_id/instance_id_driver.h"
- #include "components/invalidation/impl/invalidation_switches.h"
- #include "components/invalidation/impl/status.h"
- #include "components/invalidation/public/invalidator_state.h"
- using instance_id::InstanceID;
- namespace invalidation {
- namespace {
- const char kPayloadKey[] = "payload";
- const char kPublicTopic[] = "external_name";
- const char kVersionKey[] = "version";
- // OAuth2 Scope passed to getToken to obtain GCM registration tokens.
- // Must match Java GoogleCloudMessaging.INSTANCE_ID_SCOPE.
- const char kGCMScope[] = "GCM";
- // Lower bound time between two token validations when listening.
- const int kTokenValidationPeriodMinutesDefault = 60 * 24;
- // Returns the TTL (time-to-live) for the Instance ID token, or 0 if no TTL
- // should be specified.
- base::TimeDelta GetTimeToLive(const std::string& sender_id) {
- // This magic value is identical to kInvalidationGCMSenderId, i.e. the value
- // that Sync uses for its invalidations.
- if (sender_id == "8181035976") {
- if (!base::FeatureList::IsEnabled(switches::kSyncInstanceIDTokenTTL)) {
- return base::TimeDelta();
- }
- return base::Seconds(switches::kSyncInstanceIDTokenTTLSeconds.Get());
- }
- // This magic value is identical to kPolicyFCMInvalidationSenderID, i.e. the
- // value that ChromeOS policy uses for its invalidations.
- if (sender_id == "1013309121859") {
- if (!base::FeatureList::IsEnabled(switches::kPolicyInstanceIDTokenTTL)) {
- return base::TimeDelta();
- }
- return base::Seconds(switches::kPolicyInstanceIDTokenTTLSeconds.Get());
- }
- // The default for all other FCM clients is no TTL.
- return base::TimeDelta();
- }
- std::string GetValueFromMessage(const gcm::IncomingMessage& message,
- const std::string& key) {
- std::string value;
- auto it = message.data.find(key);
- if (it != message.data.end())
- value = it->second;
- return value;
- }
- // Unpacks the private topic included in messages to the form returned for
- // subscription requests.
- //
- // Subscriptions for private topics generate a private topic from the public
- // topic of the form "/private/${public_topic}-${something}. Messages include
- // this as the sender in the form
- // "/topics/private/${public_topic}-${something}". For such messages, strip the
- // "/topics" prefix.
- //
- // Subscriptions for public topics pass-through the public topic unchanged:
- // "${public_topic}". Messages include the sender in the form
- // "/topics/${public_topic}". For these messages, strip the "/topics/" prefix.
- //
- // If the provided sender does not match either pattern, return it unchanged.
- std::string UnpackPrivateTopic(base::StringPiece private_topic) {
- if (base::StartsWith(private_topic, "/topics/private/")) {
- return std::string(private_topic.substr(strlen("/topics")));
- } else if (base::StartsWith(private_topic, "/topics/")) {
- return std::string(private_topic.substr(strlen("/topics/")));
- } else {
- return std::string(private_topic);
- }
- }
- InvalidationParsingStatus ParseIncomingMessage(
- const gcm::IncomingMessage& message,
- std::string* payload,
- std::string* private_topic,
- std::string* public_topic,
- int64_t* version) {
- *payload = GetValueFromMessage(message, kPayloadKey);
- std::string version_str = GetValueFromMessage(message, kVersionKey);
- // Version must always be there, and be an integer.
- if (version_str.empty())
- return InvalidationParsingStatus::kVersionEmpty;
- if (!base::StringToInt64(version_str, version))
- return InvalidationParsingStatus::kVersionInvalid;
- *public_topic = GetValueFromMessage(message, kPublicTopic);
- *private_topic = UnpackPrivateTopic(message.sender_id);
- if (private_topic->empty())
- return InvalidationParsingStatus::kPrivateTopicEmpty;
- return InvalidationParsingStatus::kSuccess;
- }
- void RecordFCMMessageStatus(InvalidationParsingStatus status,
- const std::string& sender_id) {
- // These histograms are recorded quite frequently, so use the macros rather
- // than the functions.
- UMA_HISTOGRAM_ENUMERATION("FCMInvalidations.FCMMessageStatus", status);
- // Also split the histogram by a few well-known senders. The actual constants
- // aren't accessible here (they're defined in higher layers), so we simply
- // duplicate them here, strictly only for the purpose of metrics.
- constexpr char kInvalidationGCMSenderId[] = "8181035976";
- constexpr char kDriveFcmSenderId[] = "947318989803";
- constexpr char kPolicyFCMInvalidationSenderID[] = "1013309121859";
- if (sender_id == kInvalidationGCMSenderId) {
- UMA_HISTOGRAM_ENUMERATION("FCMInvalidations.FCMMessageStatus.Sync", status);
- } else if (sender_id == kDriveFcmSenderId) {
- UMA_HISTOGRAM_ENUMERATION("FCMInvalidations.FCMMessageStatus.Drive",
- status);
- } else if (sender_id == kPolicyFCMInvalidationSenderID) {
- UMA_HISTOGRAM_ENUMERATION("FCMInvalidations.FCMMessageStatus.Policy",
- status);
- }
- }
- } // namespace
- FCMNetworkHandler::FCMNetworkHandler(
- gcm::GCMDriver* gcm_driver,
- instance_id::InstanceIDDriver* instance_id_driver,
- const std::string& sender_id,
- const std::string& app_id)
- : gcm_driver_(gcm_driver),
- instance_id_driver_(instance_id_driver),
- token_validation_timer_(std::make_unique<base::OneShotTimer>()),
- sender_id_(sender_id),
- app_id_(app_id) {}
- FCMNetworkHandler::~FCMNetworkHandler() {
- StopListening();
- }
- // static
- std::unique_ptr<FCMNetworkHandler> FCMNetworkHandler::Create(
- gcm::GCMDriver* gcm_driver,
- instance_id::InstanceIDDriver* instance_id_driver,
- const std::string& sender_id,
- const std::string& app_id) {
- return std::make_unique<FCMNetworkHandler>(gcm_driver, instance_id_driver,
- sender_id, app_id);
- }
- void FCMNetworkHandler::StartListening() {
- if (IsListening()) {
- StopListening();
- }
- // Adding ourselves as Handler means start listening.
- // Being the listener is pre-requirement for token operations.
- gcm_driver_->AddAppHandler(app_id_, this);
- diagnostic_info_.instance_id_token_requested = base::Time::Now();
- instance_id_driver_->GetInstanceID(app_id_)->GetToken(
- sender_id_, kGCMScope, GetTimeToLive(sender_id_),
- /*flags=*/{InstanceID::Flags::kIsLazy},
- base::BindRepeating(&FCMNetworkHandler::DidRetrieveToken,
- weak_ptr_factory_.GetWeakPtr()));
- }
- void FCMNetworkHandler::StopListening() {
- if (IsListening())
- gcm_driver_->RemoveAppHandler(app_id_);
- }
- bool FCMNetworkHandler::IsListening() const {
- return gcm_driver_->GetAppHandler(app_id_);
- }
- void FCMNetworkHandler::DidRetrieveToken(const std::string& subscription_token,
- InstanceID::Result result) {
- base::UmaHistogramEnumeration("FCMInvalidations.InitialTokenRetrievalStatus",
- result);
- diagnostic_info_.registration_result = result;
- diagnostic_info_.token = subscription_token;
- diagnostic_info_.instance_id_token_was_received = base::Time::Now();
- switch (result) {
- case InstanceID::SUCCESS:
- // The received token is assumed to be valid, therefore, we reschedule
- // validation.
- DeliverToken(subscription_token);
- token_ = subscription_token;
- UpdateChannelState(FcmChannelState::ENABLED);
- break;
- case InstanceID::INVALID_PARAMETER:
- case InstanceID::DISABLED:
- case InstanceID::ASYNC_OPERATION_PENDING:
- case InstanceID::SERVER_ERROR:
- case InstanceID::UNKNOWN_ERROR:
- case InstanceID::NETWORK_ERROR:
- DLOG(WARNING) << "Messaging subscription failed; InstanceID::Result = "
- << result;
- UpdateChannelState(FcmChannelState::NO_INSTANCE_ID_TOKEN);
- break;
- }
- ScheduleNextTokenValidation();
- }
- void FCMNetworkHandler::ScheduleNextTokenValidation() {
- DCHECK(IsListening());
- token_validation_timer_->Start(
- FROM_HERE, base::Minutes(kTokenValidationPeriodMinutesDefault),
- base::BindOnce(&FCMNetworkHandler::StartTokenValidation,
- weak_ptr_factory_.GetWeakPtr()));
- }
- void FCMNetworkHandler::StartTokenValidation() {
- DCHECK(IsListening());
- diagnostic_info_.instance_id_token_verification_requested = base::Time::Now();
- diagnostic_info_.token_validation_requested_num++;
- instance_id_driver_->GetInstanceID(app_id_)->GetToken(
- sender_id_, kGCMScope, GetTimeToLive(sender_id_),
- /*flags=*/{InstanceID::Flags::kIsLazy},
- base::BindOnce(&FCMNetworkHandler::DidReceiveTokenForValidation,
- weak_ptr_factory_.GetWeakPtr()));
- }
- void FCMNetworkHandler::DidReceiveTokenForValidation(
- const std::string& new_token,
- InstanceID::Result result) {
- if (!IsListening()) {
- // After we requested the token, |StopListening| has been called. Thus,
- // ignore the token.
- return;
- }
- diagnostic_info_.instance_id_token_verified = base::Time::Now();
- diagnostic_info_.token_verification_result = result;
- if (result == InstanceID::SUCCESS) {
- UpdateChannelState(FcmChannelState::ENABLED);
- if (token_ != new_token) {
- diagnostic_info_.token_changed = true;
- token_ = new_token;
- DeliverToken(new_token);
- }
- }
- ScheduleNextTokenValidation();
- }
- void FCMNetworkHandler::UpdateChannelState(FcmChannelState state) {
- if (channel_state_ == state)
- return;
- channel_state_ = state;
- NotifyChannelStateChange(channel_state_);
- }
- void FCMNetworkHandler::ShutdownHandler() {}
- void FCMNetworkHandler::OnStoreReset() {}
- void FCMNetworkHandler::OnMessage(const std::string& app_id,
- const gcm::IncomingMessage& message) {
- DCHECK_EQ(app_id, app_id_);
- std::string payload;
- std::string private_topic;
- std::string public_topic;
- int64_t version = 0;
- InvalidationParsingStatus status = ParseIncomingMessage(
- message, &payload, &private_topic, &public_topic, &version);
- RecordFCMMessageStatus(status, sender_id_);
- if (status == InvalidationParsingStatus::kSuccess)
- DeliverIncomingMessage(payload, private_topic, public_topic, version);
- }
- void FCMNetworkHandler::OnMessagesDeleted(const std::string& app_id) {
- DCHECK_EQ(app_id, app_id_);
- // Note: As of 2020-02, this doesn't actually happen in practice.
- }
- void FCMNetworkHandler::OnSendError(
- const std::string& app_id,
- const gcm::GCMClient::SendErrorDetails& details) {
- // Should never be called because we don't send GCM messages to
- // the server.
- NOTREACHED() << "FCMNetworkHandler doesn't send GCM messages.";
- }
- void FCMNetworkHandler::OnSendAcknowledged(const std::string& app_id,
- const std::string& message_id) {
- // Should never be called because we don't send GCM messages to
- // the server.
- NOTREACHED() << "FCMNetworkHandler doesn't send GCM messages.";
- }
- void FCMNetworkHandler::SetTokenValidationTimerForTesting(
- std::unique_ptr<base::OneShotTimer> token_validation_timer) {
- token_validation_timer_ = std::move(token_validation_timer);
- }
- void FCMNetworkHandler::RequestDetailedStatus(
- const base::RepeatingCallback<void(base::Value::Dict)>& callback) {
- callback.Run(diagnostic_info_.CollectDebugData());
- }
- FCMNetworkHandler::FCMNetworkHandlerDiagnostic::FCMNetworkHandlerDiagnostic() =
- default;
- base::Value::Dict
- FCMNetworkHandler::FCMNetworkHandlerDiagnostic::CollectDebugData() const {
- base::Value::Dict status;
- status.SetByDottedPath("NetworkHandler.Registration-result-code",
- RegistrationResultToString(registration_result));
- status.SetByDottedPath("NetworkHandler.Token", token);
- status.SetByDottedPath(
- "NetworkHandler.Token-was-requested",
- base::TimeFormatShortDateAndTime(instance_id_token_requested));
- status.SetByDottedPath(
- "NetworkHandler.Token-was-received",
- base::TimeFormatShortDateAndTime(instance_id_token_was_received));
- status.SetByDottedPath("NetworkHandler.Token-verification-started",
- base::TimeFormatShortDateAndTime(
- instance_id_token_verification_requested));
- status.SetByDottedPath(
- "NetworkHandler.Token-was-verified",
- base::TimeFormatShortDateAndTime(instance_id_token_verified));
- status.SetByDottedPath("NetworkHandler.Verification-result-code",
- RegistrationResultToString(token_verification_result));
- status.SetByDottedPath("NetworkHandler.Token-changed-when-verified",
- token_changed);
- status.SetByDottedPath("NetworkHandler.Token-validation-requests",
- token_validation_requested_num);
- return status;
- }
- std::string
- FCMNetworkHandler::FCMNetworkHandlerDiagnostic::RegistrationResultToString(
- const instance_id::InstanceID::Result result) const {
- switch (registration_result) {
- case instance_id::InstanceID::SUCCESS:
- return "InstanceID::SUCCESS";
- case instance_id::InstanceID::INVALID_PARAMETER:
- return "InstanceID::INVALID_PARAMETER";
- case instance_id::InstanceID::DISABLED:
- return "InstanceID::DISABLED";
- case instance_id::InstanceID::ASYNC_OPERATION_PENDING:
- return "InstanceID::ASYNC_OPERATION_PENDING";
- case instance_id::InstanceID::SERVER_ERROR:
- return "InstanceID::SERVER_ERROR";
- case instance_id::InstanceID::UNKNOWN_ERROR:
- return "InstanceID::UNKNOWN_ERROR";
- case instance_id::InstanceID::NETWORK_ERROR:
- return "InstanceID::NETWORK_ERROR";
- }
- }
- } // namespace invalidation
|