blob: bb8fe002f7552d39b3865ff6b4527371ad05ee73 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync_tab_context/tab_context_container_sync_bridge.h"
#include <utility>
#include "base/containers/map_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/uuid.h"
#include "components/sync/model/crypto/agile_symmetric_key_set.h"
#include "components/sync/model/data_type_store.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/model/metadata_change_list.h"
#include "components/sync/model/model_error.h"
#include "components/sync/model/mutable_data_batch.h"
#include "components/sync/protocol/entity_data.h"
#include "components/sync/protocol/entity_specifics.pb.h"
#include "components/sync_tab_context/container_id.h"
namespace sync_tab_context {
namespace {
sync_pb::EncryptedTabContextContainerSpecifics ToSpecifics(
const ContainerId& container_id,
const syncer::AgileSymmetricKeySet& key_set) {
sync_pb::EncryptedTabContextContainerSpecifics specifics;
specifics.set_uuid(container_id.value().AsLowercaseString());
*specifics.mutable_encryption_key() = key_set.ToProto();
return specifics;
}
bool AreSpecificsValid(
const sync_pb::EncryptedTabContextContainerSpecifics& specifics) {
if (!base::Uuid::ParseCaseInsensitive(specifics.uuid()).is_valid()) {
return false;
}
std::unique_ptr<syncer::AgileSymmetricKeySet> encryption_key =
syncer::AgileSymmetricKeySet::FromProto(specifics.encryption_key());
return encryption_key && encryption_key->size() != 0;
}
} // namespace
TabContextContainerSyncBridge::TabContextContainerSyncBridge(
syncer::OnceDataTypeStoreFactory store_factory,
std::unique_ptr<syncer::DataTypeLocalChangeProcessor> change_processor)
: syncer::DataTypeSyncBridge(std::move(change_processor)) {
std::move(store_factory)
.Run(syncer::ENCRYPTED_TAB_CONTEXT_CONTAINER,
base::BindOnce(&TabContextContainerSyncBridge::OnStoreCreated,
weak_ptr_factory_.GetWeakPtr()));
}
TabContextContainerSyncBridge::~TabContextContainerSyncBridge() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
std::optional<ContainerId> TabContextContainerSyncBridge::CreateContainer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!change_processor()->IsTrackingMetadata()) {
return std::nullopt;
}
CHECK(store_);
const ContainerId container_id(base::Uuid::GenerateRandomV4());
std::unique_ptr<syncer::AgileSymmetricKeySet> key_set =
syncer::AgileSymmetricKeySet::CreateEmpty();
key_set->RotatePrimaryToNewlyGeneratedRandomKey();
sync_pb::EncryptedTabContextContainerSpecifics specifics =
ToSpecifics(container_id, *key_set);
const std::string storage_key = container_id.value().AsLowercaseString();
std::unique_ptr<syncer::DataTypeStore::WriteBatch> write_batch =
store_->CreateWriteBatch();
write_batch->WriteData(storage_key, specifics.SerializeAsString());
auto entity_data = std::make_unique<syncer::EntityData>();
*entity_data->specifics.mutable_encrypted_tab_context_container() = specifics;
entity_data->name = storage_key;
change_processor()->Put(storage_key, std::move(entity_data),
write_batch->GetMetadataChangeList());
store_->CommitWriteBatch(
std::move(write_batch),
base::BindOnce(&TabContextContainerSyncBridge::OnStoreCommit,
weak_ptr_factory_.GetWeakPtr()));
entries_[container_id] = std::move(key_set);
return container_id;
}
const syncer::AgileSymmetricKeySet*
TabContextContainerSyncBridge::GetEncryptionKeyForContainer(
const ContainerId& container_id) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return base::FindPtrOrNull(entries_, container_id);
}
bool TabContextContainerSyncBridge::IsTrackingMetadataForTesting() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return change_processor()->IsTrackingMetadata();
}
std::unique_ptr<syncer::MetadataChangeList>
TabContextContainerSyncBridge::CreateMetadataChangeList() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return syncer::DataTypeStore::WriteBatch::CreateMetadataChangeList();
}
std::optional<syncer::ModelError>
TabContextContainerSyncBridge::MergeFullSyncData(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_changes) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return ApplyIncrementalSyncChanges(std::move(metadata_change_list),
std::move(entity_changes));
}
std::optional<syncer::ModelError>
TabContextContainerSyncBridge::ApplyIncrementalSyncChanges(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_changes) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(store_);
std::unique_ptr<syncer::DataTypeStore::WriteBatch> write_batch =
store_->CreateWriteBatch(std::move(metadata_change_list));
for (const std::unique_ptr<syncer::EntityChange>& change : entity_changes) {
switch (change->type()) {
case syncer::EntityChange::ACTION_ADD:
case syncer::EntityChange::ACTION_UPDATE: {
const sync_pb::EncryptedTabContextContainerSpecifics& specifics =
change->data().specifics.encrypted_tab_context_container();
CHECK(AreSpecificsValid(specifics));
CHECK_EQ(specifics.uuid(), change->storage_key());
const ContainerId container_id(
base::Uuid::ParseCaseInsensitive(specifics.uuid()));
std::unique_ptr<syncer::AgileSymmetricKeySet> encryption_key =
syncer::AgileSymmetricKeySet::FromProto(specifics.encryption_key());
write_batch->WriteData(specifics.uuid(), specifics.SerializeAsString());
entries_[container_id] = std::move(encryption_key);
break;
}
case syncer::EntityChange::ACTION_DELETE: {
const ContainerId container_id(
base::Uuid::ParseCaseInsensitive(change->storage_key()));
write_batch->DeleteData(change->storage_key());
entries_.erase(container_id);
break;
}
}
}
store_->CommitWriteBatch(
std::move(write_batch),
base::BindOnce(&TabContextContainerSyncBridge::OnStoreCommit,
weak_ptr_factory_.GetWeakPtr()));
return std::nullopt;
}
void TabContextContainerSyncBridge::ApplyDisableSyncChanges(
std::unique_ptr<syncer::MetadataChangeList> delete_metadata_change_list) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(store_);
entries_.clear();
store_->DeleteAllDataAndMetadata(std::move(delete_metadata_change_list),
base::DoNothing());
}
std::unique_ptr<syncer::DataBatch>
TabContextContainerSyncBridge::GetDataForCommit(StorageKeyList storage_keys) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto batch = std::make_unique<syncer::MutableDataBatch>();
for (const std::string& key : storage_keys) {
const ContainerId container_id(base::Uuid::ParseCaseInsensitive(key));
if (const syncer::AgileSymmetricKeySet* key_set =
base::FindPtrOrNull(entries_, container_id)) {
auto entity_data = std::make_unique<syncer::EntityData>();
*entity_data->specifics.mutable_encrypted_tab_context_container() =
ToSpecifics(container_id, *key_set);
entity_data->name = key;
batch->Put(key, std::move(entity_data));
}
}
return batch;
}
std::unique_ptr<syncer::DataBatch>
TabContextContainerSyncBridge::GetAllDataForDebugging() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto batch = std::make_unique<syncer::MutableDataBatch>();
for (const auto& [container_id, key_set] : entries_) {
auto entity_data = std::make_unique<syncer::EntityData>();
*entity_data->specifics.mutable_encrypted_tab_context_container() =
ToSpecifics(container_id, *key_set);
const std::string key = container_id.value().AsLowercaseString();
entity_data->name = key;
batch->Put(key, std::move(entity_data));
}
return batch;
}
std::string TabContextContainerSyncBridge::GetClientTag(
const syncer::EntityData& entity_data) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return entity_data.specifics.encrypted_tab_context_container().uuid();
}
std::string TabContextContainerSyncBridge::GetStorageKey(
const syncer::EntityData& entity_data) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return entity_data.specifics.encrypted_tab_context_container().uuid();
}
sync_pb::EntitySpecifics
TabContextContainerSyncBridge::TrimAllSupportedFieldsFromRemoteSpecifics(
const sync_pb::EntitySpecifics& entity_specifics) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return sync_pb::EntitySpecifics();
}
bool TabContextContainerSyncBridge::IsEntityDataValid(
const syncer::EntityData& entity_data) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return AreSpecificsValid(
entity_data.specifics.encrypted_tab_context_container());
}
void TabContextContainerSyncBridge::OnStoreCreated(
const std::optional<syncer::ModelError>& error,
std::unique_ptr<syncer::DataTypeStore> store) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (error) {
change_processor()->ReportError(*error);
return;
}
store_ = std::move(store);
store_->ReadAllDataAndMetadata(
base::BindOnce(&TabContextContainerSyncBridge::OnReadAllDataAndMetadata,
weak_ptr_factory_.GetWeakPtr()));
}
void TabContextContainerSyncBridge::OnReadAllDataAndMetadata(
const std::optional<syncer::ModelError>& error,
std::unique_ptr<syncer::DataTypeStore::RecordList> data_records,
std::unique_ptr<syncer::MetadataBatch> metadata_batch) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (error) {
change_processor()->ReportError(*error);
return;
}
for (const syncer::DataTypeStore::Record& record : *data_records) {
sync_pb::EncryptedTabContextContainerSpecifics specifics;
if (!specifics.ParseFromString(record.value) ||
!AreSpecificsValid(specifics)) {
change_processor()->ReportError(
{FROM_HERE, syncer::ModelError::Type::
kTabContextContainerFailedToDeserializeSpecifics});
return;
}
const ContainerId container_id(
base::Uuid::ParseCaseInsensitive(specifics.uuid()));
std::unique_ptr<syncer::AgileSymmetricKeySet> encryption_key =
syncer::AgileSymmetricKeySet::FromProto(specifics.encryption_key());
entries_[container_id] = std::move(encryption_key);
}
change_processor()->ModelReadyToSync(std::move(metadata_batch));
}
void TabContextContainerSyncBridge::OnStoreCommit(
const std::optional<syncer::ModelError>& error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (error) {
change_processor()->ReportError(*error);
}
}
} // namespace sync_tab_context