// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl.h"
#include <array>
#include <string>
#include "base/command_line.h"
#include "base/containers/flat_map.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/nearby_sharing/certificates/common.h"
#include "chrome/browser/nearby_sharing/certificates/constants.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.h"
#include "chrome/browser/nearby_sharing/client/nearby_share_client.h"
#include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
#include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
#include "chrome/browser/nearby_sharing/common/nearby_share_profile_info_provider.h"
#include "chrome/browser/nearby_sharing/common/nearby_share_switches.h"
#include "chromeos/ash/components/nearby/common/client/nearby_http_result.h"
#include "chromeos/ash/components/nearby/common/scheduling/nearby_scheduler_factory.h"
#include "chromeos/ash/services/nearby/public/mojom/nearby_share_settings.mojom.h"
#include "components/cross_device/logging/logging.h"
#include "components/leveldb_proto/public/proto_database_provider.h"
#include "components/prefs/pref_service.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/floss/floss_features.h"
#include "device/bluetooth/public/cpp/bluetooth_address.h"
#include "third_party/nearby/sharing/proto/certificate_rpc.pb.h"
#include "third_party/nearby/sharing/proto/encrypted_metadata.pb.h"
namespace {
const char kDeviceIdPrefix[] = "users/me/devices/";
constexpr base::TimeDelta kListPublicCertificatesTimeout = base::Seconds(30);
constexpr std::array<nearby_share::mojom::Visibility, 3> kVisibilities = {
nearby_share::mojom::Visibility::kAllContacts,
nearby_share::mojom::Visibility::kSelectedContacts,
nearby_share::mojom::Visibility::kYourDevices};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum GetDecryptedPublicCertificateResult {
kSuccess = 0,
kNoMatch = 1,
kStorageFailure = 2,
kMaxValue = kStorageFailure
};
// Check for a command-line override for number of certificates, otherwise
// return the default |kNearbyShareNumPrivateCertificates|.
size_t NumPrivateCertificates() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (!command_line->HasSwitch(switches::kNearbyShareNumPrivateCertificates)) {
return kNearbyShareNumPrivateCertificates;
}
std::string num_certificates_str = command_line->GetSwitchValueASCII(
switches::kNearbyShareNumPrivateCertificates);
int num_certificates = 0;
if (!base::StringToInt(num_certificates_str, &num_certificates) ||
num_certificates < 1) {
CD_LOG(ERROR, Feature::NS)
<< __func__
<< ": Invalid value provided with num certificates override.";
return kNearbyShareNumPrivateCertificates;
}
return static_cast<size_t>(num_certificates);
}
size_t NumExpectedPrivateCertificates() {
return kVisibilities.size() * NumPrivateCertificates();
}
std::optional<std::string> GetBluetoothMacAddress(
device::BluetoothAdapter* bluetooth_adapter) {
if (!bluetooth_adapter) {
CD_LOG(WARNING, Feature::NS)
<< __func__
<< ": Failed to get Bluetooth MAC address; Bluetooth adapter is null.";
return std::nullopt;
}
if (!bluetooth_adapter->IsPresent()) {
// Note: The sophisticated solution would be to listen for
// device::BluetoothAdapter::Observer::AdapterPresentChanged() before trying
// to generate private certificates. We take the simple but unsophisticated
// approach by failing and retrying.
CD_LOG(WARNING, Feature::NS)
<< __func__ << ": Failed to get Bluetooth MAC address; Bluetooth "
<< "adapter is not present.";
return std::nullopt;
}
std::array<uint8_t, 6> bytes;
if (!device::ParseBluetoothAddress(bluetooth_adapter->GetAddress(), bytes)) {
CD_LOG(WARNING, Feature::NS)
<< __func__ << ": Failed to get Bluetooth MAC address; cannot parse "
<< "address: " << bluetooth_adapter->GetAddress();
return std::nullopt;
}
return std::string(bytes.begin(), bytes.end());
}
std::optional<nearby::sharing::proto::EncryptedMetadata> BuildMetadata(
std::string device_name,
std::optional<std::string> full_name,
std::optional<std::string> icon_url,
std::optional<std::string> account_name,
device::BluetoothAdapter* bluetooth_adapter) {
nearby::sharing::proto::EncryptedMetadata metadata;
if (device_name.empty()) {
CD_LOG(WARNING, Feature::NS)
<< __func__ << ": Failed to create private certificate metadata; "
<< "missing device name.";
return std::nullopt;
}
metadata.set_device_name(device_name);
if (full_name) {
metadata.set_full_name(*full_name);
}
if (icon_url) {
metadata.set_icon_url(*icon_url);
}
if (account_name) {
metadata.set_account_name(*account_name);
}
std::optional<std::string> bluetooth_mac_address =
GetBluetoothMacAddress(bluetooth_adapter);
base::UmaHistogramBoolean(
"Nearby.Share.Certificates.Manager."
"BluetoothMacAddressPresentForPrivateCertificateCreation",
bluetooth_mac_address.has_value());
if (!bluetooth_mac_address) {
CD_LOG(WARNING, Feature::NS)
<< __func__ << ": Failed to create private certificate metadata; "
<< "missing adapter address.";
return std::nullopt;
}
metadata.set_bluetooth_mac_address(*bluetooth_mac_address);
return metadata;
}
void RecordGetDecryptedPublicCertificateResultMetric(
GetDecryptedPublicCertificateResult result) {
base::UmaHistogramEnumeration(
"Nearby.Share.Certificates.Manager.GetDecryptedPublicCertificateResult",
result);
}
void RecordDownloadPublicCertificatesResultMetrics(
bool success,
ash::nearby::NearbyHttpResult result,
size_t page_number,
size_t certificate_count) {
base::UmaHistogramBoolean(
"Nearby.Share.Certificates.Manager.DownloadPublicCertificatesSuccessRate",
success);
base::UmaHistogramEnumeration(
"Nearby.Share.Certificates.Manager.DownloadPublicCertificatesHttpResult",
result);
if (success) {
base::UmaHistogramExactLinear(
"Nearby.Share.Certificates.Manager."
"DownloadPublicCertificatesSuccessPageCount",
page_number, 20);
base::UmaHistogramCounts10000(
"Nearby.Share.Certificates.Manager."
"DownloadPublicCertificatesCount",
certificate_count);
} else {
base::UmaHistogramExactLinear(
"Nearby.Share.Certificates.Manager."
"DownloadPublicCertificatesFailuePageCount",
page_number, 20);
}
}
void TryDecryptPublicCertificates(
const NearbyShareEncryptedMetadataKey& encrypted_metadata_key,
NearbyShareCertificateManager::CertDecryptedCallback callback,
bool success,
std::unique_ptr<std::vector<nearby::sharing::proto::PublicCertificate>>
public_certificates) {
if (!success || !public_certificates) {
CD_LOG(ERROR, Feature::NS)
<< __func__ << ": Failed to read public certificates from storage.";
RecordGetDecryptedPublicCertificateResultMetric(
GetDecryptedPublicCertificateResult::kStorageFailure);
std::move(callback).Run(std::nullopt);
return;
}
for (const auto& cert : *public_certificates) {
std::optional<NearbyShareDecryptedPublicCertificate> decrypted =
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
cert, encrypted_metadata_key);
if (decrypted) {
CD_LOG(VERBOSE, Feature::NS)
<< __func__ << ": Successfully decrypted public certificate with ID "
<< base::HexEncode(decrypted->id());
RecordGetDecryptedPublicCertificateResultMetric(
GetDecryptedPublicCertificateResult::kSuccess);
std::move(callback).Run(std::move(decrypted));
return;
}
}
CD_LOG(VERBOSE, Feature::NS)
<< __func__
<< ": Metadata key could not decrypt any public certificates.";
RecordGetDecryptedPublicCertificateResultMetric(
GetDecryptedPublicCertificateResult::kNoMatch);
std::move(callback).Run(std::nullopt);
}
// See documentation in declaration of `AttemptPrivateCertificateRefresh()`. The
// `bluetooth_adapter` is considered ready if it can provide its address. On
// BlueZ, it's when the `bluetooth_adapter` is non-null. On Floss, it's when
// the `bluetooth_adapter` is non-null and powered on.
bool IsAdapterReadyToRefreshPrivateCertificates(
scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) {
if (!bluetooth_adapter) {
return false;
}
if (floss::features::IsFlossEnabled()) {
return bluetooth_adapter->IsPowered();
}
return true;
}
} // namespace
// static
NearbyShareCertificateManagerImpl::Factory*
NearbyShareCertificateManagerImpl::Factory::test_factory_ = nullptr;
// static
std::unique_ptr<NearbyShareCertificateManager>
NearbyShareCertificateManagerImpl::Factory::Create(
NearbyShareLocalDeviceDataManager* local_device_data_manager,
NearbyShareContactManager* contact_manager,
NearbyShareProfileInfoProvider* profile_info_provider,
PrefService* pref_service,
leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
const base::FilePath& profile_path,
NearbyShareClientFactory* client_factory,
const base::Clock* clock) {
DCHECK(clock);
if (test_factory_) {
return test_factory_->CreateInstance(local_device_data_manager,
contact_manager, profile_info_provider,
pref_service, proto_database_provider,
profile_path, client_factory, clock);
}
return base::WrapUnique(new NearbyShareCertificateManagerImpl(
local_device_data_manager, contact_manager, profile_info_provider,
pref_service, proto_database_provider, profile_path, client_factory,
clock));
}
// static
void NearbyShareCertificateManagerImpl::Factory::SetFactoryForTesting(
Factory* test_factory) {
test_factory_ = test_factory;
}
NearbyShareCertificateManagerImpl::Factory::~Factory() = default;
NearbyShareCertificateManagerImpl::NearbyShareCertificateManagerImpl(
NearbyShareLocalDeviceDataManager* local_device_data_manager,
NearbyShareContactManager* contact_manager,
NearbyShareProfileInfoProvider* profile_info_provider,
PrefService* pref_service,
leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
const base::FilePath& profile_path,
NearbyShareClientFactory* client_factory,
const base::Clock* clock)
: local_device_data_manager_(local_device_data_manager),
contact_manager_(contact_manager),
profile_info_provider_(profile_info_provider),
pref_service_(pref_service),
client_factory_(client_factory),
clock_(clock),
certificate_storage_(NearbyShareCertificateStorageImpl::Factory::Create(
pref_service_,
proto_database_provider,
profile_path)),
private_certificate_expiration_scheduler_(
ash::nearby::NearbySchedulerFactory::CreateExpirationScheduler(
base::BindRepeating(&NearbyShareCertificateManagerImpl::
NextPrivateCertificateExpirationTime,
base::Unretained(this)),
/*retry_failures=*/true,
/*require_connectivity=*/false,
prefs::
kNearbySharingSchedulerPrivateCertificateExpirationPrefName,
pref_service_,
base::BindRepeating(&NearbyShareCertificateManagerImpl::
AttemptPrivateCertificateRefresh,
base::Unretained(this)),
Feature::NS,
clock_)),
public_certificate_expiration_scheduler_(
ash::nearby::NearbySchedulerFactory::CreateExpirationScheduler(
base::BindRepeating(&NearbyShareCertificateManagerImpl::
NextPublicCertificateExpirationTime,
base::Unretained(this)),
/*retry_failures=*/true,
/*require_connectivity=*/false,
prefs::kNearbySharingSchedulerPublicCertificateExpirationPrefName,
pref_service_,
base::BindRepeating(&NearbyShareCertificateManagerImpl::
OnPublicCertificateExpiration,
base::Unretained(this)),
Feature::NS,
clock_)),
upload_local_device_certificates_scheduler_(
ash::nearby::NearbySchedulerFactory::CreateOnDemandScheduler(
/*retry_failures=*/true,
/*require_connectivity=*/true,
prefs::
kNearbySharingSchedulerUploadLocalDeviceCertificatesPrefName,
pref_service_,
base::BindRepeating(&NearbyShareCertificateManagerImpl::
OnLocalDeviceCertificateUploadRequest,
base::Unretained(this)),
Feature::NS,
clock_)),
download_public_certificates_scheduler_(
ash::nearby::NearbySchedulerFactory::CreatePeriodicScheduler(
kNearbySharePublicCertificateDownloadPeriod,
/*retry_failures=*/true,
/*require_connectivity=*/true,
prefs::kNearbySharingSchedulerDownloadPublicCertificatesPrefName,
pref_service_,
base::BindRepeating(&NearbyShareCertificateManagerImpl::
OnDownloadPublicCertificatesRequest,
base::Unretained(this),
/*page_token=*/std::nullopt,
/*page_number=*/1,
/*certificate_count=*/0),
Feature::NS,
clock_)) {
local_device_data_manager_->AddObserver(this);
contact_manager_->AddObserver(this);
device::BluetoothAdapterFactory::Get()->GetAdapter(
base::BindOnce(&NearbyShareCertificateManagerImpl::OnGetAdapter,
weak_ptr_factory_.GetWeakPtr()));
}
NearbyShareCertificateManagerImpl::~NearbyShareCertificateManagerImpl() {
local_device_data_manager_->RemoveObserver(this);
contact_manager_->RemoveObserver(this);
}
std::vector<nearby::sharing::proto::PublicCertificate>
NearbyShareCertificateManagerImpl::GetPrivateCertificatesAsPublicCertificates(
nearby_share::mojom::Visibility visibility) {
NOTIMPLEMENTED();
return std::vector<nearby::sharing::proto::PublicCertificate>();
}
void NearbyShareCertificateManagerImpl::GetDecryptedPublicCertificate(
NearbyShareEncryptedMetadataKey encrypted_metadata_key,
CertDecryptedCallback callback) {
certificate_storage_->GetPublicCertificates(
base::BindOnce(&TryDecryptPublicCertificates,
std::move(encrypted_metadata_key), std::move(callback)));
}
void NearbyShareCertificateManagerImpl::DownloadPublicCertificates() {
download_public_certificates_scheduler_->MakeImmediateRequest();
}
void NearbyShareCertificateManagerImpl::OnStart() {
private_certificate_expiration_scheduler_->Start();
public_certificate_expiration_scheduler_->Start();
upload_local_device_certificates_scheduler_->Start();
download_public_certificates_scheduler_->Start();
}
void NearbyShareCertificateManagerImpl::OnStop() {
private_certificate_expiration_scheduler_->Stop();
public_certificate_expiration_scheduler_->Stop();
upload_local_device_certificates_scheduler_->Stop();
download_public_certificates_scheduler_->Stop();
}
std::optional<NearbySharePrivateCertificate>
NearbyShareCertificateManagerImpl::GetValidPrivateCertificate(
nearby_share::mojom::Visibility visibility) const {
std::optional<std::vector<NearbySharePrivateCertificate>> certs =
*certificate_storage_->GetPrivateCertificates();
for (auto& cert : *certs) {
if (IsNearbyShareCertificateWithinValidityPeriod(
clock_->Now(), cert.not_before(), cert.not_after(),
/*use_public_certificate_tolerance=*/false) &&
cert.visibility() == visibility) {
return std::move(cert);
}
}
CD_LOG(WARNING, Feature::NS)
<< __func__ << ": No valid private certificate found with visibility "
<< visibility;
return std::nullopt;
}
void NearbyShareCertificateManagerImpl::UpdatePrivateCertificateInStorage(
const NearbySharePrivateCertificate& private_certificate) {
certificate_storage_->UpdatePrivateCertificate(private_certificate);
}
void NearbyShareCertificateManagerImpl::OnContactsDownloaded(
const std::set<std::string>& allowed_contact_ids,
const std::vector<nearby::sharing::proto::ContactRecord>& contacts,
uint32_t num_unreachable_contacts_filtered_out) {}
void NearbyShareCertificateManagerImpl::OnContactsUploaded(
bool did_contacts_change_since_last_upload) {
if (!did_contacts_change_since_last_upload) {
return;
}
// If any of the uploaded contact data--the contact list or the allowlist--has
// changed since the previous successful upload, recreate certificates. We do
// not want to continue using the current certificates because they might have
// been shared with contacts no longer on the contact list or allowlist. NOTE:
// Ideally, we would only recreate all-contacts visibility certificates when
// contacts are removed from the contact list, and we would only recreate
// selected-contacts visibility certificates when contacts are removed from
// the allowlist, but our information is not that granular.
certificate_storage_->ClearPrivateCertificates();
private_certificate_expiration_scheduler_->MakeImmediateRequest();
}
void NearbyShareCertificateManagerImpl::OnLocalDeviceDataChanged(
bool did_device_name_change,
bool did_full_name_change,
bool did_icon_change) {
if (!did_device_name_change && !did_full_name_change && !did_icon_change) {
return;
}
// Recreate all private certificates to ensure up-to-date metadata.
certificate_storage_->ClearPrivateCertificates();
private_certificate_expiration_scheduler_->MakeImmediateRequest();
}
void NearbyShareCertificateManagerImpl::AdapterPoweredChanged(
device::BluetoothAdapter* adapter,
bool powered) {
// `NearbyShareCertificateManagerImpl` should only be added as an observer
// of `AdapterPoweredChanged()` if Floss is enabled.
CHECK(floss::features::IsFlossEnabled());
if (!powered) {
return;
}
if (is_pending_call_to_refresh_private_certificates_) {
CD_LOG(VERBOSE, Feature::NS) << __func__
<< ": Attempting to execute pending call to "
"refresh private certificates";
AttemptPrivateCertificateRefresh();
}
}
void NearbyShareCertificateManagerImpl::OnGetAdapter(
scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) {
CHECK(bluetooth_adapter);
adapter_ = bluetooth_adapter;
// Private certificate refresh depends on the `BluetoothAdapter`'s address,
// and on Floss, the address is unavailable if the `adapter_` is powered off.
// When on Floss, add observer for `AdapterPoweredChanged()` events, for
// when a refresh of private certificates is requested, and the `adapter_`
// is powered off. `NearbyShareCertificateManagerImpl` will cache the
// pending refresh request, and wait for the `adapter_` to be powered on again
// to execute the pending request. See documentation in declaration of
// `AttemptPrivateCertificateRefresh()`.
if (floss::features::IsFlossEnabled()) {
adapter_observation_.Observe(adapter_.get());
}
if (is_pending_call_to_refresh_private_certificates_) {
CD_LOG(VERBOSE, Feature::NS) << __func__
<< ": Attempting to execute pending call to "
"refresh private certificates";
AttemptPrivateCertificateRefresh();
}
}
std::optional<base::Time>
NearbyShareCertificateManagerImpl::NextPrivateCertificateExpirationTime() {
// We enforce that a fixed number--kNearbyShareNumPrivateCertificates for each
// visibility--of private certificates be present at all times. This might not
// be true the first time the user enables Nearby Share or after certificates
// are revoked. For simplicity, consider the case of missing certificates an
// "expired" state. Return the minimum time to immediately trigger the private
// certificate creation flow.
if (certificate_storage_->GetPrivateCertificates()->size() <
NumExpectedPrivateCertificates()) {
return base::Time::Min();
}
std::optional<base::Time> expiration_time =
certificate_storage_->NextPrivateCertificateExpirationTime();
DCHECK(expiration_time);
return *expiration_time;
}
void NearbyShareCertificateManagerImpl::AttemptPrivateCertificateRefresh() {
// The `adapter_` will not be ready to refresh the private certificates if
// it cannot provide its address. This can happen in the following scenarios:
// - [BlueZ]: The `adapter_` has not been retrieved yet in the asynchronous
// call to `OnGetAdapter()`. In this case, wait until the `adapter_`
// is retrieved to refresh the private certificates.
// - [Floss]: The `adapter_` has either not been retrieved yet (like
// the scenario above on BlueZ), or is not powered on. On Floss, the
// `adapter_` needs to be powered on to provide its address. In this case,
// wait until the `adapter_` is retrieved and/or powered on to refresh
// the private certificates.
if (!IsAdapterReadyToRefreshPrivateCertificates(adapter_)) {
CD_LOG(VERBOSE, Feature::NS)
<< __func__
<< ": Adapter is not ready to generate private certificates, storing "
"pending call to refresh certificates until Adapter is ready";
is_pending_call_to_refresh_private_certificates_ = true;
return;
}
CD_LOG(VERBOSE, Feature::NS)
<< __func__
<< ": Private certificate expiration detected; refreshing certificates.";
is_pending_call_to_refresh_private_certificates_ = false;
base::Time now = clock_->Now();
certificate_storage_->RemoveExpiredPrivateCertificates(now);
std::vector<NearbySharePrivateCertificate> certs =
*certificate_storage_->GetPrivateCertificates();
if (certs.size() == NumExpectedPrivateCertificates()) {
CD_LOG(VERBOSE, Feature::NS)
<< __func__ << ": All private certificates are still valid.";
private_certificate_expiration_scheduler_->HandleResult(/*success=*/true);
return;
}
// Determine how many private certificates of each visibility need to be
// created, and determine the validity period for the new certificates.
base::flat_map<nearby_share::mojom::Visibility, size_t> num_valid_certs;
base::flat_map<nearby_share::mojom::Visibility, base::Time> latest_not_after;
for (nearby_share::mojom::Visibility visibility : kVisibilities) {
num_valid_certs[visibility] = 0;
latest_not_after[visibility] = now;
}
for (const NearbySharePrivateCertificate& cert : certs) {
++num_valid_certs[cert.visibility()];
latest_not_after[cert.visibility()] =
std::max(latest_not_after[cert.visibility()], cert.not_after());
}
std::optional<nearby::sharing::proto::EncryptedMetadata> metadata =
BuildMetadata(local_device_data_manager_->GetDeviceName(),
local_device_data_manager_->GetFullName(),
local_device_data_manager_->GetIconUrl(),
profile_info_provider_->GetProfileUserName(),
adapter_.get());
if (!metadata) {
CD_LOG(WARNING, Feature::NS)
<< __func__
<< "Failed to create private certificates; cannot create metadata";
private_certificate_expiration_scheduler_->HandleResult(/*success=*/false);
return;
}
// Add new certificates if necessary. Each visibility should have
// kNearbyShareNumPrivateCertificates (unless overridden by a command-line
// switch).
size_t num_certificates = NumPrivateCertificates();
CD_LOG(INFO, Feature::NS)
<< __func__ << ": Creating "
<< num_certificates -
num_valid_certs[nearby_share::mojom::Visibility::kAllContacts]
<< " all-contacts visibility, "
<< num_certificates -
num_valid_certs[nearby_share::mojom::Visibility::kSelectedContacts]
<< " selected-contacts visibility, and "
<< num_certificates -
num_valid_certs[nearby_share::mojom::Visibility::kYourDevices]
<< " your-devices private certificates.";
for (nearby_share::mojom::Visibility visibility : kVisibilities) {
while (num_valid_certs[visibility] < num_certificates) {
certs.emplace_back(visibility,
/*not_before=*/latest_not_after[visibility],
*metadata);
++num_valid_certs[visibility];
latest_not_after[visibility] = certs.back().not_after();
}
}
certificate_storage_->ReplacePrivateCertificates(certs);
NotifyPrivateCertificatesChanged();
private_certificate_expiration_scheduler_->HandleResult(/*success=*/true);
upload_local_device_certificates_scheduler_->MakeImmediateRequest();
}
void NearbyShareCertificateManagerImpl::
OnLocalDeviceCertificateUploadRequest() {
std::vector<nearby::sharing::proto::PublicCertificate> public_certs;
std::vector<NearbySharePrivateCertificate> private_certs =
*certificate_storage_->GetPrivateCertificates();
for (const NearbySharePrivateCertificate& private_cert : private_certs) {
public_certs.push_back(*private_cert.ToPublicCertificate());
}
CD_LOG(VERBOSE, Feature::NS)
<< __func__ << ": Uploading local device certificates.";
local_device_data_manager_->UploadCertificates(
std::move(public_certs),
base::BindOnce(&NearbyShareCertificateManagerImpl::
OnLocalDeviceCertificateUploadFinished,
weak_ptr_factory_.GetWeakPtr()));
}
void NearbyShareCertificateManagerImpl::OnLocalDeviceCertificateUploadFinished(
bool success) {
CD_LOG(INFO, Feature::NS)
<< __func__ << ": Upload of local device certificates "
<< (success ? "succeeded" : "failed.");
upload_local_device_certificates_scheduler_->HandleResult(success);
}
std::optional<base::Time>
NearbyShareCertificateManagerImpl::NextPublicCertificateExpirationTime() {
std::optional<base::Time> next_expiration_time =
certificate_storage_->NextPublicCertificateExpirationTime();
// Supposedly there are no store public certificates.
if (!next_expiration_time) {
return std::nullopt;
}
// To account for clock skew between devices, we accept public certificates
// that are slightly past their validity period. This conforms with the
// GmsCore implementation.
return *next_expiration_time +
kNearbySharePublicCertificateValidityBoundOffsetTolerance;
}
void NearbyShareCertificateManagerImpl::OnPublicCertificateExpiration() {
certificate_storage_->RemoveExpiredPublicCertificates(
clock_->Now(), base::BindOnce(&NearbyShareCertificateManagerImpl::
OnExpiredPublicCertificatesRemoved,
base::Unretained(this)));
}
void NearbyShareCertificateManagerImpl::OnExpiredPublicCertificatesRemoved(
bool success) {
public_certificate_expiration_scheduler_->HandleResult(success);
}
void NearbyShareCertificateManagerImpl::OnDownloadPublicCertificatesRequest(
std::optional<std::string> page_token,
size_t page_number,
size_t certificate_count) {
DCHECK(!client_);
nearby::sharing::proto::ListPublicCertificatesRequest request;
request.set_parent(kDeviceIdPrefix + local_device_data_manager_->GetId());
if (page_token) {
request.set_page_token(*page_token);
}
// TODO(b/168701170): One Platform has a length restriction on request URLs.
// Adding all secret IDs to the request, and subsequently as query parameters,
// could result in hitting this limit. Add the secret IDs of all locally
// stored public certificates when this length restriction is circumvented.
CD_LOG(VERBOSE, Feature::NS)
<< __func__ << ": Downloading public certificates.";
timer_.Start(
FROM_HERE, kListPublicCertificatesTimeout,
base::BindOnce(
&NearbyShareCertificateManagerImpl::OnListPublicCertificatesTimeout,
base::Unretained(this), page_number, certificate_count));
client_ = client_factory_->CreateInstance();
client_->ListPublicCertificates(
request,
base::BindOnce(
&NearbyShareCertificateManagerImpl::OnListPublicCertificatesSuccess,
base::Unretained(this), page_number, certificate_count),
base::BindOnce(
&NearbyShareCertificateManagerImpl::OnListPublicCertificatesFailure,
base::Unretained(this), page_number, certificate_count));
}
void NearbyShareCertificateManagerImpl::OnListPublicCertificatesSuccess(
size_t page_number,
size_t certificate_count,
const nearby::sharing::proto::ListPublicCertificatesResponse& response) {
timer_.Stop();
std::vector<nearby::sharing::proto::PublicCertificate> certs(
response.public_certificates().begin(),
response.public_certificates().end());
std::optional<std::string> page_token =
response.next_page_token().empty()
? std::nullopt
: std::make_optional(response.next_page_token());
client_.reset();
CD_LOG(INFO, Feature::NS)
<< __func__ << ": " << certs.size() << " public certificates downloaded.";
certificate_storage_->AddPublicCertificates(
certs, base::BindOnce(&NearbyShareCertificateManagerImpl::
OnPublicCertificatesAddedToStorage,
base::Unretained(this), page_token, page_number,
certificate_count + certs.size()));
}
void NearbyShareCertificateManagerImpl::OnListPublicCertificatesFailure(
size_t page_number,
size_t certificate_count,
ash::nearby::NearbyHttpError error) {
timer_.Stop();
client_.reset();
FinishDownloadPublicCertificates(
/*success=*/false, ash::nearby::NearbyHttpErrorToResult(error),
page_number, certificate_count);
}
void NearbyShareCertificateManagerImpl::OnListPublicCertificatesTimeout(
size_t page_number,
size_t certificate_count) {
client_.reset();
FinishDownloadPublicCertificates(
/*success=*/false, ash::nearby::NearbyHttpResult::kTimeout, page_number,
certificate_count);
}
void NearbyShareCertificateManagerImpl::OnPublicCertificatesAddedToStorage(
std::optional<std::string> page_token,
size_t page_number,
size_t certificate_count,
bool success) {
if (success && page_token) {
OnDownloadPublicCertificatesRequest(page_token, page_number + 1,
certificate_count);
} else {
FinishDownloadPublicCertificates(success,
ash::nearby::NearbyHttpResult::kSuccess,
page_number, certificate_count);
}
}
void NearbyShareCertificateManagerImpl::FinishDownloadPublicCertificates(
bool success,
ash::nearby::NearbyHttpResult http_result,
size_t page_number,
size_t certificate_count) {
if (success) {
CD_LOG(VERBOSE, Feature::NS)
<< __func__
<< ": Public certificates successfully downloaded and stored.";
NotifyPublicCertificatesDownloaded();
// Recompute the expiration timer to account for new certificates.
public_certificate_expiration_scheduler_->Reschedule();
} else if (http_result == ash::nearby::NearbyHttpResult::kSuccess) {
CD_LOG(ERROR, Feature::NS)
<< __func__ << ": Public certificates not stored.";
} else {
CD_LOG(ERROR, Feature::NS)
<< __func__ << ": Public certificates download failed with HTTP error: "
<< http_result;
}
RecordDownloadPublicCertificatesResultMetrics(success, http_result,
page_number, certificate_count);
download_public_certificates_scheduler_->HandleResult(success);
}