blob: 7e433c335419a6913b889e9bfdad38c75d53d15d [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_sync_service_impl.h"
#include <utility>
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/notimplemented.h"
#include "base/notreached.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "components/sync/model/client_tag_based_data_type_processor.h"
#include "components/sync/model/crypto/agile_symmetric_key_set.h"
#include "components/sync_tab_context/ephemeral_key_fetcher.h"
#include "components/sync_tab_context/proto/tab_context_container_access_token.pb.h"
#include "components/sync_tab_context/tab_context_container_sync_bridge.h"
#include "components/sync_tab_context/tab_context_item_sync_bridge.h"
namespace sync_tab_context {
namespace {
google::protobuf::Timestamp TimeToGoogleTimestampProto(base::Time time) {
google::protobuf::Timestamp timestamp;
const int64_t seconds = (time - base::Time::UnixEpoch()).InSeconds();
const int64_t nanos =
((time - base::Time::UnixEpoch()) - base::Seconds(seconds))
.InNanoseconds();
timestamp.set_seconds(seconds);
timestamp.set_nanos(static_cast<int32_t>(nanos));
return timestamp;
}
// TODO(crbug.com/535450467): Consider improving type safety instead of dealing
// with strings.
std::string BuildContainerAccessToken(
const syncer::AgileSymmetricKeySet& container_key_set,
EphemeralKeyFetcher::Result result) {
CHECK(result.ephemeral_key);
const std::string container_key_bytes =
container_key_set.ToProto().SerializeAsString();
const std::optional<sync_pb::EncryptedData> encrypted_container_key =
result.ephemeral_key->Encrypt(base::as_byte_span(container_key_bytes));
if (!encrypted_container_key) {
return std::string();
}
TabContextContainerAccessToken token_proto;
token_proto.set_name(std::move(result.name));
token_proto.set_encrypted_container_key(
encrypted_container_key->SerializeAsString());
if (!result.expire_time.is_null()) {
*token_proto.mutable_expire_time() =
TimeToGoogleTimestampProto(result.expire_time);
}
return token_proto.SerializeAsString();
}
} // namespace
TabContextSyncServiceImpl::TabContextSyncServiceImpl(
syncer::OnceDataTypeStoreFactory store_factory,
std::unique_ptr<EphemeralKeyFetcher> ephemeral_key_fetcher,
base::RepeatingClosure dump_stack)
: container_bridge_(std::make_unique<TabContextContainerSyncBridge>(
std::move(store_factory),
std::make_unique<syncer::ClientTagBasedDataTypeProcessor>(
syncer::ENCRYPTED_TAB_CONTEXT_CONTAINER,
dump_stack))),
item_bridge_(std::make_unique<TabContextItemSyncBridge>(
std::make_unique<syncer::ClientTagBasedDataTypeProcessor>(
syncer::ENCRYPTED_TAB_CONTEXT_ITEM,
dump_stack))),
ephemeral_key_fetcher_(std::move(ephemeral_key_fetcher)) {
CHECK(ephemeral_key_fetcher_);
}
TabContextSyncServiceImpl::~TabContextSyncServiceImpl() = default;
std::optional<ContainerId> TabContextSyncServiceImpl::CreateContainer() {
return container_bridge_->CreateContainer();
}
bool TabContextSyncServiceImpl::UploadPageContext(
const ContainerId& container_id,
const std::string& entry_id,
std::string page_context) {
const syncer::AgileSymmetricKeySet* key_set =
container_bridge_->GetEncryptionKeyForContainer(container_id);
if (!key_set) {
return false;
}
// TODO(crbug.com/527991322): Consider compression before encryption, capping
// the size and moving expensive operations to a backend sequence.
std::optional<sync_pb::EncryptedData> encrypted_data =
key_set->Encrypt(base::as_byte_span(page_context));
if (!encrypted_data) {
return false;
}
return item_bridge_->UploadItem(container_id, entry_id,
std::move(*encrypted_data));
}
void TabContextSyncServiceImpl::GetContainerAccessToken(
const ContainerId& container_id,
base::OnceCallback<void(std::optional<std::string>)> cb) {
if (!container_bridge_->GetEncryptionKeyForContainer(container_id)) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(cb), std::nullopt));
return;
}
ephemeral_key_fetcher_->FetchEphemeralKey(base::BindOnce(
&TabContextSyncServiceImpl::OnEphemeralKeyFetched,
weak_ptr_factory_.GetWeakPtr(), container_id, std::move(cb)));
}
void TabContextSyncServiceImpl::OnEphemeralKeyFetched(
const ContainerId& container_id,
base::OnceCallback<void(std::optional<std::string>)> cb,
std::optional<EphemeralKeyFetcher::Result> result) {
if (!result || !result->ephemeral_key) {
std::move(cb).Run(std::nullopt);
return;
}
const syncer::AgileSymmetricKeySet* container_key_set =
container_bridge_->GetEncryptionKeyForContainer(container_id);
if (!container_key_set) {
// The container could have been removed during key fetching.
std::move(cb).Run(std::nullopt);
return;
}
std::move(cb).Run(
BuildContainerAccessToken(*container_key_set, std::move(*result)));
}
base::WeakPtr<syncer::DataTypeControllerDelegate>
TabContextSyncServiceImpl::GetSyncControllerDelegateForContainer() {
return container_bridge_->change_processor()->GetControllerDelegate();
}
base::WeakPtr<syncer::DataTypeControllerDelegate>
TabContextSyncServiceImpl::GetSyncControllerDelegateForItem() {
return item_bridge_->change_processor()->GetControllerDelegate();
}
bool TabContextSyncServiceImpl::IsActiveForTesting() const {
return container_bridge_ &&
container_bridge_->IsTrackingMetadataForTesting(); // IN-TEST
}
} // namespace sync_tab_context