chromium/ash/quick_pair/repository/fast_pair/pending_write_store.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/quick_pair/repository/fast_pair/pending_write_store.h"

#include "ash/quick_pair/common/quick_pair_browser_delegate.h"
#include "base/base64.h"
#include "base/strings/string_number_conversions.h"
#include "base/types/fixed_array.h"
#include "components/cross_device/logging/logging.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"

namespace {

const char kFastPairPendingWritesPref[] = "fast_pair.pending_writes";
const char kFastPairPendingDeletesPref[] = "fast_pair.pending_deletes";

}  // namespace

namespace ash {
namespace quick_pair {

PendingWriteStore::PendingWriteStore() = default;
PendingWriteStore::~PendingWriteStore() = default;

void PendingWriteStore::RegisterProfilePrefs(PrefRegistrySimple* registry) {
  registry->RegisterDictionaryPref(kFastPairPendingWritesPref);
  registry->RegisterDictionaryPref(kFastPairPendingDeletesPref);
}

PendingWriteStore::PendingWrite::PendingWrite(
    const std::string& mac_address,
    const nearby::fastpair::FastPairInfo fast_pair_info)
    : mac_address(mac_address), fast_pair_info(fast_pair_info) {}

PendingWriteStore::PendingWrite::PendingWrite(PendingWrite&& pending_write) =
    default;
PendingWriteStore::PendingWrite::~PendingWrite() = default;

PendingWriteStore::PendingDelete::PendingDelete(
    const std::string& mac_address,
    const std::string& hex_account_key)
    : mac_address(mac_address), hex_account_key(hex_account_key) {}

PendingWriteStore::PendingDelete::~PendingDelete() = default;

void PendingWriteStore::WritePairedDevice(
    const std::string& mac_address,
    const nearby::fastpair::FastPairInfo fast_pair_info) {
  PrefService* pref_service =
      QuickPairBrowserDelegate::Get()->GetActivePrefService();
  if (!pref_service) {
    CD_LOG(WARNING, Feature::FP)
        << __func__ << ": No user pref service available.";
    return;
  }

  // |fast_pair_info| must be converted first to bytes and then to
  // hex-encoded string format so that no UTF-8 encoding errors are thrown.
  size_t fp_info_size = fast_pair_info.ByteSizeLong();
  base::FixedArray<uint8_t> fp_info_bytes(fp_info_size);
  if (!fast_pair_info.SerializeToArray(fp_info_bytes.data(), fp_info_size)) {
    CD_LOG(WARNING, Feature::FP)
        << __func__ << ": couldn't serialize fast pair info of device "
        << "with mac address " << mac_address
        << ". Not writing this device to PendingWrite list.";
    return;
  }

  ScopedDictPrefUpdate update(pref_service, kFastPairPendingWritesPref);
  update->Set(mac_address, base::HexEncode(fp_info_bytes));
}

std::vector<PendingWriteStore::PendingWrite>
PendingWriteStore::GetPendingWrites() {
  std::vector<PendingWriteStore::PendingWrite> list;
  PrefService* pref_service =
      QuickPairBrowserDelegate::Get()->GetActivePrefService();
  if (!pref_service) {
    CD_LOG(WARNING, Feature::FP)
        << __func__ << ": No user pref service available.";
    return list;
  }

  const base::Value::Dict& result =
      pref_service->GetDict(kFastPairPendingWritesPref);

  for (const auto item : result) {
    // PendingWrite member variable |fast_pair_info| is
    // stored in |result| as a hex encoded string. This need to be parsed into
    // type nearby::fastpair::FastPairInfo before initializing a new
    // PendingWrite to store in |list|.

    // Parsing fast pair info stored as string.
    std::string fp_info_str = item.second.GetString();
    nearby::fastpair::FastPairInfo fast_pair_info;

    // To align with Android's Footprints use, removal of a device from
    // the list of devices saved to a user's account is performed by writing a
    // footprint to Footprints with empty fast pair info.
    //
    // Thus, an empty |fp_info_str| is a valid element that is
    // intended to be written to Footprints. We must explicitly catch
    // this edge-case here, because passing an empty |fp_info_str| to
    // HexStringToBytes() would result in an error.
    if (fp_info_str.empty()) {
      fast_pair_info.ParseFromString(std::string());
    } else {
      std::vector<uint8_t> fp_info_bytes;
      if (!base::HexStringToBytes(fp_info_str, &fp_info_bytes)) {
        CD_LOG(WARNING, Feature::FP)
            << __func__ << ": fast pair info of "
            << "PendingWrite with mac address " << item.first
            << " not perfectly parsed into bytes from a hex-encoded string. "
            << "PendingWrite not added to PendingWrite list.";
        continue;
      }
      // Create fast pair info from byte buffer.
      if (!fast_pair_info.ParseFromArray(fp_info_bytes.data(),
                                         fp_info_bytes.size())) {
        CD_LOG(WARNING, Feature::FP)
            << __func__ << ": failed to parse Fast Pair Info of "
            << " PendingWrite with mac address " << item.first
            << " from bytes to type nearby::fastpair::FastPairInfo. "
            << "PendingWrite not included in PendingWrite list.";
        continue;
      }
    }

    list.emplace_back(/*mac_address=*/item.first, fast_pair_info);
  }

  return list;
}

void PendingWriteStore::OnPairedDeviceSaved(const std::string& mac_address) {
  PrefService* pref_service =
      QuickPairBrowserDelegate::Get()->GetActivePrefService();
  if (!pref_service) {
    CD_LOG(WARNING, Feature::FP)
        << __func__ << ": No user pref service available.";
    return;
  }

  ScopedDictPrefUpdate update(pref_service, kFastPairPendingWritesPref);
  update->Remove(mac_address);
}

void PendingWriteStore::DeletePairedDevice(const std::string& mac_address,
                                           const std::string& hex_account_key) {
  PrefService* pref_service =
      QuickPairBrowserDelegate::Get()->GetActivePrefService();
  if (!pref_service) {
    CD_LOG(WARNING, Feature::FP)
        << __func__ << ": No user pref service available.";
    return;
  }
  ScopedDictPrefUpdate update(pref_service, kFastPairPendingDeletesPref);
  update->Set(mac_address, hex_account_key);
}

std::vector<PendingWriteStore::PendingDelete>
PendingWriteStore::GetPendingDeletes() {
  std::vector<PendingWriteStore::PendingDelete> list;
  PrefService* pref_service =
      QuickPairBrowserDelegate::Get()->GetActivePrefService();
  if (!pref_service) {
    CD_LOG(WARNING, Feature::FP)
        << __func__ << ": No user pref service available.";
    return list;
  }

  const base::Value::Dict& result =
      pref_service->GetDict(kFastPairPendingDeletesPref);

  for (const auto item : result) {
    list.emplace_back(item.first, item.second.GetString());
  }

  return list;
}

void PendingWriteStore::OnPairedDeviceDeleted(const std::string& mac_address) {
  PrefService* pref_service =
      QuickPairBrowserDelegate::Get()->GetActivePrefService();
  if (!pref_service) {
    CD_LOG(WARNING, Feature::FP)
        << __func__ << ": No user pref service available.";
    return;
  }

  ScopedDictPrefUpdate update(pref_service, kFastPairPendingDeletesPref);
  update->Remove(mac_address);
}

void PendingWriteStore::OnPairedDeviceDeleted(
    const std::vector<uint8_t>& account_key) {
  PrefService* pref_service =
      QuickPairBrowserDelegate::Get()->GetActivePrefService();
  if (!pref_service) {
    CD_LOG(WARNING, Feature::FP)
        << __func__ << ": No user pref service available.";
    return;
  }

  std::string hex_account_key = base::HexEncode(account_key);
  ScopedDictPrefUpdate update(pref_service, kFastPairPendingDeletesPref);
  const base::Value::Dict result =
      pref_service->GetDict(kFastPairPendingDeletesPref).Clone();
  for (const auto item : result) {
    if (item.second == hex_account_key) {
      CD_LOG(INFO, Feature::FP)
          << __func__ << ": Successfully removed pending delete from prefs.";
      update->Remove(item.first);
    }
  }
}

}  // namespace quick_pair
}  // namespace ash