// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/sharing_message/model/ios_sharing_device_registration_impl.h"
#import <stdint.h>
#import <vector>
#import "base/base64url.h"
#import "base/feature_list.h"
#import "base/functional/bind.h"
#import "base/metrics/histogram_functions.h"
#import "base/time/time.h"
#import "build/build_config.h"
#import "components/gcm_driver/crypto/p256_key_util.h"
#import "components/gcm_driver/instance_id/instance_id_driver.h"
#import "components/optimization_guide/core/optimization_guide_features.h"
#import "components/prefs/pref_service.h"
#import "components/sharing_message/buildflags.h"
#import "components/sharing_message/pref_names.h"
#import "components/sharing_message/sharing_constants.h"
#import "components/sharing_message/sharing_device_registration_result.h"
#import "components/sharing_message/sharing_sync_preference.h"
#import "components/sharing_message/sharing_target_device_info.h"
#import "components/sharing_message/sharing_utils.h"
#import "components/sharing_message/vapid_key_manager.h"
#import "components/sync/service/sync_service.h"
#import "components/sync_device_info/device_info.h"
#import "crypto/ec_private_key.h"
using instance_id::InstanceID;
using sync_pb::SharingSpecificFields;
IOSSharingDeviceRegistrationImpl::IOSSharingDeviceRegistrationImpl(
PrefService* pref_service,
SharingSyncPreference* sharing_sync_preference,
VapidKeyManager* vapid_key_manager,
instance_id::InstanceIDDriver* instance_id_driver,
syncer::SyncService* sync_service)
: pref_service_(pref_service),
sharing_sync_preference_(sharing_sync_preference),
vapid_key_manager_(vapid_key_manager),
instance_id_driver_(instance_id_driver),
sync_service_(sync_service) {}
IOSSharingDeviceRegistrationImpl::~IOSSharingDeviceRegistrationImpl() = default;
void IOSSharingDeviceRegistrationImpl::RegisterDevice(
RegistrationCallback callback) {
std::optional<std::string> authorized_entity = GetAuthorizationEntity();
if (!authorized_entity) {
OnVapidTargetInfoRetrieved(std::move(callback),
/*authorized_entity=*/std::nullopt,
SharingDeviceRegistrationResult::kSuccess,
/*vapid_target_info=*/std::nullopt);
return;
}
RetrieveTargetInfo(
*authorized_entity,
base::BindOnce(
&IOSSharingDeviceRegistrationImpl::OnVapidTargetInfoRetrieved,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
*authorized_entity));
}
void IOSSharingDeviceRegistrationImpl::RetrieveTargetInfo(
const std::string& authorized_entity,
TargetInfoCallback callback) {
instance_id_driver_->GetInstanceID(kSharingFCMAppID)
->GetToken(
authorized_entity, instance_id::kGCMScope,
/*time_to_live=*/base::TimeDelta(),
/*flags=*/{InstanceID::Flags::kBypassScheduler},
base::BindOnce(&IOSSharingDeviceRegistrationImpl::OnFCMTokenReceived,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
authorized_entity));
}
void IOSSharingDeviceRegistrationImpl::OnFCMTokenReceived(
TargetInfoCallback callback,
const std::string& authorized_entity,
const std::string& fcm_token,
instance_id::InstanceID::Result result) {
switch (result) {
case InstanceID::SUCCESS:
instance_id_driver_->GetInstanceID(kSharingFCMAppID)
->GetEncryptionInfo(
authorized_entity,
base::BindOnce(
&IOSSharingDeviceRegistrationImpl::OnEncryptionInfoReceived,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
fcm_token));
break;
case InstanceID::NETWORK_ERROR:
case InstanceID::SERVER_ERROR:
case InstanceID::ASYNC_OPERATION_PENDING:
std::move(callback).Run(
SharingDeviceRegistrationResult::kFcmTransientError, std::nullopt);
break;
case InstanceID::INVALID_PARAMETER:
case InstanceID::UNKNOWN_ERROR:
case InstanceID::DISABLED:
std::move(callback).Run(SharingDeviceRegistrationResult::kFcmFatalError,
std::nullopt);
break;
}
}
void IOSSharingDeviceRegistrationImpl::OnEncryptionInfoReceived(
TargetInfoCallback callback,
const std::string& fcm_token,
std::string p256dh,
std::string auth_secret) {
std::move(callback).Run(
SharingDeviceRegistrationResult::kSuccess,
std::make_optional(syncer::DeviceInfo::SharingTargetInfo{
fcm_token, p256dh, auth_secret}));
}
void IOSSharingDeviceRegistrationImpl::OnVapidTargetInfoRetrieved(
RegistrationCallback callback,
std::optional<std::string> authorized_entity,
SharingDeviceRegistrationResult result,
std::optional<syncer::DeviceInfo::SharingTargetInfo> vapid_target_info) {
if (result != SharingDeviceRegistrationResult::kSuccess) {
std::move(callback).Run(result);
return;
}
if (!CanSendViaSenderID(sync_service_)) {
OnSharingTargetInfoRetrieved(
std::move(callback), std::move(authorized_entity),
std::move(vapid_target_info), SharingDeviceRegistrationResult::kSuccess,
/*sharing_target_info=*/std::nullopt);
return;
}
// Attempt to register using sender ID when enabled.
RetrieveTargetInfo(
kSharingSenderID,
base::BindOnce(
&IOSSharingDeviceRegistrationImpl::OnSharingTargetInfoRetrieved,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
std::move(authorized_entity), std::move(vapid_target_info)));
}
void IOSSharingDeviceRegistrationImpl::OnSharingTargetInfoRetrieved(
RegistrationCallback callback,
std::optional<std::string> authorized_entity,
std::optional<syncer::DeviceInfo::SharingTargetInfo> vapid_target_info,
SharingDeviceRegistrationResult result,
std::optional<syncer::DeviceInfo::SharingTargetInfo> sharing_target_info) {
if (result != SharingDeviceRegistrationResult::kSuccess) {
std::move(callback).Run(result);
return;
}
if (!vapid_target_info && !sharing_target_info) {
std::move(callback).Run(SharingDeviceRegistrationResult::kInternalError);
return;
}
base::UmaHistogramBoolean("Sharing.LocalSharingTargetInfoSupportsSync",
!!sharing_target_info);
std::set<SharingSpecificFields::EnabledFeatures> enabled_features =
GetEnabledFeatures(/*supports_vapid=*/authorized_entity.has_value());
syncer::DeviceInfo::SharingInfo sharing_info(
vapid_target_info ? std::move(*vapid_target_info)
: syncer::DeviceInfo::SharingTargetInfo(),
sharing_target_info ? std::move(*sharing_target_info)
: syncer::DeviceInfo::SharingTargetInfo(),
/*chime_representative_target_id=*/std::string(),
std::move(enabled_features));
sharing_sync_preference_->SetLocalSharingInfo(std::move(sharing_info));
sharing_sync_preference_->SetFCMRegistration(
// Clears authorized_entity in preferences if it's not populated.
SharingSyncPreference::FCMRegistration(std::move(authorized_entity),
base::Time::Now()));
std::move(callback).Run(SharingDeviceRegistrationResult::kSuccess);
}
void IOSSharingDeviceRegistrationImpl::UnregisterDevice(
RegistrationCallback callback) {
auto registration = sharing_sync_preference_->GetFCMRegistration();
if (!registration) {
std::move(callback).Run(
SharingDeviceRegistrationResult::kDeviceNotRegistered);
return;
}
sharing_sync_preference_->ClearLocalSharingInfo();
if (!registration->authorized_entity) {
OnVapidFCMTokenDeleted(std::move(callback),
SharingDeviceRegistrationResult::kSuccess);
return;
}
DeleteFCMToken(
*registration->authorized_entity,
base::BindOnce(&IOSSharingDeviceRegistrationImpl::OnVapidFCMTokenDeleted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void IOSSharingDeviceRegistrationImpl::OnVapidFCMTokenDeleted(
RegistrationCallback callback,
SharingDeviceRegistrationResult result) {
if (result != SharingDeviceRegistrationResult::kSuccess) {
std::move(callback).Run(result);
return;
}
DeleteFCMToken(kSharingSenderID, std::move(callback));
}
void IOSSharingDeviceRegistrationImpl::DeleteFCMToken(
const std::string& authorized_entity,
RegistrationCallback callback) {
instance_id_driver_->GetInstanceID(kSharingFCMAppID)
->DeleteToken(
authorized_entity, instance_id::kGCMScope,
base::BindOnce(&IOSSharingDeviceRegistrationImpl::OnFCMTokenDeleted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void IOSSharingDeviceRegistrationImpl::OnFCMTokenDeleted(
RegistrationCallback callback,
InstanceID::Result result) {
switch (result) {
case InstanceID::SUCCESS:
// INVALID_PARAMETER is expected if InstanceID.GetToken hasn't been
// invoked since restart.
case InstanceID::INVALID_PARAMETER:
sharing_sync_preference_->ClearFCMRegistration();
std::move(callback).Run(SharingDeviceRegistrationResult::kSuccess);
return;
case InstanceID::NETWORK_ERROR:
case InstanceID::SERVER_ERROR:
case InstanceID::ASYNC_OPERATION_PENDING:
std::move(callback).Run(
SharingDeviceRegistrationResult::kFcmTransientError);
return;
case InstanceID::UNKNOWN_ERROR:
case InstanceID::DISABLED:
std::move(callback).Run(SharingDeviceRegistrationResult::kFcmFatalError);
return;
}
NOTREACHED_IN_MIGRATION();
}
std::optional<std::string>
IOSSharingDeviceRegistrationImpl::GetAuthorizationEntity() const {
crypto::ECPrivateKey* vapid_key = vapid_key_manager_->GetOrCreateKey();
if (!vapid_key) {
return std::nullopt;
}
std::string public_key;
if (!gcm::GetRawPublicKey(*vapid_key, &public_key)) {
return std::nullopt;
}
std::string base64_public_key;
base::Base64UrlEncode(public_key, base::Base64UrlEncodePolicy::OMIT_PADDING,
&base64_public_key);
return std::make_optional(std::move(base64_public_key));
}
std::set<SharingSpecificFields::EnabledFeatures>
IOSSharingDeviceRegistrationImpl::GetEnabledFeatures(
bool supports_vapid) const {
// Used in tests
if (enabled_features_testing_value_) {
return enabled_features_testing_value_.value();
}
std::set<SharingSpecificFields::EnabledFeatures> enabled_features;
if (IsOptimizationGuidePushNotificationSupported()) {
enabled_features.insert(
SharingSpecificFields::OPTIMIZATION_GUIDE_PUSH_NOTIFICATION);
}
return enabled_features;
}
bool IOSSharingDeviceRegistrationImpl::IsClickToCallSupported() const {
return false;
}
bool IOSSharingDeviceRegistrationImpl::IsSharedClipboardSupported() const {
return false;
}
bool IOSSharingDeviceRegistrationImpl::IsSmsFetcherSupported() const {
return false;
}
bool IOSSharingDeviceRegistrationImpl::IsRemoteCopySupported() const {
return false;
}
bool IOSSharingDeviceRegistrationImpl::
IsOptimizationGuidePushNotificationSupported() const {
return optimization_guide::features::IsOptimizationHintsEnabled() &&
optimization_guide::features::IsPushNotificationsEnabled();
}
void IOSSharingDeviceRegistrationImpl::SetEnabledFeaturesForTesting(
std::set<SharingSpecificFields::EnabledFeatures> enabled_features) {
enabled_features_testing_value_ = std::move(enabled_features);
}