chromium/chromeos/ash/services/quick_pair/public/cpp/account_key_filter.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 "chromeos/ash/services/quick_pair/public/cpp/account_key_filter.h"

#include <math.h>

#include <cstddef>
#include <iterator>

#include "base/ranges/algorithm.h"
#include "chromeos/ash/services/quick_pair/public/cpp/not_discoverable_advertisement.h"
#include "crypto/sha2.h"

namespace ash {
namespace quick_pair {

namespace {

constexpr int kBitsInByte = 8;

// SASS enabled peripherals create their Bloom filters by changing the first
// byte of either the account key in use or the most recently used account key
// according to this spec:
// https://developers.google.com/nearby/fast-pair/early-access/specifications/extensions/sass#SassInUseAccountKey
constexpr uint8_t kRecentlyUsedByte = 0x05;
constexpr uint8_t kInUseByte = 0x06;

// Helper to AccountKeyFilter::IsAccountKeyInFilter().
// Performs the test to see if |data| is in |bit_sets|, a Bloom filter.
bool AccountKeyFilterTest(const std::vector<uint8_t>& data,
                          const std::vector<uint8_t>& bit_sets) {
  std::array<uint8_t, 32> hashed = crypto::SHA256Hash(data);

  // Iterate over the hashed input in 4 byte increments, combine those 4
  // bytes into an unsigned int and use it as the index into our
  // |bit_sets|.
  for (size_t i = 0; i < hashed.size(); i += 4) {
    uint32_t hash = uint32_t{hashed[i]} << 24 | uint32_t{hashed[i + 1]} << 16 |
                    uint32_t{hashed[i + 2]} << 8 | hashed[i + 3];

    size_t num_bits = bit_sets.size() * kBitsInByte;
    size_t n = hash % num_bits;
    size_t byte_index = floor(n / kBitsInByte);
    size_t bit_index = n % kBitsInByte;
    bool is_set = (bit_sets[byte_index] >> bit_index) & 0x01;

    if (!is_set)
      return false;
  }

  return true;
}

}  // namespace

AccountKeyFilter::AccountKeyFilter(
    const NotDiscoverableAdvertisement& advertisement)
    : bit_sets_(advertisement.account_key_filter) {
  salt_values_.resize(advertisement.salt.size());
  base::ranges::copy(advertisement.salt, salt_values_.begin());

  // If the advertisement contains battery information, then that information
  // was also appended to the account keys to generate the filter. We need to
  // do the same when checking for matches, so save the values in salt_values_
  // for that purpose later.
  if (advertisement.battery_notification) {
    salt_values_.push_back(
        advertisement.battery_notification->show_ui ? 0b00110011 : 0b00110100);

    salt_values_.push_back(
        advertisement.battery_notification->left_bud_info.ToByte());

    salt_values_.push_back(
        advertisement.battery_notification->right_bud_info.ToByte());

    salt_values_.push_back(
        advertisement.battery_notification->case_info.ToByte());
  }
}

AccountKeyFilter::AccountKeyFilter(
    const std::vector<uint8_t>& account_key_filter_bytes,
    const std::vector<uint8_t>& salt_values)
    : bit_sets_(account_key_filter_bytes), salt_values_(salt_values) {}

AccountKeyFilter::AccountKeyFilter(const AccountKeyFilter&) = default;
AccountKeyFilter& AccountKeyFilter::operator=(AccountKeyFilter&&) = default;
AccountKeyFilter::~AccountKeyFilter() = default;

bool AccountKeyFilter::IsAccountKeyInFilter(
    const std::vector<uint8_t>& account_key_bytes) const {
  if (bit_sets_.empty())
    return false;

  // We first need to append the salt value to the input (see
  // https://developers.google.com/nearby/fast-pair/spec#AccountKeyFilter).
  std::vector<uint8_t> default_account_key(account_key_bytes);
  for (auto& byte : salt_values_)
    default_account_key.push_back(byte);

  // We need to try account keys with different first bytes in case
  // the peripheral is SASS per
  // https://developers.google.com/nearby/fast-pair/early-access/specifications/extensions/sass#SassAdvertisingPayload
  std::vector<uint8_t> recently_used_account_key(default_account_key);
  recently_used_account_key[0] = kRecentlyUsedByte;

  std::vector<uint8_t> in_use_account_key(default_account_key);
  in_use_account_key[0] = kInUseByte;

  return AccountKeyFilterTest(default_account_key, bit_sets_) ||
         AccountKeyFilterTest(in_use_account_key, bit_sets_) ||
         AccountKeyFilterTest(recently_used_account_key, bit_sets_);
}

}  // namespace quick_pair
}  // namespace ash