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 #pragma once
16 
17 #include <optional>
18 #include <string>
19 #include <unordered_set>
20 
21 #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h"
22 #include "pw_bluetooth_sapphire/internal/host/common/uint128.h"
23 
24 namespace bt {
25 
26 // Use raw, non-class enum to explicitly enable usage of enum values as numeric
27 // sizes.
28 enum UUIDElemSize : uint8_t { k16Bit = 2, k32Bit = 4, k128Bit = 16 };
29 
30 // Represents a 128-bit Bluetooth UUID. This class allows UUID values to be
31 // constructed in the official Bluetooth 16-bit, 32-bit, and 128-bit formats and
32 // to be compared against any other Bluetooth UUID.
33 class UUID final {
34  public:
35   // Constructs a UUID from |bytes|. |bytes| should contain a 16-, 32-, or
36   // 128-bit UUID in little-endian byte order. Returns false if |bytes| contains
37   // an unsupported size.
38   static bool FromBytes(const ByteBuffer& bytes, UUID* out_uuid);
39 
40   // Returns a random (Version 4) UUID.
41   static UUID Generate();
42 
43   // The default constructor initializes all values to zero.
44   constexpr UUID() = default;
45 
46   // Constructs a UUID from |bytes|. This is similar to FromBytes, except it
47   // asserts if |bytes| has an unsupported size.
48   explicit UUID(const ByteBuffer& bytes);
49 
UUID(const UInt128 & uuid128)50   constexpr explicit UUID(const UInt128& uuid128) : value_(uuid128) {
51     if (!IsValueCompressable())
52       return;
53 
54     if (value_[kBaseOffset + 2] == 0 && value_[kBaseOffset + 3] == 0) {
55       type_ = Type::k16Bit;
56     } else {
57       type_ = Type::k32Bit;
58     }
59   }
60 
UUID(const uint16_t uuid16)61   constexpr explicit UUID(const uint16_t uuid16)
62       : type_(Type::k16Bit), value_(BuildSIGUUID(uuid16)) {}
63 
UUID(const uint32_t uuid32)64   constexpr explicit UUID(const uint32_t uuid32)
65       : type_(uuid32 > std::numeric_limits<uint16_t>::max() ? Type::k32Bit
66                                                             : Type::k16Bit),
67         value_(BuildSIGUUID(uuid32)) {}
68 
69   // Equality operators.
70   bool operator==(const UUID& uuid) const;
71   bool operator==(uint16_t uuid16) const;
72   bool operator==(uint32_t uuid32) const;
73   bool operator==(const UInt128& uuid128) const;
74   bool operator!=(const UUID& uuid) const { return !(*this == uuid); }
75   bool operator!=(uint16_t uuid16) const { return !(*this == uuid16); }
76   bool operator!=(uint32_t uuid32) const { return !(*this == uuid32); }
77   bool operator!=(const UInt128& uuid128) const { return !(*this == uuid128); }
78 
79   // Compares a UUID with the contents of a raw buffer in little-endian byte
80   // order. This is useful for making a direct comparison with UUIDs received
81   // over PDUs. Returns false if |bytes| has an unaccepted size; the only
82   // accepted sizes for are 2, 4, and 16 for 16-bit, 32-bit, and 128-bit
83   // formats, respectively.
84   bool CompareBytes(const ByteBuffer& bytes) const;
85 
86   // Returns a string representation of this UUID in the following format:
87   //
88   //   xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
89   //
90   // where x is one of the alphanumeric characters in the string
91   // 0123456789abcdef.
92   std::string ToString() const;
93 
94   // Returns the number of bytes required to store this UUID. Returns 16 (i.e.
95   // 128 bits) if |allow_32bit| is false and the compact size is 4 bytes (i.e.
96   // 32 bits).
97   UUIDElemSize CompactSize(bool allow_32bit = true) const;
98 
99   // Writes a little-endian representation of this UUID to |buffer|.  Returns
100   // the number of bytes used. there must be enough space in |buffer| to store
101   // |CompactSize()| bytes.
102   size_t ToBytes(MutableByteBuffer* bytes, bool allow_32bit = true) const;
103 
104   // Returns the most compact representation of this UUID. If |allow_32bit| is
105   // false, then a 32-bit UUIDs will default to 128-bit. The contents will be in
106   // little-endian order.
107   //
108   // Unlike ToBytes(), this does not copy. Since the returned view does not own
109   // its data, it should not outlive this UUID instance.
110   BufferView CompactView(bool allow_32bit = true) const;
111 
112   // Returns a hash of this UUID.
113   std::size_t Hash() const;
114 
115   // Returns the underlying value in little-endian byte order.
value()116   const UInt128& value() const { return value_; }
117 
118   std::optional<uint16_t> As16Bit() const;
119 
120  private:
121   // The Bluetooth Base UUID defines the first value in the range reserved
122   // by the Bluetooth SIG for often-used and officially registered UUIDs. This
123   // UUID is defined as
124   //
125   //    "00000000-0000-1000-8000-00805F9B34FB"
126   //
127   // (see Core Spec v5.0, Vol 3, Part B, Section 2.5.1)
128   static constexpr UInt128 kBaseUuid = {{0xFB,
129                                          0x34,
130                                          0x9B,
131                                          0x5F,
132                                          0x80,
133                                          0x00,
134                                          0x00,
135                                          0x80,
136                                          0x00,
137                                          0x10,
138                                          0x00,
139                                          0x00,
140                                          0x00,
141                                          0x00,
142                                          0x00,
143                                          0x00}};
144 
145   // A 16-bit or 32-bit UUID can be converted to a 128-bit UUID using the
146   // following formula:
147   //
148   //   16-/32-bit value * 2^96 + Bluetooth_Base_UUID
149   //
150   // This is the equivalent of modifying the higher order bytes of the base UUID
151   // starting at octet 12 (96 bits = 12 bytes).
152   //
153   // (see Core Spec v5.0, Vol 3, Part B, Section 2.5.1)
154   static constexpr size_t kBaseOffset = 12;
155 
156   // Returns a 128-bit SIG UUID from the given 16-bit value.
BuildSIGUUID(const uint16_t uuid16)157   static constexpr UInt128 BuildSIGUUID(const uint16_t uuid16) {
158     return BuildSIGUUID(static_cast<uint32_t>(uuid16));
159   }
160 
161   // Returns a 128-bit SIG UUID from the given 32-bit value.
BuildSIGUUID(const uint32_t uuid32)162   static constexpr UInt128 BuildSIGUUID(const uint32_t uuid32) {
163     UInt128 result(kBaseUuid);
164     result[kBaseOffset] = static_cast<uint8_t>(uuid32);
165     result[kBaseOffset + 1] = static_cast<uint8_t>(uuid32 >> 8);
166     result[kBaseOffset + 2] = static_cast<uint8_t>(uuid32 >> 16);
167     result[kBaseOffset + 3] = static_cast<uint8_t>(uuid32 >> 24);
168     return result;
169   }
170 
171   // Returns true if the contents of |value_| represents a UUID in the SIG
172   // reserved range.
IsValueCompressable()173   constexpr bool IsValueCompressable() const {
174     // C++14 allows for-loops in constexpr functions.
175     for (size_t i = 0; i < kBaseOffset; i++) {
176       if (kBaseUuid[i] != value_[i])
177         return false;
178     }
179     return true;
180   }
181 
182   // We store the type that this was initialized with to allow quick comparison
183   // with short Bluetooth SIG UUIDs.
184   enum class Type : uint8_t {
185     k16Bit,
186     k32Bit,
187     k128Bit,
188   };
189 
190   // If a quick conversion is possible, these return the 16 or 32 bit values of
191   // the UUID in host byte order.
192   uint16_t ValueAs16Bit() const;
193   uint32_t ValueAs32Bit() const;
194 
195   Type type_ = Type::k128Bit;
196   UInt128 value_ alignas(size_t) = {};
197 };
198 
199 // Returns true if the given |uuid_string| contains a valid UUID in the
200 // following format:
201 //
202 //   xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
203 //
204 // where x is one of the alphanumeric characters in the string
205 // 0123456789abcdefABCDEF.
206 bool IsStringValidUuid(const std::string& uuid_string);
207 
208 // Constructs a 128-bit UUID from a string representation in one of the
209 // following formats:
210 //
211 //   xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (full UUID string)
212 //   xxxx (abbreviated 16-bit UUID)
213 //
214 // where x is one of the alphanumeric characters in the string
215 // 0123456789abcdefABCDEF.
216 //
217 // Returns false if the string does not represent a valid Bluetooth UUID.
218 // Otherwise returns true and populates |out_uuid|.
219 bool StringToUuid(const std::string& uuid_string, UUID* out_uuid);
220 
221 // Equality operators
222 inline bool operator==(uint16_t lhs, const UUID& rhs) { return rhs == lhs; }
223 
224 inline bool operator==(uint32_t lhs, const UUID& rhs) { return rhs == lhs; }
225 
226 inline bool operator==(const UInt128& lhs, const UUID& rhs) {
227   return rhs == lhs;
228 }
229 
230 inline bool operator!=(uint16_t lhs, const UUID& rhs) { return rhs != lhs; }
231 
232 inline bool operator!=(uint32_t lhs, const UUID& rhs) { return rhs != lhs; }
233 
234 inline bool operator!=(const UInt128& lhs, const UUID& rhs) {
235   return rhs != lhs;
236 }
237 
238 }  // namespace bt
239 
240 // Specialization of std::hash for std::unordered_set, std::unordered_map, etc.
241 namespace std {
242 
243 template <>
244 struct hash<bt::UUID> {
245   size_t operator()(const bt::UUID& k) const { return k.Hash(); }
246 };
247 
248 }  // namespace std
249