• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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