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