/proc/self/cwd/pw_bluetooth_sapphire/host/common/advertising_data.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright 2023 The Pigweed Authors |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
4 | | // use this file except in compliance with the License. You may obtain a copy of |
5 | | // the License at |
6 | | // |
7 | | // https://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
11 | | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
12 | | // License for the specific language governing permissions and limitations under |
13 | | // the License. |
14 | | |
15 | | #include "pw_bluetooth_sapphire/internal/host/common/advertising_data.h" |
16 | | |
17 | | #include <cpp-string/string_printf.h> |
18 | | #include <pw_assert/check.h> |
19 | | #include <pw_bytes/endian.h> |
20 | | #include <pw_preprocessor/compiler.h> |
21 | | #include <pw_string/utf_codecs.h> |
22 | | |
23 | | #include <string> |
24 | | #include <type_traits> |
25 | | |
26 | | #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h" |
27 | | #include "pw_bluetooth_sapphire/internal/host/common/log.h" |
28 | | #include "pw_bluetooth_sapphire/internal/host/common/to_string.h" |
29 | | #include "pw_bluetooth_sapphire/internal/host/common/uuid.h" |
30 | | |
31 | | namespace bt { |
32 | | |
33 | | namespace { |
34 | | |
35 | 529 | DataType ServiceUuidTypeForUuidSize(UUIDElemSize size, bool complete) { |
36 | 529 | switch (size) { |
37 | 161 | case UUIDElemSize::k16Bit: |
38 | 161 | return complete ? DataType::kComplete16BitServiceUuids |
39 | 161 | : DataType::kIncomplete16BitServiceUuids; |
40 | 177 | case UUIDElemSize::k32Bit: |
41 | 177 | return complete ? DataType::kComplete32BitServiceUuids |
42 | 177 | : DataType::kIncomplete32BitServiceUuids; |
43 | 191 | case UUIDElemSize::k128Bit: |
44 | 191 | return complete ? DataType::kComplete128BitServiceUuids |
45 | 191 | : DataType::kIncomplete128BitServiceUuids; |
46 | 0 | default: |
47 | 0 | PW_CRASH( |
48 | 529 | "called ServiceUuidTypeForUuidSize with unknown UUIDElemSize %du", |
49 | 529 | size); |
50 | 529 | } |
51 | 529 | } |
52 | | |
53 | 4.97k | DataType ServiceDataTypeForUuidSize(UUIDElemSize size) { |
54 | 4.97k | switch (size) { |
55 | 2.15k | case UUIDElemSize::k16Bit: |
56 | 2.15k | return DataType::kServiceData16Bit; |
57 | 2.15k | case UUIDElemSize::k32Bit: |
58 | 2.15k | return DataType::kServiceData32Bit; |
59 | 658 | case UUIDElemSize::k128Bit: |
60 | 658 | return DataType::kServiceData128Bit; |
61 | 0 | default: |
62 | 0 | PW_CRASH( |
63 | 4.97k | "called ServiceDataTypeForUuidSize with unknown UUIDElemSize %du", |
64 | 4.97k | size); |
65 | 4.97k | }; |
66 | 0 | } |
67 | | |
68 | 114 | DataType SolicitationUuidTypeForUuidSize(UUIDElemSize size) { |
69 | 114 | switch (size) { |
70 | 71 | case UUIDElemSize::k16Bit: |
71 | 71 | return DataType::kSolicitationUuid16Bit; |
72 | 26 | case UUIDElemSize::k32Bit: |
73 | 26 | return DataType::kSolicitationUuid32Bit; |
74 | 17 | case UUIDElemSize::k128Bit: |
75 | 17 | return DataType::kSolicitationUuid128Bit; |
76 | 0 | default: |
77 | 0 | PW_CRASH( |
78 | 114 | "called SolicitationUuidTypeForUuidSize with unknown UUIDElemSize " |
79 | 114 | "%du", |
80 | 114 | size); |
81 | 114 | }; |
82 | 0 | } |
83 | | |
84 | 41.0k | size_t EncodedServiceDataSize(const UUID& uuid, const BufferView data) { |
85 | 41.0k | return uuid.CompactSize() + data.size(); |
86 | 41.0k | } |
87 | | |
88 | | // clang-format off |
89 | | // https://www.bluetooth.com/specifications/assigned-numbers/uri-scheme-name-string-mapping |
90 | | constexpr const char* kUriSchemes[] = {"aaa:", "aaas:", "about:", "acap:", "acct:", "cap:", "cid:", |
91 | | "coap:", "coaps:", "crid:", "data:", "dav:", "dict:", "dns:", "file:", "ftp:", "geo:", |
92 | | "go:", "gopher:", "h323:", "http:", "https:", "iax:", "icap:", "im:", "imap:", "info:", |
93 | | "ipp:", "ipps:", "iris:", "iris.beep:", "iris.xpc:", "iris.xpcs:", "iris.lwz:", "jabber:", |
94 | | "ldap:", "mailto:", "mid:", "msrp:", "msrps:", "mtqp:", "mupdate:", "news:", "nfs:", "ni:", |
95 | | "nih:", "nntp:", "opaquelocktoken:", "pop:", "pres:", "reload:", "rtsp:", "rtsps:", "rtspu:", |
96 | | "service:", "session:", "shttp:", "sieve:", "sip:", "sips:", "sms:", "snmp:", "soap.beep:", |
97 | | "soap.beeps:", "stun:", "stuns:", "tag:", "tel:", "telnet:", "tftp:", "thismessage:", |
98 | | "tn3270:", "tip:", "turn:", "turns:", "tv:", "urn:", "vemmi:", "ws:", "wss:", "xcon:", |
99 | | "xcon-userid:", "xmlrpc.beep:", "xmlrpc.beeps:", "xmpp:", "z39.50r:", "z39.50s:", "acr:", |
100 | | "adiumxtra:", "afp:", "afs:", "aim:", "apt:", "attachment:", "aw:", "barion:", "beshare:", |
101 | | "bitcoin:", "bolo:", "callto:", "chrome:", "chrome-extension:", "com-eventbrite-attendee:", |
102 | | "content:", "cvs:", "dlna-playsingle:", "dlna-playcontainer:", "dtn:", "dvb:", "ed2k:", |
103 | | "facetime:", "feed:", "feedready:", "finger:", "fish:", "gg:", "git:", "gizmoproject:", |
104 | | "gtalk:", "ham:", "hcp:", "icon:", "ipn:", "irc:", "irc6:", "ircs:", "itms:", "jar:", |
105 | | "jms:", "keyparc:", "lastfm:", "ldaps:", "magnet:", "maps:", "market:", "message:", "mms:", |
106 | | "ms-help:", "ms-settings-power:", "msnim:", "mumble:", "mvn:", "notes:", "oid:", "palm:", |
107 | | "paparazzi:", "pkcs11:", "platform:", "proxy:", "psyc:", "query:", "res:", "resource:", |
108 | | "rmi:", "rsync:", "rtmfp:", "rtmp:", "secondlife:", "sftp:", "sgn:", "skype:", "smb:", |
109 | | "smtp:", "soldat:", "spotify:", "ssh:", "steam:", "submit:", "svn:", "teamspeak:", |
110 | | "teliaeid:", "things:", "udp:", "unreal:", "ut2004:", "ventrilo:", "view-source:", |
111 | | "webcal:", "wtai:", "wyciwyg:", "xfire:", "xri:", "ymsgr:", "example:", |
112 | | "ms-settings-cloudstorage:"}; |
113 | | // clang-format on |
114 | | |
115 | | const size_t kUriSchemesSize = std::extent<decltype(kUriSchemes)>::value; |
116 | | |
117 | 67.8k | std::string EncodeUri(const std::string& uri) { |
118 | 5.38M | for (uint32_t i = 0; i < kUriSchemesSize; i++) { |
119 | 5.36M | const char* scheme = kUriSchemes[i]; |
120 | 5.36M | size_t scheme_len = strlen(scheme); |
121 | 5.36M | if (std::strncmp(uri.c_str(), scheme, scheme_len) == 0) { |
122 | 52.9k | const pw::Result<pw::utf8::EncodedCodePoint> encoded_scheme = |
123 | 52.9k | pw::utf8::EncodeCodePoint(i + 2); |
124 | 52.9k | PW_DCHECK(encoded_scheme.ok()); |
125 | 52.9k | return std::string(encoded_scheme->as_view()) + uri.substr(scheme_len); |
126 | 52.9k | } |
127 | 5.36M | } |
128 | | // First codepoint (U+0001) is for uncompressed schemes. |
129 | 14.8k | const pw::Result<pw::utf8::EncodedCodePoint> encoded_scheme = |
130 | 14.8k | pw::utf8::EncodeCodePoint(1u); |
131 | 14.8k | PW_DCHECK(encoded_scheme.ok()); |
132 | 14.8k | return std::string(encoded_scheme->as_view()) + uri; |
133 | 14.8k | } |
134 | | |
135 | | const char kUndefinedScheme = 0x01; |
136 | | |
137 | 48.0k | std::string DecodeUri(const std::string& uri) { |
138 | 48.0k | if (uri[0] == kUndefinedScheme) { |
139 | 5.50k | return uri.substr(1); |
140 | 5.50k | } |
141 | | |
142 | | // NOTE: as we are reading UTF-8 from `uri`, it is possible that `code_point` |
143 | | // corresponds to > 1 byte of `uri` (even for valid URI encoding schemes, as |
144 | | // U+00(>7F) encodes to 2 bytes). |
145 | 42.5k | const auto result = pw::utf8::ReadCodePoint(uri); |
146 | 42.5k | if (!result.ok()) { |
147 | 5.83k | bt_log(INFO, |
148 | 5.83k | "gap-le", |
149 | 5.83k | "Attempted to decode malformed UTF-8 in AdvertisingData URI"); |
150 | 5.83k | return ""; |
151 | 5.83k | } |
152 | 36.7k | const uint32_t code_point = result->code_point(); |
153 | | // `uri` is not a c-string, so URIs that start with '\0' after c_str |
154 | | // conversion (i.e. both empty URIs and URIs with leading null bytes '\0') are |
155 | | // caught by the code_point < 2 check. We check |
156 | | // "< 2" instead of "== 0" for redundancy (extra safety!) with the |
157 | | // kUndefinedScheme check above. |
158 | 36.7k | if (code_point >= kUriSchemesSize + 2 || code_point < 2) { |
159 | 901 | bt_log( |
160 | 901 | ERROR, |
161 | 901 | "gap-le", |
162 | 901 | "Failed to decode URI - supplied UTF-8 encoding scheme codepoint %u " |
163 | 901 | "must be in the " |
164 | 901 | "range 2-kUriSchemesSize + 1 (2-%zu) to correspond to a URI encoding", |
165 | 901 | code_point, |
166 | 901 | kUriSchemesSize + 1); |
167 | 901 | return ""; |
168 | 901 | } |
169 | 35.8k | return kUriSchemes[code_point - 2] + uri.substr(result->size()); |
170 | 36.7k | } |
171 | | |
172 | | template <typename T> |
173 | 2.18k | inline size_t BufferWrite(MutableByteBuffer* buffer, size_t pos, const T& var) { |
174 | 2.18k | buffer->Write((const uint8_t*)(uintptr_t)(&var), sizeof(T), pos); |
175 | 2.18k | return sizeof(T); |
176 | 2.18k | } |
177 | | |
178 | | } // namespace |
179 | | |
180 | 8.72k | AdvertisingData::AdvertisingData(AdvertisingData&& other) noexcept { |
181 | 8.72k | *this = std::move(other); |
182 | 8.72k | } |
183 | | |
184 | 8.72k | AdvertisingData& AdvertisingData::operator=(AdvertisingData&& other) noexcept { |
185 | | // Reset `other`'s state to that of a fresh, empty AdvertisingData |
186 | 8.72k | local_name_ = std::exchange(other.local_name_, {}); |
187 | 8.72k | tx_power_ = std::exchange(other.tx_power_, {}); |
188 | 8.72k | appearance_ = std::exchange(other.appearance_, {}); |
189 | 8.72k | service_uuids_ = std::exchange(other.service_uuids_, kEmptyServiceUuidMap); |
190 | 8.72k | solicitation_uuids_ = |
191 | 8.72k | std::exchange(other.solicitation_uuids_, kEmptyServiceUuidMap); |
192 | 8.72k | manufacturer_data_ = std::exchange(other.manufacturer_data_, {}); |
193 | 8.72k | service_data_ = std::exchange(other.service_data_, {}); |
194 | 8.72k | uris_ = std::exchange(other.uris_, {}); |
195 | 8.72k | flags_ = std::exchange(other.flags_, {}); |
196 | 8.72k | resolvable_set_identifier_ = |
197 | 8.72k | std::exchange(other.resolvable_set_identifier_, {}); |
198 | 8.72k | broadcast_name_ = std::exchange(other.broadcast_name_, {}); |
199 | 8.72k | return *this; |
200 | 8.72k | } |
201 | | |
202 | 34 | std::string AdvertisingData::ParseErrorToString(ParseError e) { |
203 | 34 | switch (e) { |
204 | 2 | case ParseError::kInvalidTlvFormat: |
205 | 2 | return "provided bytes are not a valid type-length-value container"; |
206 | 2 | case ParseError::kTxPowerLevelMalformed: |
207 | 2 | return "malformed tx power level"; |
208 | 0 | case ParseError::kLocalNameTooLong: |
209 | 0 | return "local name exceeds max length (248)"; |
210 | 26 | case ParseError::kUuidsMalformed: |
211 | 26 | return "malformed UUIDs list"; |
212 | 0 | case ParseError::kManufacturerSpecificDataTooSmall: |
213 | 0 | return "manufacturer specific data too small"; |
214 | 0 | case ParseError::kServiceDataTooSmall: |
215 | 0 | return "service data too small to fit UUIDs"; |
216 | 0 | case ParseError::kServiceDataUuidMalformed: |
217 | 0 | return "UUIDs associated with service data are malformed"; |
218 | 2 | case ParseError::kAppearanceMalformed: |
219 | 2 | return "malformed appearance field"; |
220 | 0 | case ParseError::kMissing: |
221 | 0 | return "data missing"; |
222 | 0 | case ParseError::kResolvableSetIdentifierSize: |
223 | 0 | return "resolvable set identifier is wrong size"; |
224 | 2 | case ParseError::kBroadcastNameTooShort: |
225 | 2 | return "broadcast name is too short"; |
226 | 0 | case ParseError::kBroadcastNameTooLong: |
227 | 0 | return "broadcast name is too long"; |
228 | 34 | } |
229 | 34 | } |
230 | | |
231 | 0 | std::string AdvFlagsToString(const std::optional<AdvFlags>& flags_opt) { |
232 | 0 | std::string result = "Flags: {"; |
233 | |
|
234 | 0 | if (!flags_opt.has_value()) { |
235 | 0 | return result += "} "; |
236 | 0 | } |
237 | | |
238 | 0 | const AdvFlags& flags = flags_opt.value(); |
239 | |
|
240 | 0 | if (flags & kLELimitedDiscoverableMode) { |
241 | 0 | result += " LE Limited Discoverable Mode,"; |
242 | 0 | } |
243 | 0 | if (flags & kLEGeneralDiscoverableMode) { |
244 | 0 | result += " LE General Discoverable Mode,"; |
245 | 0 | } |
246 | 0 | if (flags & kBREDRNotSupported) { |
247 | 0 | result += " BR/EDR Not Supported,"; |
248 | 0 | } |
249 | 0 | if (flags & kSimultaneousLEAndBREDRController) { |
250 | 0 | result += " Simultaneous LE And BR/EDR Controller,"; |
251 | 0 | } |
252 | 0 | if (flags & kSimultaneousLEAndBREDRHost) { |
253 | 0 | result += " Simultaneous LE And BR/EDR Host,"; |
254 | 0 | } |
255 | 0 | return result += " }, "; |
256 | 0 | } |
257 | | |
258 | 0 | std::string AdvertisingData::ToString() const { |
259 | 0 | std::string result = "Advertising Data { "; |
260 | |
|
261 | 0 | if (local_name_) { |
262 | 0 | bt_lib_cpp_string::StringAppendf( |
263 | 0 | &result, |
264 | 0 | "%s Name: %s, ", |
265 | 0 | (local_name_->is_complete ? "Complete" : "Short"), |
266 | 0 | local_name_->name.c_str()); |
267 | 0 | } |
268 | |
|
269 | 0 | if (tx_power_) { |
270 | 0 | bt_lib_cpp_string::StringAppendf(&result, "TX Power: %hhd, ", *tx_power_); |
271 | 0 | } |
272 | |
|
273 | 0 | if (appearance_) { |
274 | 0 | bt_lib_cpp_string::StringAppendf( |
275 | 0 | &result, "Appearance: 0x%04x, ", *appearance_); |
276 | 0 | } |
277 | |
|
278 | 0 | if (!uris_.empty()) { |
279 | 0 | result += "URIs: { "; |
280 | 0 | for (const auto& uri : uris_) { |
281 | 0 | bt_lib_cpp_string::StringAppendf(&result, "%s, ", uri.c_str()); |
282 | 0 | } |
283 | 0 | result += "}, "; |
284 | 0 | } |
285 | |
|
286 | 0 | if (flags_.has_value()) { |
287 | 0 | result += AdvFlagsToString(flags_); |
288 | 0 | } |
289 | |
|
290 | 0 | bool has_service_uuids = false; |
291 | 0 | for (const auto& [_, bounded_uuids] : service_uuids_) { |
292 | 0 | if (!bounded_uuids.set().empty()) { |
293 | 0 | has_service_uuids = true; |
294 | 0 | break; |
295 | 0 | } |
296 | 0 | } |
297 | |
|
298 | 0 | if (has_service_uuids) { |
299 | 0 | result += "Service UUIDs: { "; |
300 | 0 | for (const auto& [_, bounded_uuids] : service_uuids_) { |
301 | 0 | for (const auto& uuid : bounded_uuids.set()) { |
302 | 0 | bt_lib_cpp_string::StringAppendf(&result, "%s, ", bt_str(uuid)); |
303 | 0 | } |
304 | 0 | } |
305 | 0 | result += "}, "; |
306 | 0 | } |
307 | |
|
308 | 0 | if (!service_data_.empty()) { |
309 | 0 | result += "Service Data: { "; |
310 | 0 | for (const auto& [uuid, data_buffer] : service_data_) { |
311 | 0 | bt_lib_cpp_string::StringAppendf( |
312 | 0 | &result, |
313 | 0 | "{ UUID:%s, Data: {%s} }, ", |
314 | 0 | bt_str(uuid), |
315 | 0 | data_buffer.ToString(/*as_hex*/ true).c_str()); |
316 | 0 | } |
317 | 0 | result += "}, "; |
318 | 0 | } |
319 | |
|
320 | 0 | bool has_solicitation_uuids = false; |
321 | 0 | for (const auto& [_, bounded_uuids] : solicitation_uuids_) { |
322 | 0 | if (!bounded_uuids.set().empty()) { |
323 | 0 | has_solicitation_uuids = true; |
324 | 0 | break; |
325 | 0 | } |
326 | 0 | } |
327 | |
|
328 | 0 | if (has_solicitation_uuids) { |
329 | 0 | result += "Solicitation UUIDs: { "; |
330 | 0 | for (const auto& [_, bounded_uuids] : solicitation_uuids_) { |
331 | 0 | for (const auto& uuid : bounded_uuids.set()) { |
332 | 0 | bt_lib_cpp_string::StringAppendf(&result, "%s, ", bt_str(uuid)); |
333 | 0 | } |
334 | 0 | } |
335 | 0 | result += "}, "; |
336 | 0 | } |
337 | |
|
338 | 0 | if (!manufacturer_data_.empty()) { |
339 | 0 | result += "Manufacturer Data: { "; |
340 | 0 | for (const auto& [company_id, data_buffer] : manufacturer_data_) { |
341 | 0 | bt_lib_cpp_string::StringAppendf( |
342 | 0 | &result, |
343 | 0 | "{ Company ID: 0x%04x, Data: {%s} }, ", |
344 | 0 | company_id, |
345 | 0 | data_buffer.ToString(/*as_hex*/ true).c_str()); |
346 | 0 | } |
347 | 0 | result += "}, "; |
348 | 0 | } |
349 | |
|
350 | 0 | if (resolvable_set_identifier_.has_value()) { |
351 | 0 | result += "Resolvable Set Idenfidier: { "; |
352 | 0 | BufferView view(resolvable_set_identifier_->data(), |
353 | 0 | resolvable_set_identifier_->size()); |
354 | 0 | result += view.ToString(/*as_hex=*/true); |
355 | 0 | result += "}, "; |
356 | 0 | } |
357 | |
|
358 | 0 | if (broadcast_name_) { |
359 | 0 | bt_lib_cpp_string::StringAppendf( |
360 | 0 | &result, "Broadcast Name: %s, ", broadcast_name_->c_str()); |
361 | 0 | } |
362 | 0 | result += "}"; |
363 | 0 | return result; |
364 | 0 | } |
365 | | |
366 | | AdvertisingData::ParseResult AdvertisingData::FromBytes( |
367 | 4.48k | const ByteBuffer& data) { |
368 | 4.48k | if (data.size() == 0) { |
369 | 4 | return fit::error(ParseError::kMissing); |
370 | 4 | } |
371 | 4.48k | SupplementDataReader reader(data); |
372 | 4.48k | if (!reader.is_valid()) { |
373 | 25 | return fit::error(ParseError::kInvalidTlvFormat); |
374 | 25 | } |
375 | | |
376 | 4.45k | AdvertisingData out_ad; |
377 | 4.45k | DataType type; |
378 | 4.45k | BufferView field; |
379 | 164k | while (reader.GetNextField(&type, &field)) { |
380 | | // While parsing through the advertising data fields, we do not need to |
381 | | // validate that per-field sizes do not overflow a uint8_t because they, by |
382 | | // construction, are obtained from a uint8_t. |
383 | 159k | PW_DCHECK(field.size() <= std::numeric_limits<uint8_t>::max()); |
384 | 159k | PW_MODIFY_DIAGNOSTICS_PUSH(); |
385 | 159k | PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum"); |
386 | 159k | switch (type) { |
387 | 221 | case DataType::kTxPowerLevel: { |
388 | 221 | if (field.size() != kTxPowerLevelSize) { |
389 | 15 | return fit::error(ParseError::kTxPowerLevelMalformed); |
390 | 15 | } |
391 | | |
392 | 206 | out_ad.SetTxPower(static_cast<int8_t>(field[0])); |
393 | 206 | break; |
394 | 221 | } |
395 | 2.24k | case DataType::kShortenedLocalName: { |
396 | 2.24k | if (field.ToString().size() > kMaxNameLength) { |
397 | 2 | return fit::error(ParseError::kLocalNameTooLong); |
398 | 2 | } |
399 | | |
400 | 2.24k | (void)out_ad.SetLocalName(field.ToString(), /*is_complete=*/false); |
401 | 2.24k | break; |
402 | 2.24k | } |
403 | 18.4k | case DataType::kCompleteLocalName: { |
404 | 18.4k | if (field.ToString().size() > kMaxNameLength) { |
405 | 1 | return fit::error(ParseError::kLocalNameTooLong); |
406 | 1 | } |
407 | | |
408 | 18.4k | (void)out_ad.SetLocalName(field.ToString(), /*is_complete=*/true); |
409 | 18.4k | break; |
410 | 18.4k | } |
411 | 886 | case DataType::kIncomplete16BitServiceUuids: |
412 | 1.99k | case DataType::kComplete16BitServiceUuids: |
413 | 3.55k | case DataType::kIncomplete32BitServiceUuids: |
414 | 11.2k | case DataType::kComplete32BitServiceUuids: |
415 | 12.9k | case DataType::kIncomplete128BitServiceUuids: |
416 | 13.9k | case DataType::kComplete128BitServiceUuids: { |
417 | | // AddServiceUuid fails when the number of N bit UUIDs exceed the |
418 | | // kMaxNBitUuids bounds. These bounds are based on the number of UUIDs |
419 | | // that fit in the wire (byte) representation of an AdvertisingData, so |
420 | | // for valid AdvertisingData packets, the number of N bit service UUIDs |
421 | | // cannot exceed the bounds limits. However, because invalid packets may |
422 | | // provide multiple DataType fields for the same UUID (not allowed by |
423 | | // CSS v9 Part A 1.1.1), this limit may be exceeded, in which case we |
424 | | // reject the packet. |
425 | 13.9k | if (!ParseUuids( |
426 | 13.9k | field, |
427 | 13.9k | SizeForType(type), |
428 | 13.9k | fit::bind_member<&AdvertisingData::AddServiceUuid>(&out_ad))) { |
429 | 239 | return fit::error(ParseError::kUuidsMalformed); |
430 | 239 | } |
431 | 13.7k | break; |
432 | 13.9k | } |
433 | 13.7k | case DataType::kSolicitationUuid16Bit: |
434 | 655 | case DataType::kSolicitationUuid32Bit: |
435 | 944 | case DataType::kSolicitationUuid128Bit: { |
436 | 944 | if (!ParseUuids(field, |
437 | 944 | SizeForType(type), |
438 | 944 | fit::bind_member<&AdvertisingData::AddSolicitationUuid>( |
439 | 944 | &out_ad))) { |
440 | 12 | return fit::error(ParseError::kUuidsMalformed); |
441 | 12 | } |
442 | 932 | break; |
443 | 944 | } |
444 | 12.0k | case DataType::kManufacturerSpecificData: { |
445 | 12.0k | if (field.size() < kManufacturerSpecificDataSizeMin) { |
446 | 12 | return fit::error(ParseError::kManufacturerSpecificDataTooSmall); |
447 | 12 | } |
448 | | |
449 | 12.0k | uint16_t id = static_cast<uint16_t>(pw::bytes::ConvertOrderFrom( |
450 | 12.0k | cpp20::endian::little, |
451 | 12.0k | *reinterpret_cast<const uint16_t*>(field.data()))); |
452 | 12.0k | const BufferView manuf_data(field.data() + kManufacturerIdSize, |
453 | 12.0k | field.size() - kManufacturerIdSize); |
454 | | |
455 | 12.0k | PW_CHECK(out_ad.SetManufacturerData(id, manuf_data)); |
456 | 12.0k | break; |
457 | 12.0k | } |
458 | 12.0k | case DataType::kServiceData16Bit: |
459 | 9.39k | case DataType::kServiceData32Bit: |
460 | 36.1k | case DataType::kServiceData128Bit: { |
461 | 36.1k | UUID uuid; |
462 | 36.1k | size_t uuid_size = SizeForType(type); |
463 | 36.1k | if (field.size() < uuid_size) { |
464 | 28 | return fit::error(ParseError::kServiceDataTooSmall); |
465 | 28 | } |
466 | 36.0k | const BufferView uuid_bytes(field.data(), uuid_size); |
467 | 36.0k | if (!UUID::FromBytes(uuid_bytes, &uuid)) { |
468 | 0 | return fit::error(ParseError::kServiceDataUuidMalformed); |
469 | 0 | } |
470 | 36.0k | const BufferView service_data(field.data() + uuid_size, |
471 | 36.0k | field.size() - uuid_size); |
472 | 36.0k | PW_CHECK(out_ad.SetServiceData(uuid, service_data)); |
473 | 36.0k | break; |
474 | 36.0k | } |
475 | 36.0k | case DataType::kAppearance: { |
476 | | // TODO(armansito): Peer should have a function to return the |
477 | | // device appearance, as it can be obtained either from advertising data |
478 | | // or via GATT. |
479 | 593 | if (field.size() != kAppearanceSize) { |
480 | 16 | return fit::error(ParseError::kAppearanceMalformed); |
481 | 16 | } |
482 | | |
483 | 577 | out_ad.SetAppearance(pw::bytes::ConvertOrderFrom(cpp20::endian::little, |
484 | 577 | field.To<uint16_t>())); |
485 | 577 | break; |
486 | 593 | } |
487 | 48.0k | case DataType::kURI: { |
488 | | // Assertion is safe as AddUri only fails when field size > uint8_t, |
489 | | // which is impossible. |
490 | 48.0k | PW_CHECK(out_ad.AddUri(DecodeUri(field.ToString()))); |
491 | 48.0k | break; |
492 | 48.0k | } |
493 | 48.0k | case DataType::kFlags: { |
494 | | // Flags field may be zero or more octets long but we only store the |
495 | | // first octet. |
496 | 7.92k | if (field.size() > 0) { |
497 | 4.48k | out_ad.SetFlags(field[0]); |
498 | 4.48k | } else { |
499 | 3.43k | out_ad.SetFlags(0); |
500 | 3.43k | } |
501 | 7.92k | break; |
502 | 48.0k | } |
503 | 413 | case DataType::kResolvableSetIdentifier: { |
504 | 413 | if (field.size() != kResolvableSetIdentifierSize) { |
505 | 13 | return fit::error(ParseError::kResolvableSetIdentifierSize); |
506 | 13 | } |
507 | 400 | std::array<uint8_t, kResolvableSetIdentifierSize> ident{ |
508 | 400 | field[0], field[1], field[2], field[3], field[4], field[5]}; |
509 | 400 | out_ad.SetResolvableSetIdentifier(ident); |
510 | 400 | break; |
511 | 413 | } |
512 | 821 | case DataType::kBroadcastName: { |
513 | 821 | if (field.size() < kMinBroadcastNameBytes) { |
514 | 10 | return fit::error(ParseError::kBroadcastNameTooShort); |
515 | 10 | } |
516 | 811 | if (field.size() > kMaxBroadcastNameBytes) { |
517 | 2 | return fit::error(ParseError::kBroadcastNameTooLong); |
518 | 2 | } |
519 | 809 | std::string name = field.ToString(); |
520 | 809 | out_ad.SetBroadcastName(name); |
521 | 809 | break; |
522 | 811 | } |
523 | 18.1k | default: |
524 | 18.1k | bt_log(DEBUG, |
525 | 18.1k | "gap", |
526 | 18.1k | "ignored advertising field (type %#.2x)", |
527 | 18.1k | static_cast<unsigned int>(type)); |
528 | 18.1k | break; |
529 | 159k | } |
530 | 159k | PW_MODIFY_DIAGNOSTICS_POP(); |
531 | 159k | } |
532 | | |
533 | 4.10k | return fit::ok(std::move(out_ad)); |
534 | 4.45k | } |
535 | | |
536 | 0 | void AdvertisingData::Copy(AdvertisingData* out) const { |
537 | 0 | *out = AdvertisingData(); |
538 | |
|
539 | 0 | if (local_name_) { |
540 | 0 | PW_CHECK(out->SetLocalName(*local_name_)); |
541 | 0 | } |
542 | | |
543 | 0 | if (tx_power_) { |
544 | 0 | out->SetTxPower(*tx_power_); |
545 | 0 | } |
546 | |
|
547 | 0 | if (appearance_) { |
548 | 0 | out->SetAppearance(*appearance_); |
549 | 0 | } |
550 | |
|
551 | 0 | out->service_uuids_ = service_uuids_; |
552 | 0 | out->solicitation_uuids_ = solicitation_uuids_; |
553 | 0 | out->resolvable_set_identifier_ = resolvable_set_identifier_; |
554 | 0 | out->broadcast_name_ = broadcast_name_; |
555 | |
|
556 | 0 | for (const auto& it : manufacturer_data_) { |
557 | 0 | PW_CHECK(out->SetManufacturerData(it.first, it.second.view())); |
558 | 0 | } |
559 | | |
560 | 0 | for (const auto& it : service_data_) { |
561 | 0 | PW_CHECK(out->SetServiceData(it.first, it.second.view())); |
562 | 0 | } |
563 | | |
564 | 0 | for (const auto& it : uris_) { |
565 | 0 | PW_CHECK(out->AddUri(it), "Copying invalid AD with too-long URI"); |
566 | 0 | } |
567 | 0 | } |
568 | | |
569 | 27.4k | [[nodiscard]] bool AdvertisingData::AddServiceUuid(const UUID& uuid) { |
570 | 27.4k | auto iter = service_uuids_.find(uuid.CompactSize()); |
571 | 27.4k | PW_CHECK(iter != service_uuids_.end()); |
572 | 27.4k | BoundedUuids& uuids = iter->second; |
573 | 27.4k | return uuids.AddUuid(uuid); |
574 | 27.4k | } |
575 | | |
576 | 0 | std::unordered_set<UUID> AdvertisingData::service_uuids() const { |
577 | 0 | std::unordered_set<UUID> out; |
578 | 0 | for (auto& [_elemsize, uuids] : service_uuids_) { |
579 | 0 | out.insert(uuids.set().begin(), uuids.set().end()); |
580 | 0 | } |
581 | 0 | return out; |
582 | 0 | } |
583 | | |
584 | | [[nodiscard]] bool AdvertisingData::SetServiceData(const UUID& uuid, |
585 | 36.0k | const ByteBuffer& data) { |
586 | 36.0k | size_t encoded_size = EncodedServiceDataSize(uuid, data.view()); |
587 | 36.0k | if (encoded_size > kMaxEncodedServiceDataLength) { |
588 | 0 | bt_log(WARN, |
589 | 0 | "gap-le", |
590 | 0 | "SetServiceData for UUID %s failed: (UUID+data) size %zu > maximum " |
591 | 0 | "allowed size %du", |
592 | 0 | bt_str(uuid), |
593 | 0 | encoded_size, |
594 | 0 | kMaxEncodedServiceDataLength); |
595 | 0 | return false; |
596 | 0 | } |
597 | 36.0k | service_data_[uuid] = DynamicByteBuffer(data); |
598 | 36.0k | return true; |
599 | 36.0k | } |
600 | | |
601 | 0 | std::unordered_set<UUID> AdvertisingData::service_data_uuids() const { |
602 | 0 | std::unordered_set<UUID> uuids; |
603 | 0 | for (const auto& it : service_data_) { |
604 | 0 | uuids.emplace(it.first); |
605 | 0 | } |
606 | 0 | return uuids; |
607 | 0 | } |
608 | | |
609 | 0 | BufferView AdvertisingData::service_data(const UUID& uuid) const { |
610 | 0 | auto iter = service_data_.find(uuid); |
611 | 0 | if (iter == service_data_.end()) |
612 | 0 | return BufferView(); |
613 | 0 | return BufferView(iter->second); |
614 | 0 | } |
615 | | |
616 | 2.93k | [[nodiscard]] bool AdvertisingData::AddSolicitationUuid(const UUID& uuid) { |
617 | 2.93k | auto iter = solicitation_uuids_.find(uuid.CompactSize()); |
618 | 2.93k | PW_CHECK(iter != solicitation_uuids_.end()); |
619 | 2.93k | BoundedUuids& uuids = iter->second; |
620 | 2.93k | return uuids.AddUuid(uuid); |
621 | 2.93k | } |
622 | | |
623 | 0 | std::unordered_set<UUID> AdvertisingData::solicitation_uuids() const { |
624 | 0 | std::unordered_set<UUID> out; |
625 | 0 | for (auto& [_elemsize, uuids] : solicitation_uuids_) { |
626 | 0 | out.insert(uuids.set().begin(), uuids.set().end()); |
627 | 0 | } |
628 | 0 | return out; |
629 | 0 | } |
630 | | |
631 | | [[nodiscard]] bool AdvertisingData::SetManufacturerData( |
632 | 12.0k | const uint16_t company_id, const BufferView& data) { |
633 | 12.0k | size_t field_size = data.size(); |
634 | 12.0k | if (field_size > kMaxManufacturerDataLength) { |
635 | 0 | bt_log(WARN, |
636 | 0 | "gap-le", |
637 | 0 | "SetManufacturerData for company id %#.4x failed: (UUID+data) size " |
638 | 0 | "%zu > maximum allowed " |
639 | 0 | "size %hhu", |
640 | 0 | company_id, |
641 | 0 | field_size, |
642 | 0 | kMaxManufacturerDataLength); |
643 | 0 | return false; |
644 | 0 | } |
645 | 12.0k | manufacturer_data_[company_id] = DynamicByteBuffer(data); |
646 | 12.0k | return true; |
647 | 12.0k | } |
648 | | |
649 | 0 | std::unordered_set<uint16_t> AdvertisingData::manufacturer_data_ids() const { |
650 | 0 | std::unordered_set<uint16_t> manuf_ids; |
651 | 0 | for (const auto& it : manufacturer_data_) { |
652 | 0 | manuf_ids.emplace(it.first); |
653 | 0 | } |
654 | 0 | return manuf_ids; |
655 | 0 | } |
656 | | |
657 | 0 | BufferView AdvertisingData::manufacturer_data(const uint16_t company_id) const { |
658 | 0 | auto iter = manufacturer_data_.find(company_id); |
659 | 0 | if (iter == manufacturer_data_.end()) |
660 | 0 | return BufferView(); |
661 | 0 | return BufferView(iter->second); |
662 | 0 | } |
663 | | |
664 | 206 | void AdvertisingData::SetTxPower(int8_t dbm) { tx_power_ = dbm; } |
665 | | |
666 | 0 | std::optional<int8_t> AdvertisingData::tx_power() const { return tx_power_; } |
667 | | |
668 | 20.7k | bool AdvertisingData::SetLocalName(const LocalName& local_name) { |
669 | 20.7k | if (local_name.name.size() > kMaxNameLength) { |
670 | 0 | return false; |
671 | 0 | } |
672 | 20.7k | if (local_name_.has_value() && local_name_->is_complete && |
673 | 20.7k | !local_name.is_complete) { |
674 | 1.44k | return false; |
675 | 1.44k | } |
676 | 19.2k | local_name_ = local_name; |
677 | 19.2k | return true; |
678 | 20.7k | } |
679 | | |
680 | 516 | std::optional<AdvertisingData::LocalName> AdvertisingData::local_name() const { |
681 | 516 | return local_name_; |
682 | 516 | } |
683 | | |
684 | | void AdvertisingData::SetResolvableSetIdentifier( |
685 | 400 | std::array<uint8_t, kResolvableSetIdentifierSize> identifier) { |
686 | 400 | resolvable_set_identifier_ = identifier; |
687 | 400 | } |
688 | | |
689 | | const std::optional<std::array<uint8_t, kResolvableSetIdentifierSize>>& |
690 | 0 | AdvertisingData::resolvable_set_identifier() const { |
691 | 0 | return resolvable_set_identifier_; |
692 | 0 | } |
693 | | |
694 | 809 | void AdvertisingData::SetBroadcastName(const std::string& name) { |
695 | 809 | broadcast_name_ = name; |
696 | 809 | } |
697 | | |
698 | 0 | const std::optional<std::string>& AdvertisingData::broadcast_name() const { |
699 | 0 | return broadcast_name_; |
700 | 0 | } |
701 | | |
702 | 48.0k | [[nodiscard]] bool AdvertisingData::AddUri(const std::string& uri) { |
703 | 48.0k | if (EncodeUri(uri).size() > kMaxEncodedUriLength) { |
704 | 0 | bt_log(WARN, |
705 | 0 | "gap-le", |
706 | 0 | "not inserting uri %s as it exceeds the max URI size for AD", |
707 | 0 | uri.c_str()); |
708 | 0 | return false; |
709 | 0 | } |
710 | 48.0k | if (uri.empty()) { |
711 | 7.27k | bt_log(WARN, "gap-le", "skipping insertion of empty uri to AD"); |
712 | 7.27k | return true; |
713 | 7.27k | } |
714 | 40.7k | uris_.insert(uri); |
715 | 40.7k | return true; |
716 | 48.0k | } |
717 | | |
718 | 0 | const std::unordered_set<std::string>& AdvertisingData::uris() const { |
719 | 0 | return uris_; |
720 | 0 | } |
721 | | |
722 | 577 | void AdvertisingData::SetAppearance(uint16_t appearance) { |
723 | 577 | appearance_ = appearance; |
724 | 577 | } |
725 | | |
726 | 0 | std::optional<uint16_t> AdvertisingData::appearance() const { |
727 | 0 | return appearance_; |
728 | 0 | } |
729 | | |
730 | 7.92k | void AdvertisingData::SetFlags(AdvFlags flags) { flags_ = flags; } |
731 | | |
732 | 0 | std::optional<AdvFlags> AdvertisingData::flags() const { return flags_; } |
733 | | |
734 | 3.59k | size_t AdvertisingData::CalculateBlockSize(bool include_flags) const { |
735 | 3.59k | size_t len = 0; |
736 | 3.59k | if (include_flags) { |
737 | 1.50k | len += kTLVFlagsSize; |
738 | 1.50k | } |
739 | | |
740 | 3.59k | if (tx_power_) { |
741 | 13 | len += kTLVTxPowerLevelSize; |
742 | 13 | } |
743 | | |
744 | 3.59k | if (appearance_) { |
745 | 9 | len += kTLVAppearanceSize; |
746 | 9 | } |
747 | | |
748 | 3.59k | if (local_name_) { |
749 | 159 | len += 2 + local_name_->name.size(); |
750 | 159 | } |
751 | | |
752 | 3.59k | for (const auto& manuf_pair : manufacturer_data_) { |
753 | 2.95k | len += 2 + 2 + manuf_pair.second.size(); |
754 | 2.95k | } |
755 | | |
756 | 9.70k | for (const auto& service_data_pair : service_data_) { |
757 | 9.70k | len += 2 + service_data_pair.first.CompactSize() + |
758 | 9.70k | service_data_pair.second.size(); |
759 | 9.70k | } |
760 | | |
761 | 15.2k | for (const auto& uri : uris_) { |
762 | 15.2k | len += 2 + EncodeUri(uri).size(); |
763 | 15.2k | } |
764 | | |
765 | 10.7k | for (const auto& [uuid_size, bounded_uuids] : service_uuids_) { |
766 | 10.7k | if (bounded_uuids.set().empty()) { |
767 | 9.90k | continue; |
768 | 9.90k | } |
769 | 870 | len += 2; // 1 byte for # of UUIDs and 1 for UUID type |
770 | 870 | len += uuid_size * bounded_uuids.set().size(); |
771 | 870 | } |
772 | | |
773 | 10.7k | for (const auto& [uuid_size, bounded_uuids] : solicitation_uuids_) { |
774 | 10.7k | if (bounded_uuids.set().empty()) { |
775 | 10.6k | continue; |
776 | 10.6k | } |
777 | 158 | len += 2; // 1 byte for # of UUIDs and 1 for UUID type |
778 | 158 | len += uuid_size * bounded_uuids.set().size(); |
779 | 158 | } |
780 | | |
781 | 3.59k | if (resolvable_set_identifier_.has_value()) { |
782 | 13 | len += kTLVResolvableSetIdentifierSize; |
783 | 13 | } |
784 | | |
785 | 3.59k | if (broadcast_name_) { |
786 | 56 | len += 2 + broadcast_name_->size(); |
787 | 56 | } |
788 | | |
789 | 3.59k | return len; |
790 | 3.59k | } |
791 | | |
792 | | bool AdvertisingData::WriteBlock(MutableByteBuffer* buffer, |
793 | 3.59k | std::optional<AdvFlags> flags) const { |
794 | 3.59k | PW_DCHECK(buffer); |
795 | | |
796 | 3.59k | size_t min_buf_size = CalculateBlockSize(flags.has_value()); |
797 | 3.59k | if (buffer->size() < min_buf_size) { |
798 | 770 | return false; |
799 | 770 | } |
800 | | |
801 | 2.82k | size_t pos = 0; |
802 | 2.82k | if (flags) { |
803 | 1.26k | (*buffer)[pos++] = |
804 | 1.26k | kTLVFlagsSize - 1; // size variable includes current field, subtract 1 |
805 | 1.26k | (*buffer)[pos++] = static_cast<uint8_t>(DataType::kFlags); |
806 | 1.26k | (*buffer)[pos++] = static_cast<uint8_t>(flags.value()); |
807 | 1.26k | } |
808 | | |
809 | 2.82k | if (tx_power_) { |
810 | 11 | (*buffer)[pos++] = kTLVTxPowerLevelSize - |
811 | 11 | 1; // size variable includes current field, subtract 1 |
812 | 11 | (*buffer)[pos++] = static_cast<uint8_t>(DataType::kTxPowerLevel); |
813 | 11 | (*buffer)[pos++] = static_cast<uint8_t>(tx_power_.value()); |
814 | 11 | } |
815 | | |
816 | 2.82k | if (appearance_) { |
817 | 8 | (*buffer)[pos++] = kTLVAppearanceSize - |
818 | 8 | 1; // size variable includes current field, subtract 1 |
819 | 8 | (*buffer)[pos++] = static_cast<uint8_t>(DataType::kAppearance); |
820 | 8 | pos += BufferWrite(buffer, pos, appearance_.value()); |
821 | 8 | } |
822 | | |
823 | 2.82k | if (local_name_) { |
824 | 118 | PW_CHECK(local_name_->name.size() <= kMaxNameLength); |
825 | 118 | (*buffer)[pos++] = |
826 | 118 | static_cast<uint8_t>(local_name_->name.size()) + 1; // 1 for null char |
827 | 118 | (*buffer)[pos++] = static_cast<uint8_t>(DataType::kCompleteLocalName); |
828 | 118 | buffer->Write(reinterpret_cast<const uint8_t*>(local_name_->name.c_str()), |
829 | 118 | local_name_->name.size(), |
830 | 118 | pos); |
831 | 118 | pos += local_name_->name.size(); |
832 | 118 | } |
833 | | |
834 | 2.82k | for (const auto& manuf_pair : manufacturer_data_) { |
835 | 2.17k | size_t data_size = manuf_pair.second.size(); |
836 | 2.17k | PW_CHECK(data_size <= kMaxManufacturerDataLength); |
837 | 2.17k | (*buffer)[pos++] = |
838 | 2.17k | 1 + 2 + |
839 | 2.17k | static_cast<uint8_t>(data_size); // 1 for type, 2 for Manuf. Code |
840 | 2.17k | (*buffer)[pos++] = |
841 | 2.17k | static_cast<uint8_t>(DataType::kManufacturerSpecificData); |
842 | 2.17k | pos += BufferWrite(buffer, pos, manuf_pair.first); |
843 | 2.17k | buffer->Write(manuf_pair.second, pos); |
844 | 2.17k | pos += data_size; |
845 | 2.17k | } |
846 | | |
847 | 4.97k | for (const auto& service_data_pair : service_data_) { |
848 | 4.97k | UUID uuid = service_data_pair.first; |
849 | 4.97k | size_t encoded_service_data_size = |
850 | 4.97k | EncodedServiceDataSize(uuid, service_data_pair.second.view()); |
851 | 4.97k | PW_CHECK(encoded_service_data_size <= kMaxEncodedServiceDataLength); |
852 | 4.97k | (*buffer)[pos++] = |
853 | 4.97k | 1 + static_cast<uint8_t>(encoded_service_data_size); // 1 for type |
854 | 4.97k | (*buffer)[pos++] = |
855 | 4.97k | static_cast<uint8_t>(ServiceDataTypeForUuidSize(uuid.CompactSize())); |
856 | 4.97k | auto target = buffer->mutable_view(pos); |
857 | 4.97k | pos += service_data_pair.first.ToBytes(&target); |
858 | 4.97k | buffer->Write(service_data_pair.second, pos); |
859 | 4.97k | pos += service_data_pair.second.size(); |
860 | 4.97k | } |
861 | | |
862 | 4.47k | for (const auto& uri : uris_) { |
863 | 4.47k | std::string s = EncodeUri(uri); |
864 | 4.47k | PW_CHECK(s.size() <= kMaxEncodedUriLength); |
865 | 4.47k | (*buffer)[pos++] = 1 + static_cast<uint8_t>(s.size()); // 1 for type |
866 | 4.47k | (*buffer)[pos++] = static_cast<uint8_t>(DataType::kURI); |
867 | 4.47k | buffer->Write(reinterpret_cast<const uint8_t*>(s.c_str()), s.length(), pos); |
868 | 4.47k | pos += s.size(); |
869 | 4.47k | } |
870 | | |
871 | 8.46k | for (const auto& [uuid_width, bounded_uuids] : service_uuids_) { |
872 | 8.46k | if (bounded_uuids.set().empty()) { |
873 | 7.93k | continue; |
874 | 7.93k | } |
875 | | |
876 | | // 1 for type |
877 | 529 | PW_CHECK(1 + uuid_width * bounded_uuids.set().size() <= |
878 | 529 | std::numeric_limits<uint8_t>::max()); |
879 | 529 | (*buffer)[pos++] = |
880 | 529 | 1 + uuid_width * static_cast<uint8_t>(bounded_uuids.set().size()); |
881 | 529 | (*buffer)[pos++] = static_cast<uint8_t>( |
882 | 529 | ServiceUuidTypeForUuidSize(uuid_width, /*complete=*/false)); |
883 | 4.32k | for (const auto& uuid : bounded_uuids.set()) { |
884 | 4.32k | PW_CHECK(uuid.CompactSize() == uuid_width, |
885 | 4.32k | "UUID: %s - Expected Width: %d", |
886 | 4.32k | bt_str(uuid), |
887 | 4.32k | uuid_width); |
888 | 4.32k | auto target = buffer->mutable_view(pos); |
889 | 4.32k | pos += uuid.ToBytes(&target); |
890 | 4.32k | } |
891 | 529 | } |
892 | | |
893 | 8.46k | for (const auto& [uuid_width, bounded_uuids] : solicitation_uuids_) { |
894 | 8.46k | if (bounded_uuids.set().empty()) { |
895 | 8.34k | continue; |
896 | 8.34k | } |
897 | | |
898 | | // 1 for type |
899 | 114 | PW_CHECK(1 + uuid_width * bounded_uuids.set().size() <= |
900 | 114 | std::numeric_limits<uint8_t>::max()); |
901 | 114 | (*buffer)[pos++] = |
902 | 114 | 1 + uuid_width * static_cast<uint8_t>(bounded_uuids.set().size()); |
903 | 114 | (*buffer)[pos++] = |
904 | 114 | static_cast<uint8_t>(SolicitationUuidTypeForUuidSize(uuid_width)); |
905 | 1.27k | for (const auto& uuid : bounded_uuids.set()) { |
906 | 1.27k | PW_CHECK(uuid.CompactSize() == uuid_width, |
907 | 1.27k | "UUID: %s - Expected Width: %d", |
908 | 1.27k | bt_str(uuid), |
909 | 1.27k | uuid_width); |
910 | 1.27k | auto target = buffer->mutable_view(pos); |
911 | 1.27k | pos += uuid.ToBytes(&target); |
912 | 1.27k | } |
913 | 114 | } |
914 | | |
915 | 2.82k | if (resolvable_set_identifier_) { |
916 | 11 | (*buffer)[pos++] = |
917 | 11 | 1 + |
918 | 11 | static_cast<uint8_t>(resolvable_set_identifier_->size()); // 1 for type |
919 | 11 | (*buffer)[pos++] = static_cast<uint8_t>(DataType::kResolvableSetIdentifier); |
920 | 11 | buffer->Write(resolvable_set_identifier_->data(), |
921 | 11 | resolvable_set_identifier_->size(), |
922 | 11 | pos); |
923 | 11 | pos += resolvable_set_identifier_->size(); |
924 | 11 | } |
925 | | |
926 | 2.82k | if (broadcast_name_) { |
927 | 36 | (*buffer)[pos++] = |
928 | 36 | 1 + static_cast<uint8_t>(broadcast_name_->size()); // 1 for type |
929 | 36 | (*buffer)[pos++] = static_cast<uint8_t>(DataType::kBroadcastName); |
930 | 36 | buffer->Write(reinterpret_cast<const uint8_t*>(broadcast_name_->c_str()), |
931 | 36 | broadcast_name_->size(), |
932 | 36 | pos); |
933 | 36 | pos += broadcast_name_->size(); |
934 | 36 | } |
935 | | |
936 | 2.82k | return true; |
937 | 2.82k | } |
938 | | |
939 | 0 | bool AdvertisingData::operator==(const AdvertisingData& other) const { |
940 | 0 | if ((local_name_ != other.local_name_) || (tx_power_ != other.tx_power_) || |
941 | 0 | (appearance_ != other.appearance_) || |
942 | 0 | (service_uuids_ != other.service_uuids_) || |
943 | 0 | (solicitation_uuids_ != other.solicitation_uuids_) || |
944 | 0 | (uris_ != other.uris_) || (flags_ != other.flags_) || |
945 | 0 | (resolvable_set_identifier_ != other.resolvable_set_identifier_) || |
946 | 0 | (broadcast_name_ != other.broadcast_name_)) { |
947 | 0 | return false; |
948 | 0 | } |
949 | | |
950 | 0 | if (manufacturer_data_.size() != other.manufacturer_data_.size()) { |
951 | 0 | return false; |
952 | 0 | } |
953 | | |
954 | 0 | for (const auto& it : manufacturer_data_) { |
955 | 0 | auto that = other.manufacturer_data_.find(it.first); |
956 | 0 | if (that == other.manufacturer_data_.end()) { |
957 | 0 | return false; |
958 | 0 | } |
959 | 0 | size_t bytes = it.second.size(); |
960 | 0 | if (bytes != that->second.size()) { |
961 | 0 | return false; |
962 | 0 | } |
963 | 0 | if (std::memcmp(it.second.data(), that->second.data(), bytes) != 0) { |
964 | 0 | return false; |
965 | 0 | } |
966 | 0 | } |
967 | | |
968 | 0 | if (service_data_.size() != other.service_data_.size()) { |
969 | 0 | return false; |
970 | 0 | } |
971 | | |
972 | 0 | for (const auto& it : service_data_) { |
973 | 0 | auto that = other.service_data_.find(it.first); |
974 | 0 | if (that == other.service_data_.end()) { |
975 | 0 | return false; |
976 | 0 | } |
977 | 0 | size_t bytes = it.second.size(); |
978 | 0 | if (bytes != that->second.size()) { |
979 | 0 | return false; |
980 | 0 | } |
981 | 0 | if (std::memcmp(it.second.data(), that->second.data(), bytes) != 0) { |
982 | 0 | return false; |
983 | 0 | } |
984 | 0 | } |
985 | | |
986 | 0 | return true; |
987 | 0 | } |
988 | | |
989 | 0 | bool AdvertisingData::operator!=(const AdvertisingData& other) const { |
990 | 0 | return !(*this == other); |
991 | 0 | } |
992 | | |
993 | 30.3k | bool AdvertisingData::BoundedUuids::AddUuid(UUID uuid) { |
994 | 30.3k | PW_CHECK(set_.size() <= bound_); |
995 | 30.3k | if (set_.size() < bound_) { |
996 | 28.6k | if (!set_.insert(uuid).second) { |
997 | 14.4k | bt_log(INFO, |
998 | 14.4k | "gap-le", |
999 | 14.4k | "Skipping addition of duplicate UUID %s to AD", |
1000 | 14.4k | bt_str(uuid)); |
1001 | 14.4k | } |
1002 | 28.6k | return true; |
1003 | 28.6k | } |
1004 | 1.72k | if (set_.find(uuid) != set_.end()) { |
1005 | 1.51k | bt_log(INFO, |
1006 | 1.51k | "gap-le", |
1007 | 1.51k | "Skipping addition of duplicate UUID %s to AD", |
1008 | 1.51k | bt_str(uuid)); |
1009 | 1.51k | return true; |
1010 | 1.51k | } |
1011 | 1.72k | bt_log(WARN, |
1012 | 206 | "gap-le", |
1013 | 206 | "Failed to add UUID %s to AD - no space left", |
1014 | 206 | bt_str(uuid)); |
1015 | 206 | return false; |
1016 | 1.72k | } |
1017 | | } // namespace bt |