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/sdp/service_record.h"
16
17 #include <pw_assert/check.h>
18 #include <pw_bytes/endian.h>
19
20 #include <iterator>
21 #include <set>
22 #include <vector>
23
24 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
25 #include "pw_bluetooth_sapphire/internal/host/sdp/sdp.h"
26
27 namespace bt::sdp {
28
29 namespace {
30
31 // Adds all UUIDs that it finds in |elem| to |out|, recursing through
32 // sequences and alternatives if necessary.
AddAllUUIDs(const DataElement & elem,std::unordered_set<UUID> * out)33 void AddAllUUIDs(const DataElement& elem, std::unordered_set<UUID>* out) {
34 DataElement::Type type = elem.type();
35 if (type == DataElement::Type::kUuid) {
36 out->emplace(*elem.Get<UUID>());
37 } else if (type == DataElement::Type::kSequence ||
38 type == DataElement::Type::kAlternative) {
39 const DataElement* it;
40 for (size_t idx = 0; nullptr != (it = elem.At(idx)); idx++) {
41 AddAllUUIDs(*it, out);
42 }
43 }
44 }
45
46 } // namespace
47
ServiceRecord()48 ServiceRecord::ServiceRecord() {
49 SetAttribute(kServiceId, DataElement(UUID::Generate()));
50 }
51
ServiceRecord(const ServiceRecord & other)52 ServiceRecord::ServiceRecord(const ServiceRecord& other) {
53 handle_ = other.handle_;
54 security_level_ = other.security_level_;
55
56 for (const auto& attribute : other.attributes_) {
57 attributes_.emplace(attribute.first, attribute.second.Clone());
58 }
59
60 for (const auto& protocol : other.addl_protocols_) {
61 addl_protocols_.emplace(protocol.first, protocol.second.Clone());
62 }
63 }
64
SetAttribute(AttributeId id,DataElement value)65 void ServiceRecord::SetAttribute(AttributeId id, DataElement value) {
66 attributes_.erase(id);
67 attributes_.emplace(id, std::move(value));
68 }
69
GetAttribute(AttributeId id) const70 const DataElement& ServiceRecord::GetAttribute(AttributeId id) const {
71 auto it = attributes_.find(id);
72 PW_DCHECK(it != attributes_.end(), "attribute %#.4x not set!", id);
73 return it->second;
74 }
75
HasAttribute(AttributeId id) const76 bool ServiceRecord::HasAttribute(AttributeId id) const {
77 return attributes_.count(id) == 1;
78 }
79
RemoveAttribute(AttributeId id)80 void ServiceRecord::RemoveAttribute(AttributeId id) { attributes_.erase(id); }
81
IsProtocolOnly() const82 bool ServiceRecord::IsProtocolOnly() const {
83 // Protocol-only services have exactly:
84 // - A UUID (generated by constructor)
85 // - A primary protocol descriptor
86 // - A service record handle (assigned by the SDP server)
87 if (attributes_.size() != 3) {
88 return false;
89 }
90 for (AttributeId x :
91 {kServiceRecordHandle, kProtocolDescriptorList, kServiceId}) {
92 if (attributes_.count(x) != 1) {
93 return false;
94 }
95 }
96 return true;
97 }
98
IsRegisterable() const99 bool ServiceRecord::IsRegisterable() const {
100 // Services must at least have a ServiceClassIDList (5.0, Vol 3, Part B, 5.1)
101 if (!HasAttribute(kServiceClassIdList)) {
102 bt_log(TRACE, "sdp", "record missing ServiceClass");
103 return false;
104 }
105 // Class ID list is a data element sequence in which each data element is
106 // a UUID representing the service classes that a given service record
107 // conforms to. (5.0, Vol 3, Part B, 5.1.2)
108 const DataElement& class_id_list = GetAttribute(kServiceClassIdList);
109 if (class_id_list.type() != DataElement::Type::kSequence) {
110 bt_log(TRACE, "sdp", "class ID list isn't a sequence");
111 return false;
112 }
113
114 size_t idx;
115 const DataElement* elem;
116 for (idx = 0; nullptr != (elem = class_id_list.At(idx)); idx++) {
117 if (elem->type() != DataElement::Type::kUuid) {
118 bt_log(TRACE, "sdp", "class ID list elements are not all UUIDs");
119 return false;
120 }
121 }
122
123 if (idx == 0) {
124 bt_log(TRACE, "sdp", "no elements in the Class ID list (need at least 1)");
125 return false;
126 }
127
128 if (!HasAttribute(kBrowseGroupList)) {
129 bt_log(TRACE, "sdp", "record isn't part of a browse group");
130 return false;
131 }
132
133 return true;
134 }
135
SetHandle(ServiceHandle handle)136 void ServiceRecord::SetHandle(ServiceHandle handle) {
137 handle_ = handle;
138 SetAttribute(kServiceRecordHandle, DataElement(uint32_t(handle_)));
139 }
140
GetAttributesInRange(AttributeId start,AttributeId end) const141 std::set<AttributeId> ServiceRecord::GetAttributesInRange(
142 AttributeId start, AttributeId end) const {
143 std::set<AttributeId> attrs;
144 if (start > end) {
145 return attrs;
146 }
147 for (auto it = attributes_.lower_bound(start);
148 it != attributes_.end() && (it->first <= end);
149 ++it) {
150 attrs.emplace(it->first);
151 }
152
153 return attrs;
154 }
155
FindUUID(const std::unordered_set<UUID> & uuids) const156 bool ServiceRecord::FindUUID(const std::unordered_set<UUID>& uuids) const {
157 if (uuids.size() == 0) {
158 return true;
159 }
160 // Gather all the UUIDs in the attributes
161 std::unordered_set<UUID> attribute_uuids;
162 for (const auto& it : attributes_) {
163 AddAllUUIDs(it.second, &attribute_uuids);
164 }
165 for (const auto& uuid : uuids) {
166 if (attribute_uuids.count(uuid) == 0) {
167 return false;
168 }
169 }
170 return true;
171 }
172
SetServiceClassUUIDs(const std::vector<UUID> & classes)173 void ServiceRecord::SetServiceClassUUIDs(const std::vector<UUID>& classes) {
174 std::vector<DataElement> class_uuids;
175 for (const auto& uuid : classes) {
176 class_uuids.emplace_back(DataElement(uuid));
177 }
178 DataElement class_id_list_val(std::move(class_uuids));
179 SetAttribute(kServiceClassIdList, std::move(class_id_list_val));
180 }
181
AddProtocolDescriptor(const ProtocolListId id,const UUID & uuid,DataElement params)182 void ServiceRecord::AddProtocolDescriptor(const ProtocolListId id,
183 const UUID& uuid,
184 DataElement params) {
185 std::vector<DataElement> seq;
186 if (id == kPrimaryProtocolList) {
187 auto list_it = attributes_.find(kProtocolDescriptorList);
188 if (list_it != attributes_.end()) {
189 auto v = list_it->second.Get<std::vector<DataElement>>();
190 seq = std::move(*v);
191 }
192 } else if (addl_protocols_.count(id)) {
193 auto v = addl_protocols_[id].Get<std::vector<DataElement>>();
194 seq = std::move(*v);
195 }
196
197 std::vector<DataElement> protocol_desc;
198 protocol_desc.emplace_back(DataElement(uuid));
199 if (params.type() == DataElement::Type::kSequence) {
200 auto v = params.Get<std::vector<DataElement>>();
201 auto param_seq = std::move(*v);
202 std::move(std::begin(param_seq),
203 std::end(param_seq),
204 std::back_inserter(protocol_desc));
205 } else if (params.type() != DataElement::Type::kNull) {
206 protocol_desc.emplace_back(std::move(params));
207 }
208
209 seq.emplace_back(DataElement(std::move(protocol_desc)));
210
211 if (id == kPrimaryProtocolList) {
212 SetAttribute(kProtocolDescriptorList, DataElement(std::move(seq)));
213 } else {
214 addl_protocols_.erase(id);
215 addl_protocols_.emplace(id, DataElement(std::move(seq)));
216
217 std::vector<DataElement> addl_protocol_seq;
218 for (const auto& it : addl_protocols_) {
219 addl_protocol_seq.emplace_back(it.second.Clone());
220 }
221
222 SetAttribute(kAdditionalProtocolDescriptorList,
223 DataElement(std::move(addl_protocol_seq)));
224 }
225 }
226
AddProfile(const UUID & uuid,uint8_t major,uint8_t minor)227 void ServiceRecord::AddProfile(const UUID& uuid, uint8_t major, uint8_t minor) {
228 std::vector<DataElement> seq;
229 auto list_it = attributes_.find(kBluetoothProfileDescriptorList);
230 if (list_it != attributes_.end()) {
231 auto v = list_it->second.Get<std::vector<DataElement>>();
232 seq = std::move(*v);
233 }
234
235 std::vector<DataElement> profile_desc;
236 profile_desc.emplace_back(DataElement(uuid));
237 // Safety notes:
238 // 1.) `<<` applies integer promotion of `major` to `int` (32 bits) before
239 // operating. This makes
240 // it safe to left shift 8 bits, even though 8 is >= `major`'s original
241 // width.
242 // 2.) Casting to 16 bits is safe because `major` and `minor` are both only 8
243 // bits, so it is only
244 // possible for 16 bits of the resulting value to be populated.
245 uint16_t profile_version = static_cast<uint16_t>(
246 (major << std::numeric_limits<uint8_t>::digits) | minor);
247 profile_desc.emplace_back(DataElement(profile_version));
248
249 seq.emplace_back(DataElement(std::move(profile_desc)));
250
251 SetAttribute(kBluetoothProfileDescriptorList, DataElement(std::move(seq)));
252 }
253
AddInfo(const std::string & language_code,const std::string & name,const std::string & description,const std::string & provider)254 bool ServiceRecord::AddInfo(const std::string& language_code,
255 const std::string& name,
256 const std::string& description,
257 const std::string& provider) {
258 if ((name.empty() && description.empty() && provider.empty()) ||
259 (language_code.size() != 2)) {
260 return false;
261 }
262 AttributeId base_attrid = 0x0100;
263 std::vector<DataElement> base_attr_list;
264 auto it = attributes_.find(kLanguageBaseAttributeIdList);
265 if (it != attributes_.end()) {
266 auto v = it->second.Get<std::vector<DataElement>>();
267 base_attr_list = std::move(*v);
268
269 // "%" can't be in pw_assert statements.
270 const size_t list_size_mod_3 = base_attr_list.size() % 3;
271 PW_DCHECK(list_size_mod_3 == 0);
272
273 // 0x0100 is guaranteed to be taken, start counting from higher.
274 base_attrid = 0x9000;
275 }
276
277 // Find the first base_attrid that's not taken
278 while (HasAttribute(base_attrid + kServiceNameOffset) ||
279 HasAttribute(base_attrid + kServiceDescriptionOffset) ||
280 HasAttribute(base_attrid + kProviderNameOffset)) {
281 base_attrid++;
282 if (base_attrid == 0xFFFF) {
283 return false;
284 }
285 }
286
287 // Core Spec v5.0, Vol 3, Part B, Sect 5.1.8: "The LanguageBaseAttributeIDList
288 // attribute consists of a data element sequence in which each element is a
289 // 16-bit unsigned integer."
290 // The language code consists of two byte characters in left-to-right order,
291 // so it may be considered a 16-bit big-endian integer that can be converted
292 // to host byte order.
293 uint16_t lang_encoded = pw::bytes::ConvertOrderFrom(
294 cpp20::endian::big, *((const uint16_t*)(language_code.data())));
295 base_attr_list.emplace_back(DataElement(lang_encoded));
296 base_attr_list.emplace_back(DataElement(uint16_t{106})); // UTF-8
297 base_attr_list.emplace_back(DataElement(base_attrid));
298
299 if (!name.empty()) {
300 SetAttribute(base_attrid + kServiceNameOffset, DataElement(name));
301 }
302 if (!description.empty()) {
303 SetAttribute(base_attrid + kServiceDescriptionOffset,
304 DataElement(description));
305 }
306 if (!provider.empty()) {
307 SetAttribute(base_attrid + kProviderNameOffset, DataElement(provider));
308 }
309
310 SetAttribute(kLanguageBaseAttributeIdList,
311 DataElement(std::move(base_attr_list)));
312 return true;
313 }
314
GetInfo() const315 std::vector<ServiceRecord::Information> ServiceRecord::GetInfo() const {
316 if (!HasAttribute(kLanguageBaseAttributeIdList)) {
317 return {};
318 }
319
320 const auto& base_id_list = GetAttribute(kLanguageBaseAttributeIdList);
321 // Expected to be a sequence.
322 if (base_id_list.type() != DataElement::Type::kSequence) {
323 bt_log(WARN, "sdp", "kLanguageBaseAttributeIdList not a sequence");
324 return {};
325 }
326
327 std::vector<ServiceRecord::Information> out;
328 const auto& base_id_seq = base_id_list.Get<std::vector<DataElement>>();
329 const size_t list_size_mod_3 = base_id_seq->size() % 3;
330 PW_DCHECK(list_size_mod_3 == 0);
331
332 for (size_t i = 0; i + 2 < base_id_seq->size(); i += 3) {
333 // Each entry is a triplet of uint16_t (language_code, encoding format, base
334 // attribute ID). Encoding format is always Utf-8 and can be ignored.
335 const std::optional<uint16_t> language = base_id_seq->at(i).Get<uint16_t>();
336 const std::optional<uint16_t> base_attr_id =
337 base_id_seq->at(i + 2).Get<uint16_t>();
338
339 if (!language || !base_attr_id) {
340 bt_log(WARN, "sdp", "Missing language or base_attr_id");
341 return {};
342 }
343
344 ServiceRecord::Information info;
345 // The language code is stored in host byte order, but is interpreted as two
346 // byte characters in left-to-right order (big-endian).
347 uint16_t language_be =
348 pw::bytes::ConvertOrderTo(cpp20::endian::big, language.value());
349 info.language_code = std::string(
350 reinterpret_cast<const char*>(&language_be), sizeof(language_be));
351
352 if (HasAttribute(base_attr_id.value() + kServiceNameOffset)) {
353 std::optional<std::string> name =
354 GetAttribute(base_attr_id.value() + kServiceNameOffset)
355 .Get<std::string>();
356 if (!name) {
357 bt_log(WARN, "sdp", "Invalid name field in information entry");
358 return {};
359 }
360 info.name = std::move(name.value());
361 }
362
363 if (HasAttribute(base_attr_id.value() + kServiceDescriptionOffset)) {
364 std::optional<std::string> description =
365 GetAttribute(base_attr_id.value() + kServiceDescriptionOffset)
366 .Get<std::string>();
367 if (!description) {
368 bt_log(WARN, "sdp", "Invalid description field in information entry");
369 return {};
370 }
371 info.description = std::move(description.value());
372 }
373
374 if (HasAttribute(base_attr_id.value() + kProviderNameOffset)) {
375 std::optional<std::string> provider =
376 GetAttribute(base_attr_id.value() + kProviderNameOffset)
377 .Get<std::string>();
378 if (!provider) {
379 bt_log(WARN, "sdp", "Invalid provider field in information entry");
380 return {};
381 }
382 info.provider = std::move(provider.value());
383 }
384
385 out.emplace_back(std::move(info));
386 }
387
388 return out;
389 }
390
ToString() const391 std::string ServiceRecord::ToString() const {
392 std::string str;
393
394 if (HasAttribute(kBluetoothProfileDescriptorList)) {
395 const DataElement& prof_desc =
396 GetAttribute(kBluetoothProfileDescriptorList);
397 str += "Profile Descriptor: " + prof_desc.ToString() + "\n";
398 }
399
400 if (HasAttribute(kServiceClassIdList)) {
401 const DataElement& svc_class_list = GetAttribute(kServiceClassIdList);
402 str += "Service Class Id List: " + svc_class_list.ToString();
403 }
404
405 return str;
406 }
407
408 } // namespace bt::sdp
409