blob: f7513f33cf69723804655990f0d3730377ffceaa [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "location/lbs/contexthub/nanoapps/nearby/fast_pair_filter.h"
#include <cinttypes>
#include <iterator>
#include "location/lbs/contexthub/nanoapps/nearby/bloom_filter.h"
#include "location/lbs/contexthub/nanoapps/nearby/fast_pair_account_data.h"
#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
#define LOG_TAG "[NEARBY][FAST_PAIR_FILTER]"
namespace nearby {
constexpr uint16_t kFastPairUuid = 0xFE2C;
constexpr uint16_t kFastPairUuidFirstByte = 0xFE;
constexpr uint16_t kFastPairUuidSecondByte = 0x2C;
constexpr size_t kFpAccountKeyLength = 16;
constexpr size_t kFastPairModelIdLength = 3;
constexpr uint8_t kAccountKeyFirstByte[] = {
0b00000100, // Default.
0b00000101, // Recent.
0b00000110 // In use.
};
// The key fed into Bloom Filter is the concatenation of account key, SALT, and
// RRD, and the max length is kFpAccountKeyLength + 3 x 2^4. SALT,Battery, or
// RRD length is less than 2^4 according to the spec.
constexpr size_t kMaxBloomFilterKeyLength = kFpAccountKeyLength + 48;
// Returns true if data_element is Fast Pair account.
bool IsAccountDataElement(const nearby_DataElement &data_element) {
return (data_element.has_key &&
data_element.key ==
nearby_DataElement_ElementType_DE_FAST_PAIR_ACCOUNT_KEY &&
data_element.has_value && data_element.has_value_length &&
data_element.value_length == kFpAccountKeyLength);
}
// Returns true if filter include Fast Pair initial pair.
// Otherwise, returns false and saves account keys in account_keys.
bool CheckFastPairFilter(const nearby_BleFilter &filter,
chre::DynamicVector<const uint8_t *> *account_keys) {
bool has_initial_pair = false;
for (int i = 0; i < filter.data_element_count; i++) {
if (IsAccountDataElement(filter.data_element[i])) {
uint8_t account_value = 0;
for (size_t j = 0; j < kFpAccountKeyLength; j++) {
account_value |= filter.data_element[i].value[j];
}
if (account_value == 0) {
// Account value for initial pair are all zeros.
LOGD("Find Fast Pair initial pair filter.");
has_initial_pair = true;
} else {
account_keys->push_back(
static_cast<const uint8_t *>(filter.data_element[i].value));
}
}
}
return has_initial_pair;
}
// Fills a Fast Pair filtered result with service_data and account_key.
// Passes account_key as nullptr for initial pair.
// Returns false when filling failed due to memory overflow.
bool FillResult(const BleServiceData &service_data, const uint8_t *account_key,
nearby_BleFilterResult *result) {
if (result->data_element_count >= std::size(result->data_element)) {
LOGE("Failed to fill Fast Pair result. Data Elements buffer full");
return false;
}
// Sends the service data, which will be re-parsed by Fast Pair in GmsCore.
if (!result->has_ble_service_data) {
// the buffer size of 'result->ble_service_data' is defined in
// ble_filter.options, which must be large enough to hold one byte service
// length, two bytes UUID, and the service data.
if (account_key == nullptr) {
// Initial Pair service data only has model ID.
static_assert(kFastPairModelIdLength + 3 <=
sizeof(result->ble_service_data));
} else if (service_data.length + 3 > sizeof(result->ble_service_data)) {
LOGE("Fast Pair BLE service data overflows the result buffer.");
return false;
}
result->has_ble_service_data = true;
// First byte is the length of service data plus two bytes UUID.
result->ble_service_data[0] = service_data.length + 2;
// Second and third byte includes the FP 3.2 UUID.
result->ble_service_data[1] = kFastPairUuidFirstByte;
result->ble_service_data[2] = kFastPairUuidSecondByte;
// The rest bytes are service data.
memcpy(result->ble_service_data + 3, service_data.data,
service_data.length);
}
// Capacity has been checked above.
size_t de_index = result->data_element_count;
result->data_element[de_index].has_key = true;
result->data_element[de_index].key =
nearby_DataElement_ElementType_DE_FAST_PAIR_ACCOUNT_KEY;
result->data_element[de_index].has_value_length = true;
result->data_element[de_index].value_length = kFpAccountKeyLength;
result->data_element[de_index].has_value = true;
CHRE_ASSERT(sizeof(result->data_element[de_index].value) >=
kFpAccountKeyLength);
if (account_key != nullptr) {
memcpy(result->data_element[de_index].value, account_key,
kFpAccountKeyLength);
}
result->data_element_count++;
result->has_result_type = true;
result->result_type = nearby_BleFilterResult_ResultType_RESULT_FAST_PAIR;
return true;
}
bool MatchInitialFastPair(const BleServiceData &ble_service_data,
nearby_BleFilterResult *result) {
if (ble_service_data.uuid != kFastPairUuid) {
LOGD("Not Fast Pair service data.");
return false;
}
// Service data for initial pair only contains the three-byte model id.
if (ble_service_data.length != kFastPairModelIdLength) {
LOGD(
"Not a initial pair whose BLE service data only includes a model "
"id of three bytes.");
return false;
}
return FillResult(ble_service_data, nullptr, result);
}
bool MatchSubsequentPair(const uint8_t *account_key,
const BleServiceData &service_data,
nearby_BleFilterResult *result) {
LOGD("MatchSubsequentPair");
if (service_data.uuid != kFastPairUuid) {
LOGD("service data uuid %x is not Fast Pair uuid %x", service_data.uuid,
kFastPairUuid);
return false;
}
if (service_data.length == kFastPairModelIdLength) {
LOGD(
"Initial Pair advertisements, not proceed to subsequent pair "
"filtering.");
return false;
}
FastPairAccountData account_data = FastPairAccountData::Parse(
ByteArray(const_cast<uint8_t *>(service_data.data), service_data.length));
if (!account_data.is_valid) {
return false;
}
LOGD_SENSITIVE_INFO("Fast Pair Bloom Filter:");
for (size_t i = 0; i < account_data.filter.length; i++) {
LOGD_SENSITIVE_INFO("%x", account_data.filter.data[i]);
}
if (account_data.filter.length > BloomFilter::kMaxBloomFilterByteSize) {
LOGE("Subsequent Pair Bloom Filter size %zu exceeds: %zu",
account_data.filter.length, BloomFilter::kMaxBloomFilterByteSize);
return false;
}
BloomFilter bloom_filter =
BloomFilter(account_data.filter.data, account_data.filter.length);
// SALT, BATTERY, and RRD length must be less than 2^4 in FastPairAccountData
// implementation based on the spec.
CHRE_ASSERT((kFpAccountKeyLength + account_data.salt.length +
account_data.battery.length + account_data.rrd.length) <=
kMaxBloomFilterKeyLength);
uint8_t key[kMaxBloomFilterKeyLength];
size_t pos = 0;
memcpy(&key[pos], account_key, kFpAccountKeyLength);
pos += kFpAccountKeyLength;
memcpy(&key[pos], account_data.salt.data, account_data.salt.length);
pos += account_data.salt.length;
LOGD_SENSITIVE_INFO("Fast Pair subsequent pair SALT");
for (size_t i = 0; i < account_data.salt.length; i++) {
LOGD_SENSITIVE_INFO("%x", account_data.salt.data[i]);
}
memcpy(&key[pos], account_data.battery.data, account_data.battery.length);
pos += account_data.battery.length;
LOGD_SENSITIVE_INFO("Fast Pair subsequent pair battery:");
for (size_t i = 0; i < account_data.battery.length; i++) {
LOGD_SENSITIVE_INFO("%x", account_data.battery.data[i]);
}
if (account_data.version == 1) {
memcpy(&key[pos], account_data.rrd.data, account_data.rrd.length);
pos += account_data.rrd.length;
LOGD_SENSITIVE_INFO("Fast Pair subsequent pair RRD");
for (size_t i = 0; i < account_data.rrd.length; i++) {
LOGD_SENSITIVE_INFO("%x", account_data.rrd.data[i]);
}
}
LOGD_SENSITIVE_INFO("Fast Pair subsequent pair combined key:");
for (size_t i = 0; i < pos; i++) {
LOGD_SENSITIVE_INFO("%x", key[i]);
}
bool matched = bloom_filter.MayContain(key, pos);
if (!matched && account_data.rrd.length > 0) {
// Flip the first byte to 4, 5, 6 when RRD is presented.
for (uint8_t firstByte : kAccountKeyFirstByte) {
key[0] = firstByte;
matched = bloom_filter.MayContain(key, pos);
if (matched) break;
}
}
if (matched) {
LOGD("Subsequent Pair match succeeds.");
return FillResult(service_data, account_key, result);
} else {
return false;
}
}
bool MatchFastPair(const nearby_BleFilter &filter,
const BleScanRecord &scan_record,
nearby_BleFilterResult *result) {
LOGD("MatchFastPair");
chre::DynamicVector<const uint8_t *> account_keys;
if (CheckFastPairFilter(filter, &account_keys)) {
LOGD("Fast Pair initial pair filter found.");
for (const auto &ble_service_data : scan_record.service_data) {
if (MatchInitialFastPair(ble_service_data, result)) {
return true;
}
}
} else {
for (const auto account_key : account_keys) {
for (const auto &ble_service_data : scan_record.service_data) {
if (MatchSubsequentPair(account_key, ble_service_data, result)) {
return true;
}
}
}
}
return false;
}
} // namespace nearby