#include "chromeos/ash/components/sync_wifi/wifi_configuration_bridge.h"
#include <algorithm>
#include <optional>
#include "ash/constants/ash_features.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "chromeos/ash/components/network/network_configuration_handler.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_metadata_store.h"
#include "chromeos/ash/components/sync_wifi/local_network_collector.h"
#include "chromeos/ash/components/sync_wifi/network_identifier.h"
#include "chromeos/ash/components/sync_wifi/network_type_conversions.h"
#include "chromeos/ash/components/sync_wifi/synced_network_metrics_logger.h"
#include "chromeos/ash/components/sync_wifi/synced_network_updater.h"
#include "chromeos/ash/components/timer_factory/timer_factory.h"
#include "components/device_event_log/device_event_log.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/sync/base/deletion_origin.h"
#include "components/sync/model/data_type_local_change_processor.h"
#include "components/sync/model/entity_change.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/model/metadata_change_list.h"
#include "components/sync/model/mutable_data_batch.h"
#include "components/sync/protocol/data_type_state.pb.h"
#include "components/sync/protocol/wifi_configuration_specifics.pb.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
namespace ash::sync_wifi {
namespace {
std::unique_ptr<syncer::EntityData> GenerateWifiEntityData(
const sync_pb::WifiConfigurationSpecifics& proto) {
auto entity_data = std::make_unique<syncer::EntityData>();
entity_data->specifics.mutable_wifi_configuration()->CopyFrom(proto);
entity_data->name = NetworkIdentifier::FromProto(proto).SerializeToString();
return entity_data;
}
constexpr base::TimeDelta kSyncAfterCreatedTimeout = base::Seconds(20);
}
WifiConfigurationBridge::WifiConfigurationBridge(
SyncedNetworkUpdater* synced_network_updater,
LocalNetworkCollector* local_network_collector,
NetworkConfigurationHandler* network_configuration_handler,
SyncedNetworkMetricsLogger* metrics_recorder,
ash::timer_factory::TimerFactory* timer_factory,
PrefService* pref_service,
std::unique_ptr<syncer::DataTypeLocalChangeProcessor> change_processor,
syncer::OnceDataTypeStoreFactory create_store_callback)
: DataTypeSyncBridge(std::move(change_processor)),
synced_network_updater_(synced_network_updater),
local_network_collector_(local_network_collector),
network_configuration_handler_(network_configuration_handler),
metrics_recorder_(metrics_recorder),
timer_factory_(timer_factory),
pref_service_(pref_service),
network_metadata_store_(nullptr) {
std::move(create_store_callback)
.Run(syncer::WIFI_CONFIGURATIONS,
base::BindOnce(&WifiConfigurationBridge::OnStoreCreated,
weak_ptr_factory_.GetWeakPtr()));
if (network_configuration_handler_) {
network_configuration_handler_->AddObserver(this);
}
}
WifiConfigurationBridge::~WifiConfigurationBridge() {
OnShuttingDown();
}
void WifiConfigurationBridge::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterBooleanPref(kIsFirstRun, true);
registry->RegisterBooleanPref(kHasFixedAutoconnect, false);
}
void WifiConfigurationBridge::OnShuttingDown() {
if (network_metadata_store_) {
network_metadata_store_->RemoveObserver(this);
network_metadata_store_ = nullptr;
}
if (network_configuration_handler_) {
network_configuration_handler_->RemoveObserver(this);
network_configuration_handler_ = nullptr;
}
}
std::unique_ptr<syncer::MetadataChangeList>
WifiConfigurationBridge::CreateMetadataChangeList() {
return syncer::DataTypeStore::WriteBatch::CreateMetadataChangeList();
}
std::optional<syncer::ModelError> WifiConfigurationBridge::MergeFullSyncData(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList change_list) {
DCHECK(entries_.empty());
DCHECK(local_network_collector_);
local_network_collector_->GetAllSyncableNetworks(
base::BindOnce(&WifiConfigurationBridge::OnGetAllSyncableNetworksResult,
weak_ptr_factory_.GetWeakPtr(),
std::move(metadata_change_list), std::move(change_list)));
return std::nullopt;
}
void WifiConfigurationBridge::OnGetAllSyncableNetworksResult(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList change_list,
std::vector<sync_pb::WifiConfigurationSpecifics> local_network_list) {
NET_LOG(EVENT) << "Merging " << local_network_list.size() << " local and "
<< change_list.size() << " synced networks.";
base::flat_map<NetworkIdentifier, sync_pb::WifiConfigurationSpecifics>
sync_networks;
base::flat_map<NetworkIdentifier, sync_pb::WifiConfigurationSpecifics>
local_networks;
for (std::unique_ptr<syncer::EntityChange>& change : change_list) {
if (change->type() == syncer::EntityChange::ACTION_DELETE) {
continue;
}
const sync_pb::WifiConfigurationSpecifics& proto =
change->data().specifics.wifi_configuration();
NetworkIdentifier id = NetworkIdentifier::FromProto(proto);
if (auto it = sync_networks.find(id);
it != sync_networks.end() && it->second.last_connected_timestamp() >
proto.last_connected_timestamp()) {
continue;
}
sync_networks[id] = proto;
}
for (sync_pb::WifiConfigurationSpecifics& proto : local_network_list) {
NetworkIdentifier id = NetworkIdentifier::FromProto(proto);
if (auto it = sync_networks.find(id);
it != sync_networks.end() && it->second.last_connected_timestamp() >
proto.last_connected_timestamp()) {
continue;
}
local_networks[id] = proto;
std::unique_ptr<syncer::EntityData> entity_data =
GenerateWifiEntityData(proto);
std::string storage_key = GetStorageKey(*entity_data);
change_processor()->Put(storage_key, std::move(entity_data),
metadata_change_list.get());
entries_[storage_key] = proto;
}
std::unique_ptr<syncer::DataTypeStore::WriteBatch> batch =
store_->CreateWriteBatch();
for (const auto& [id, proto] : sync_networks) {
if (auto it = local_networks.find(id);
it != local_networks.end() && it->second.last_connected_timestamp() >
proto.last_connected_timestamp()) {
continue;
}
synced_network_updater_->AddOrUpdateNetwork(proto);
batch->WriteData(id.SerializeToString(), proto.SerializeAsString());
entries_[id.SerializeToString()] = proto;
}
batch->TakeMetadataChangesFrom(std::move(metadata_change_list));
Commit(std::move(batch));
RecordNetworkMetrics();
}
std::optional<syncer::ModelError>
WifiConfigurationBridge::ApplyIncrementalSyncChanges(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_changes) {
std::unique_ptr<syncer::DataTypeStore::WriteBatch> batch =
store_->CreateWriteBatch();
NET_LOG(EVENT) << "Applying " << entity_changes.size()
<< " pending changes.";
for (std::unique_ptr<syncer::EntityChange>& change : entity_changes) {
if (change->type() == syncer::EntityChange::ACTION_DELETE) {
auto it = entries_.find(change->storage_key());
if (it != entries_.end()) {
entries_.erase(it);
batch->DeleteData(change->storage_key());
if (!base::FeatureList::IsEnabled(features::kWifiSyncApplyDeletes)) {
NET_LOG(EVENT) << "Ignoring delete request from sync server.";
continue;
}
synced_network_updater_->RemoveNetwork(
NetworkIdentifier::DeserializeFromString(change->storage_key()));
} else {
NET_LOG(EVENT) << "Received delete request for network which is not "
"tracked by sync.";
}
continue;
}
auto& specifics = change->data().specifics.wifi_configuration();
synced_network_updater_->AddOrUpdateNetwork(specifics);
batch->WriteData(change->storage_key(), specifics.SerializeAsString());
entries_[change->storage_key()] = std::move(specifics);
}
batch->TakeMetadataChangesFrom(std::move(metadata_change_list));
Commit(std::move(batch));
RecordNetworkMetrics();
return std::nullopt;
}
std::unique_ptr<syncer::DataBatch> WifiConfigurationBridge::GetDataForCommit(
StorageKeyList storage_keys) {
auto batch = std::make_unique<syncer::MutableDataBatch>();
for (const std::string& id : storage_keys) {
auto it = entries_.find(id);
if (it == entries_.end()) {
continue;
}
batch->Put(id, GenerateWifiEntityData(it->second));
}
return batch;
}
std::unique_ptr<syncer::DataBatch>
WifiConfigurationBridge::GetAllDataForDebugging() {
auto batch = std::make_unique<syncer::MutableDataBatch>();
for (const auto& [storage_key, specifics] : entries_) {
batch->Put(storage_key, GenerateWifiEntityData(specifics));
}
return batch;
}
std::string WifiConfigurationBridge::GetClientTag(
const syncer::EntityData& entity_data) const {
return GetStorageKey(entity_data);
}
std::string WifiConfigurationBridge::GetStorageKey(
const syncer::EntityData& entity_data) const {
return NetworkIdentifier::FromProto(
entity_data.specifics.wifi_configuration())
.SerializeToString();
}
bool WifiConfigurationBridge::IsEntityDataValid(
const syncer::EntityData& entity_data) const {
const sync_pb::WifiConfigurationSpecifics& specifics =
entity_data.specifics.wifi_configuration();
if (specifics.hex_ssid().empty()) {
return false;
}
switch (specifics.security_type()) {
case sync_pb::WifiConfigurationSpecifics::SECURITY_TYPE_UNSPECIFIED:
case sync_pb::WifiConfigurationSpecifics::SECURITY_TYPE_NONE:
return false;
case sync_pb::WifiConfigurationSpecifics::SECURITY_TYPE_PSK:
case sync_pb::WifiConfigurationSpecifics::SECURITY_TYPE_WEP:
return true;
}
NOTREACHED();
}
void WifiConfigurationBridge::ApplyDisableSyncChanges(
std::unique_ptr<syncer::MetadataChangeList> delete_metadata_change_list) {
entries_.clear();
pending_deletes_.clear();
network_guid_to_timer_map_.clear();
networks_to_sync_when_ready_.clear();
if (store_) {
store_->DeleteAllDataAndMetadata(base::DoNothing());
}
weak_ptr_factory_.InvalidateWeakPtrs();
}
void WifiConfigurationBridge::RecordNetworkMetrics() {
metrics_recorder_->RecordTotalCount(entries_.size());
if (entries_.empty()) {
local_network_collector_->RecordZeroNetworksEligibleForSync();
}
}
void WifiConfigurationBridge::OnStoreCreated(
const std::optional<syncer::ModelError>& error,
std::unique_ptr<syncer::DataTypeStore> store) {
if (error) {
change_processor()->ReportError(*error);
return;
}
store_ = std::move(store);
store_->ReadAllData(base::BindOnce(&WifiConfigurationBridge::OnReadAllData,
weak_ptr_factory_.GetWeakPtr()));
}
void WifiConfigurationBridge::OnReadAllData(
const std::optional<syncer::ModelError>& error,
std::unique_ptr<syncer::DataTypeStore::RecordList> records) {
if (error) {
change_processor()->ReportError(*error);
return;
}
for (syncer::DataTypeStore::Record& record : *records) {
sync_pb::WifiConfigurationSpecifics data;
if (record.id.empty() || !data.ParseFromString(record.value)) {
NET_LOG(EVENT) << "Unable to parse proto for entry with key: "
<< record.id;
continue;
}
entries_[record.id] = std::move(data);
}
store_->ReadAllMetadata(
base::BindOnce(&WifiConfigurationBridge::OnReadAllMetadata,
weak_ptr_factory_.GetWeakPtr()));
if (!pref_service_->GetBoolean(kHasFixedAutoconnect)) {
local_network_collector_->ExecuteAfterNetworksLoaded(
base::BindOnce(&WifiConfigurationBridge::FixAutoconnect,
weak_ptr_factory_.GetWeakPtr()));
}
if (pref_service_->GetBoolean(kIsFirstRun)) {
pref_service_->SetBoolean(kIsFirstRun, false);
if (entries_.empty()) {
return;
}
}
RecordNetworkMetrics();
}
void WifiConfigurationBridge::FixAutoconnect() {
if (!pref_service_->GetBoolean(kHasFixedAutoconnect)) {
std::vector<sync_pb::WifiConfigurationSpecifics> protos;
protos.reserve(entries_.size());
for (const auto& [storage_key, specifics] : entries_) {
protos.push_back(specifics);
}
local_network_collector_->FixAutoconnect(
std::move(protos),
base::BindOnce(&WifiConfigurationBridge::OnFixAutoconnectComplete,
weak_ptr_factory_.GetWeakPtr()));
}
}
void WifiConfigurationBridge::OnFixAutoconnectComplete() {
pref_service_->SetBoolean(kHasFixedAutoconnect, true);
}
void WifiConfigurationBridge::OnReadAllMetadata(
const std::optional<syncer::ModelError>& error,
std::unique_ptr<syncer::MetadataBatch> metadata_batch) {
TRACE_EVENT0("ui", "WifiConfigurationBridge::OnReadAllMetadata");
if (error) {
change_processor()->ReportError(*error);
return;
}
change_processor()->ModelReadyToSync(std::move(metadata_batch));
base::flat_map<std::string,
std::optional<sync_pb::WifiConfigurationSpecifics>>
updates = networks_to_sync_when_ready_;
for (auto const& [storage_key, specifics] : updates) {
if (specifics) {
SaveNetworkToSync(specifics);
continue;
}
RemoveNetworkFromSync(storage_key);
}
networks_to_sync_when_ready_.clear();
}
void WifiConfigurationBridge::OnCommit(
const std::optional<syncer::ModelError>& error) {
if (error)
change_processor()->ReportError(*error);
}
void WifiConfigurationBridge::Commit(
std::unique_ptr<syncer::DataTypeStore::WriteBatch> batch) {
store_->CommitWriteBatch(std::move(batch),
base::BindOnce(&WifiConfigurationBridge::OnCommit,
weak_ptr_factory_.GetWeakPtr()));
}
std::vector<NetworkIdentifier> WifiConfigurationBridge::GetAllIdsForTesting() {
std::vector<NetworkIdentifier> ids;
for (const auto& [storage_key, specifics] : entries_)
ids.push_back(NetworkIdentifier::FromProto(specifics));
return ids;
}
void WifiConfigurationBridge::OnFirstConnectionToNetwork(
const std::string& guid) {
if (auto it = network_guid_to_timer_map_.find(guid);
it != network_guid_to_timer_map_.end()) {
network_guid_to_timer_map_.erase(it);
}
if (network_metadata_store_->GetIsConfiguredBySync(guid)) {
NET_LOG(EVENT) << "Not uploading network on first connect: "
<< NetworkGuidId(guid) << " was added by sync.";
return;
}
NET_LOG(EVENT) << "Syncing network after first connect: "
<< NetworkGuidId(guid);
local_network_collector_->GetSyncableNetwork(
guid, base::BindOnce(&WifiConfigurationBridge::SaveNetworkToSync,
weak_ptr_factory_.GetWeakPtr()));
}
void WifiConfigurationBridge::OnNetworkUpdate(
const std::string& guid,
const base::Value::Dict* set_properties) {
if (!set_properties)
return;
if (synced_network_updater_->IsUpdateInProgress(guid) ||
network_metadata_store_->GetIsConfiguredBySync(guid)) {
NET_LOG(EVENT) << "Not uploading change to " << NetworkGuidId(guid)
<< ", modified network was configured "
"by sync.";
return;
}
if (!set_properties->contains(shill::kAutoConnectProperty) &&
!set_properties->contains(shill::kPriorityProperty) &&
!set_properties->contains(shill::kProxyConfigProperty) &&
!set_properties->contains(shill::kMeteredProperty) &&
!set_properties->FindByDottedPath(
base::StringPrintf("%s.%s", shill::kStaticIPConfigProperty,
shill::kNameServersProperty))) {
NET_LOG(EVENT) << "Not uploading change to " << NetworkGuidId(guid)
<< ", modified network field(s) are not synced.";
return;
}
NET_LOG(EVENT) << "Updating sync with changes to " << NetworkGuidId(guid);
local_network_collector_->GetSyncableNetwork(
guid, base::BindOnce(&WifiConfigurationBridge::SaveNetworkToSync,
weak_ptr_factory_.GetWeakPtr()));
}
void WifiConfigurationBridge::SaveNetworkToSync(
std::optional<sync_pb::WifiConfigurationSpecifics> proto) {
if (!proto) {
return;
}
if (!store_ || !change_processor()->IsTrackingMetadata()) {
networks_to_sync_when_ready_.insert_or_assign(
NetworkIdentifier::FromProto(*proto).SerializeToString(), proto);
return;
}
std::unique_ptr<syncer::EntityData> entity_data =
GenerateWifiEntityData(*proto);
auto id = NetworkIdentifier::FromProto(*proto);
std::string storage_key = GetStorageKey(*entity_data);
std::unique_ptr<syncer::DataTypeStore::WriteBatch> batch =
store_->CreateWriteBatch();
batch->WriteData(storage_key, proto->SerializeAsString());
change_processor()->Put(storage_key, std::move(entity_data),
batch->GetMetadataChangeList());
entries_[storage_key] = *proto;
Commit(std::move(batch));
NET_LOG(EVENT) << "Saved network "
<< NetworkId(NetworkStateFromNetworkIdentifier(id))
<< " to sync.";
RecordNetworkMetrics();
}
void WifiConfigurationBridge::OnNetworkCreated(const std::string& guid) {
std::unique_ptr<base::OneShotTimer>& timer =
network_guid_to_timer_map_
.insert_or_assign(guid, timer_factory_->CreateOneShotTimer())
.first->second;
timer->Start(
FROM_HERE, kSyncAfterCreatedTimeout,
base::BindOnce(&WifiConfigurationBridge::OnNetworkConfiguredDelayComplete,
weak_ptr_factory_.GetWeakPtr(), guid));
}
void WifiConfigurationBridge::OnNetworkConfiguredDelayComplete(
const std::string& network_guid) {
if (auto it = network_guid_to_timer_map_.find(network_guid);
it != network_guid_to_timer_map_.end()) {
network_guid_to_timer_map_.erase(it);
}
if (network_metadata_store_->GetIsConfiguredBySync(network_guid)) {
NET_LOG(EVENT) << "Not uploading newly configured network "
<< NetworkGuidId(network_guid) << ", it was added by sync.";
return;
}
NET_LOG(EVENT) << "Attempting to sync new network after delay.";
local_network_collector_->GetSyncableNetwork(
network_guid, base::BindOnce(&WifiConfigurationBridge::SaveNetworkToSync,
weak_ptr_factory_.GetWeakPtr()));
}
void WifiConfigurationBridge::OnBeforeConfigurationRemoved(
const std::string& service_path,
const std::string& guid) {
std::optional<NetworkIdentifier> id =
local_network_collector_->GetNetworkIdentifierFromGuid(guid);
if (!id) {
return;
}
NET_LOG(EVENT) << "Storing metadata for " << NetworkPathId(service_path)
<< " in preparation for removal.";
pending_deletes_[guid] = id->SerializeToString();
}
void WifiConfigurationBridge::OnConfigurationRemoved(
const std::string& service_path,
const std::string& network_guid) {
auto it = pending_deletes_.find(network_guid);
if (it == pending_deletes_.end()) {
NET_LOG(EVENT) << "Configuration " << network_guid
<< " removed with no matching saved metadata.";
return;
}
const std::string& storage_key = it->second;
if (!store_ || !change_processor()->IsTrackingMetadata()) {
networks_to_sync_when_ready_.insert_or_assign(storage_key, std::nullopt);
return;
}
RemoveNetworkFromSync(storage_key);
}
void WifiConfigurationBridge::RemoveNetworkFromSync(
const std::string& storage_key) {
auto entries_it = entries_.find(storage_key);
if (entries_it == entries_.end()) {
return;
}
std::unique_ptr<syncer::DataTypeStore::WriteBatch> batch =
store_->CreateWriteBatch();
batch->DeleteData(storage_key);
change_processor()->Delete(storage_key, syncer::DeletionOrigin::Unspecified(),
batch->GetMetadataChangeList());
entries_.erase(entries_it);
Commit(std::move(batch));
NET_LOG(EVENT) << "Removed network from sync.";
}
void WifiConfigurationBridge::SetNetworkMetadataStore(
base::WeakPtr<NetworkMetadataStore> network_metadata_store) {
if (network_metadata_store_) {
network_metadata_store->RemoveObserver(this);
}
network_metadata_store_ = network_metadata_store;
network_metadata_store->AddObserver(this);
}
}