xref: /aosp_15_r20/system/chre/apps/nearby/location/lbs/contexthub/nanoapps/nearby/fast_pair_filter.cc (revision 84e339476a462649f82315436d70fd732297a399)
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "location/lbs/contexthub/nanoapps/nearby/fast_pair_filter.h"
18 
19 #include <cinttypes>
20 #include <iterator>
21 
22 #include "location/lbs/contexthub/nanoapps/nearby/bloom_filter.h"
23 #include "location/lbs/contexthub/nanoapps/nearby/fast_pair_account_data.h"
24 #include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
25 
26 #define LOG_TAG "[NEARBY][FAST_PAIR_FILTER]"
27 
28 namespace nearby {
29 
30 constexpr uint16_t kFastPairUuid = 0xFE2C;
31 constexpr uint16_t kFastPairUuidFirstByte = 0xFE;
32 constexpr uint16_t kFastPairUuidSecondByte = 0x2C;
33 constexpr size_t kFpAccountKeyLength = 16;
34 constexpr size_t kFastPairModelIdLength = 3;
35 constexpr uint8_t kAccountKeyFirstByte[] = {
36     0b00000100,  // Default.
37     0b00000101,  // Recent.
38     0b00000110   // In use.
39 };
40 // The key fed into Bloom Filter is the concatenation of account key, SALT, and
41 // RRD, and the max length is kFpAccountKeyLength + 3 x 2^4. SALT,Battery, or
42 // RRD length is less than 2^4 according to the spec.
43 constexpr size_t kMaxBloomFilterKeyLength = kFpAccountKeyLength + 48;
44 
45 // Returns true if data_element is Fast Pair account.
IsAccountDataElement(const nearby_DataElement & data_element)46 bool IsAccountDataElement(const nearby_DataElement &data_element) {
47   return (data_element.has_key &&
48           data_element.key ==
49               nearby_DataElement_ElementType_DE_FAST_PAIR_ACCOUNT_KEY &&
50           data_element.has_value && data_element.has_value_length &&
51           data_element.value_length == kFpAccountKeyLength);
52 }
53 
54 // Returns true if filter include Fast Pair initial pair.
55 // Otherwise, returns false and saves account keys in account_keys.
CheckFastPairFilter(const nearby_BleFilter & filter,chre::DynamicVector<const uint8_t * > * account_keys)56 bool CheckFastPairFilter(const nearby_BleFilter &filter,
57                          chre::DynamicVector<const uint8_t *> *account_keys) {
58   bool has_initial_pair = false;
59   for (int i = 0; i < filter.data_element_count; i++) {
60     if (IsAccountDataElement(filter.data_element[i])) {
61       uint8_t account_value = 0;
62       for (size_t j = 0; j < kFpAccountKeyLength; j++) {
63         account_value |= filter.data_element[i].value[j];
64       }
65       if (account_value == 0) {
66         // Account value for initial pair are all zeros.
67         LOGD("Find Fast Pair initial pair filter.");
68         has_initial_pair = true;
69       } else {
70         account_keys->push_back(
71             static_cast<const uint8_t *>(filter.data_element[i].value));
72       }
73     }
74   }
75   return has_initial_pair;
76 }
77 
78 // Fills a Fast Pair filtered result with service_data and account_key.
79 // Passes account_key as nullptr for initial pair.
80 // Returns false when filling failed due to memory overflow.
FillResult(const BleServiceData & service_data,const uint8_t * account_key,nearby_BleFilterResult * result)81 bool FillResult(const BleServiceData &service_data, const uint8_t *account_key,
82                 nearby_BleFilterResult *result) {
83   if (result->data_element_count >= std::size(result->data_element)) {
84     LOGE("Failed to fill Fast Pair result. Data Elements buffer full");
85     return false;
86   }
87   // Sends the service data, which will be re-parsed by Fast Pair in GmsCore.
88   if (!result->has_ble_service_data) {
89     // the buffer size of 'result->ble_service_data' is defined in
90     // ble_filter.options, which must be large enough to hold one byte service
91     // length, two bytes UUID, and the service data.
92     if (account_key == nullptr) {
93       // Initial Pair service data only has model ID.
94       static_assert(kFastPairModelIdLength + 3 <=
95                     sizeof(result->ble_service_data));
96     } else if (service_data.length + 3 > sizeof(result->ble_service_data)) {
97       LOGE("Fast Pair BLE service data overflows the result buffer.");
98       return false;
99     }
100     result->has_ble_service_data = true;
101     // First byte is the length of service data plus two bytes UUID.
102     result->ble_service_data[0] = service_data.length + 2;
103     // Second and third byte includes the FP 3.2 UUID.
104     result->ble_service_data[1] = kFastPairUuidFirstByte;
105     result->ble_service_data[2] = kFastPairUuidSecondByte;
106     // The rest bytes are service data.
107     memcpy(result->ble_service_data + 3, service_data.data,
108            service_data.length);
109   }
110 
111   // Capacity has been checked above.
112   size_t de_index = result->data_element_count;
113   result->data_element[de_index].has_key = true;
114   result->data_element[de_index].key =
115       nearby_DataElement_ElementType_DE_FAST_PAIR_ACCOUNT_KEY;
116   result->data_element[de_index].has_value_length = true;
117   result->data_element[de_index].value_length = kFpAccountKeyLength;
118   result->data_element[de_index].has_value = true;
119   CHRE_ASSERT(sizeof(result->data_element[de_index].value) >=
120               kFpAccountKeyLength);
121   if (account_key != nullptr) {
122     memcpy(result->data_element[de_index].value, account_key,
123            kFpAccountKeyLength);
124   }
125   result->data_element_count++;
126 
127   result->has_result_type = true;
128   result->result_type = nearby_BleFilterResult_ResultType_RESULT_FAST_PAIR;
129   return true;
130 }
131 
MatchInitialFastPair(const BleServiceData & ble_service_data,nearby_BleFilterResult * result)132 bool MatchInitialFastPair(const BleServiceData &ble_service_data,
133                           nearby_BleFilterResult *result) {
134   if (ble_service_data.uuid != kFastPairUuid) {
135     LOGD("Not Fast Pair service data.");
136     return false;
137   }
138   // Service data for initial pair only contains the three-byte model id.
139   if (ble_service_data.length != kFastPairModelIdLength) {
140     LOGD(
141         "Not a initial pair whose BLE service data only includes a model "
142         "id of three bytes.");
143     return false;
144   }
145   return FillResult(ble_service_data, nullptr, result);
146 }
147 
MatchSubsequentPair(const uint8_t * account_key,const BleServiceData & service_data,nearby_BleFilterResult * result)148 bool MatchSubsequentPair(const uint8_t *account_key,
149                          const BleServiceData &service_data,
150                          nearby_BleFilterResult *result) {
151   LOGD("MatchSubsequentPair");
152   if (service_data.uuid != kFastPairUuid) {
153     LOGD("service data uuid %x is not Fast Pair uuid %x", service_data.uuid,
154          kFastPairUuid);
155     return false;
156   }
157   if (service_data.length == kFastPairModelIdLength) {
158     LOGD(
159         "Initial Pair advertisements, not proceed to subsequent pair "
160         "filtering.");
161     return false;
162   }
163   FastPairAccountData account_data = FastPairAccountData::Parse(
164       ByteArray(const_cast<uint8_t *>(service_data.data), service_data.length));
165   if (!account_data.is_valid) {
166     return false;
167   }
168   LOGD_SENSITIVE_INFO("Fast Pair Bloom Filter:");
169   for (size_t i = 0; i < account_data.filter.length; i++) {
170     LOGD_SENSITIVE_INFO("%x", account_data.filter.data[i]);
171   }
172   if (account_data.filter.length > BloomFilter::kMaxBloomFilterByteSize) {
173     LOGE("Subsequent Pair Bloom Filter size %zu exceeds: %zu",
174          account_data.filter.length, BloomFilter::kMaxBloomFilterByteSize);
175     return false;
176   }
177   BloomFilter bloom_filter =
178       BloomFilter(account_data.filter.data, account_data.filter.length);
179   // SALT, BATTERY, and RRD length must be less than 2^4 in FastPairAccountData
180   // implementation based on the spec.
181   CHRE_ASSERT((kFpAccountKeyLength + account_data.salt.length +
182                account_data.battery.length + account_data.rrd.length) <=
183               kMaxBloomFilterKeyLength);
184   uint8_t key[kMaxBloomFilterKeyLength];
185   size_t pos = 0;
186   memcpy(&key[pos], account_key, kFpAccountKeyLength);
187   pos += kFpAccountKeyLength;
188   memcpy(&key[pos], account_data.salt.data, account_data.salt.length);
189   pos += account_data.salt.length;
190   LOGD_SENSITIVE_INFO("Fast Pair subsequent pair SALT");
191   for (size_t i = 0; i < account_data.salt.length; i++) {
192     LOGD_SENSITIVE_INFO("%x", account_data.salt.data[i]);
193   }
194   memcpy(&key[pos], account_data.battery.data, account_data.battery.length);
195   pos += account_data.battery.length;
196   LOGD_SENSITIVE_INFO("Fast Pair subsequent pair battery:");
197   for (size_t i = 0; i < account_data.battery.length; i++) {
198     LOGD_SENSITIVE_INFO("%x", account_data.battery.data[i]);
199   }
200   if (account_data.version == 1) {
201     memcpy(&key[pos], account_data.rrd.data, account_data.rrd.length);
202     pos += account_data.rrd.length;
203     LOGD_SENSITIVE_INFO("Fast Pair subsequent pair RRD");
204     for (size_t i = 0; i < account_data.rrd.length; i++) {
205       LOGD_SENSITIVE_INFO("%x", account_data.rrd.data[i]);
206     }
207   }
208 
209   LOGD_SENSITIVE_INFO("Fast Pair subsequent pair combined key:");
210   for (size_t i = 0; i < pos; i++) {
211     LOGD_SENSITIVE_INFO("%x", key[i]);
212   }
213   bool matched = bloom_filter.MayContain(key, pos);
214   if (!matched && account_data.rrd.length > 0) {
215     // Flip the first byte to 4, 5, 6 when RRD is presented.
216     for (uint8_t firstByte : kAccountKeyFirstByte) {
217       key[0] = firstByte;
218       matched = bloom_filter.MayContain(key, pos);
219       if (matched) break;
220     }
221   }
222   if (matched) {
223     LOGD("Subsequent Pair match succeeds.");
224     return FillResult(service_data, account_key, result);
225   } else {
226     return false;
227   }
228 }
229 
MatchFastPair(const nearby_BleFilter & filter,const BleScanRecord & scan_record,nearby_BleFilterResult * result)230 bool MatchFastPair(const nearby_BleFilter &filter,
231                    const BleScanRecord &scan_record,
232                    nearby_BleFilterResult *result) {
233   LOGD("MatchFastPair");
234   chre::DynamicVector<const uint8_t *> account_keys;
235   if (CheckFastPairFilter(filter, &account_keys)) {
236     LOGD("Fast Pair initial pair filter found.");
237     for (const auto &ble_service_data : scan_record.service_data) {
238       if (MatchInitialFastPair(ble_service_data, result)) {
239         return true;
240       }
241     }
242   } else {
243     for (const auto account_key : account_keys) {
244       for (const auto &ble_service_data : scan_record.service_data) {
245         if (MatchSubsequentPair(account_key, ble_service_data, result)) {
246           return true;
247         }
248       }
249     }
250   }
251   return false;
252 }
253 
254 }  // namespace nearby
255