• 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/common/uuid.h"
16 
17 #include <endian.h>
18 
19 #include <cinttypes>
20 
21 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
22 #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h"
23 #include "pw_bluetooth_sapphire/internal/host/common/random.h"
24 #include "pw_string/format.h"
25 
26 namespace bt {
27 namespace {
28 
29 // Format string that can be passed to sscanf. This allows sscanf to convert
30 // each octet into a uint8_t.
31 constexpr char kScanUuidFormatString[] =
32     "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8
33     "-"
34     "%2" SCNx8 "%2" SCNx8
35     "-"
36     "%2" SCNx8 "%2" SCNx8
37     "-"
38     "%2" SCNx8 "%2" SCNx8
39     "-"
40     "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8;
41 
42 // Parses the contents of a |uuid_string| and returns the result in |out_bytes|.
43 // Returns false if |uuid_string| does not represent a valid UUID.
44 // TODO(armansito): After having used UUID in camel-case words all over the
45 // place, I've decided that it sucks. I'm explicitly naming this using the
46 // "Uuid" style as a reminder to fix style elsewhere.
ParseUuidString(const std::string & uuid_string,UInt128 * out_bytes)47 bool ParseUuidString(const std::string& uuid_string, UInt128* out_bytes) {
48   BT_DEBUG_ASSERT(out_bytes);
49 
50   if (uuid_string.length() == 4) {
51     // Possibly a 16-bit short UUID, parse it in context of the Base UUID.
52     return ParseUuidString(
53         "0000" + uuid_string + "-0000-1000-8000-00805F9B34FB", out_bytes);
54   }
55 
56   // This is a 36 character string, including 4 "-" characters and two
57   // characters for each of the 16-octets that form the 128-bit UUID.
58   if (uuid_string.length() != 36)
59     return false;
60 
61   int result = std::sscanf(uuid_string.c_str(),
62                            kScanUuidFormatString,
63                            out_bytes->data() + 15,
64                            out_bytes->data() + 14,
65                            out_bytes->data() + 13,
66                            out_bytes->data() + 12,
67                            out_bytes->data() + 11,
68                            out_bytes->data() + 10,
69                            out_bytes->data() + 9,
70                            out_bytes->data() + 8,
71                            out_bytes->data() + 7,
72                            out_bytes->data() + 6,
73                            out_bytes->data() + 5,
74                            out_bytes->data() + 4,
75                            out_bytes->data() + 3,
76                            out_bytes->data() + 2,
77                            out_bytes->data() + 1,
78                            out_bytes->data());
79 
80   return (result > 0) && (static_cast<size_t>(result) == out_bytes->size());
81 }
82 
83 }  // namespace
84 
FromBytes(const ByteBuffer & bytes,UUID * out_uuid)85 bool UUID::FromBytes(const ByteBuffer& bytes, UUID* out_uuid) {
86   switch (bytes.size()) {
87     case UUIDElemSize::k16Bit: {
88       uint16_t dst;
89       memcpy(&dst, bytes.data(), sizeof(dst));
90       *out_uuid = UUID(le16toh(dst));
91       return true;
92     }
93     case UUIDElemSize::k32Bit: {
94       uint32_t dst;
95       memcpy(&dst, bytes.data(), sizeof(dst));
96       *out_uuid = UUID(le32toh(dst));
97       return true;
98     }
99     case UUIDElemSize::k128Bit: {
100       UInt128 dst;
101       memcpy(dst.data(), bytes.data(), sizeof(dst));
102       *out_uuid = UUID(dst);
103       return true;
104     }
105   }
106 
107   return false;
108 }
109 
Generate()110 UUID UUID::Generate() {
111   // We generate a 128-bit random UUID in the form of version 4 as described in
112   // ITU-T Rec. X.667(10/2012) Sec 15.1. This is the same as RFC 4122.
113   UInt128 uuid = Random<UInt128>();
114   //  Set the four most significant bits (bits 15 through 12) of the
115   //  "VersionAndTimeHigh" field to 4.
116   constexpr uint8_t version_number = 0b0100'0000;
117   uuid[6] = (uuid[6] & 0b0000'1111) | version_number;
118   // Set the two most significant bits (bits 7 and 6) of the
119   // "VariantAndClockSeqHigh" field to 1 and 0, respectively.
120   uuid[8] = (uuid[8] & 0b0011'1111) | 0b1000'0000;
121   return UUID(uuid);
122 }
123 
UUID(const ByteBuffer & bytes)124 UUID::UUID(const ByteBuffer& bytes) {
125   bool result = FromBytes(bytes, this);
126   BT_ASSERT_MSG(result, "|bytes| must contain a 16, 32, or 128-bit UUID");
127 }
128 
operator ==(const UUID & uuid) const129 bool UUID::operator==(const UUID& uuid) const { return value_ == uuid.value_; }
130 
operator ==(uint16_t uuid16) const131 bool UUID::operator==(uint16_t uuid16) const {
132   if (type_ == Type::k16Bit)
133     return uuid16 == ValueAs16Bit();
134 
135   // Quick conversion is not possible; compare as two 128-bit UUIDs.
136   return *this == UUID(uuid16);
137 }
138 
operator ==(uint32_t uuid32) const139 bool UUID::operator==(uint32_t uuid32) const {
140   if (type_ != Type::k128Bit)
141     return uuid32 == ValueAs32Bit();
142 
143   // Quick conversion is not possible; compare as two 128-bit UUIDs.
144   return *this == UUID(uuid32);
145 }
146 
operator ==(const UInt128 & uuid128) const147 bool UUID::operator==(const UInt128& uuid128) const {
148   return value_ == uuid128;
149 }
150 
CompareBytes(const ByteBuffer & bytes) const151 bool UUID::CompareBytes(const ByteBuffer& bytes) const {
152   UUID other;
153   if (!FromBytes(bytes, &other)) {
154     return false;
155   }
156   return *this == other;
157 }
158 
ToString() const159 std::string UUID::ToString() const {
160   char out[sizeof("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")];
161   pw::StatusWithSize result = pw::string::Format(
162       {out, sizeof(out)},
163       "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
164       value_[15],
165       value_[14],
166       value_[13],
167       value_[12],
168       value_[11],
169       value_[10],
170       value_[9],
171       value_[8],
172       value_[7],
173       value_[6],
174       value_[5],
175       value_[4],
176       value_[3],
177       value_[2],
178       value_[1],
179       value_[0]);
180   BT_DEBUG_ASSERT(result.ok());
181   return out;
182 }
183 
CompactSize(bool allow_32bit) const184 UUIDElemSize UUID::CompactSize(bool allow_32bit) const {
185   switch (type_) {
186     case Type::k16Bit:
187       return UUIDElemSize::k16Bit;
188     case Type::k32Bit:
189       if (allow_32bit)
190         return UUIDElemSize::k32Bit;
191 
192       // Fall through if 32-bit UUIDs are not allowed.
193       [[fallthrough]];
194     case Type::k128Bit:
195       return UUIDElemSize::k128Bit;
196   };
197   BT_PANIC("uuid type of %du is invalid", static_cast<uint8_t>(type_));
198 }
199 
ToBytes(MutableByteBuffer * bytes,bool allow_32bit) const200 size_t UUID::ToBytes(MutableByteBuffer* bytes, bool allow_32bit) const {
201   size_t size = CompactSize(allow_32bit);
202   size_t offset = (size == UUIDElemSize::k128Bit) ? 0u : kBaseOffset;
203   bytes->Write(value_.data() + offset, size);
204   return size;
205 }
206 
CompactView(bool allow_32bit) const207 BufferView UUID::CompactView(bool allow_32bit) const {
208   size_t size = CompactSize(allow_32bit);
209   size_t offset = (size == UUIDElemSize::k128Bit) ? 0u : kBaseOffset;
210   return BufferView(value_.data() + offset, size);
211 }
212 
Hash() const213 std::size_t UUID::Hash() const {
214   static_assert(sizeof(value_) % sizeof(size_t) == 0);
215   // Morally we'd like to assert this, but:
216   //
217   // 'alignof' applied to an expression is a GNU extension.
218   //
219   // static_assert(alignof(value_) % alignof(size_t) == 0);
220   size_t hash = 0;
221   for (size_t i = 0; i < (sizeof(value_) / sizeof(size_t)); i++) {
222     hash ^=
223         *reinterpret_cast<const size_t*>(value_.data() + (i * sizeof(size_t)));
224   }
225   return hash;
226 }
227 
As16Bit() const228 std::optional<uint16_t> UUID::As16Bit() const {
229   std::optional<uint16_t> ret;
230   if (type_ == Type::k16Bit) {
231     ret = ValueAs16Bit();
232   }
233   return ret;
234 }
235 
ValueAs16Bit() const236 uint16_t UUID::ValueAs16Bit() const {
237   BT_DEBUG_ASSERT(type_ == Type::k16Bit);
238 
239   return le16toh(
240       *reinterpret_cast<const uint16_t*>(value_.data() + kBaseOffset));
241 }
242 
ValueAs32Bit() const243 uint32_t UUID::ValueAs32Bit() const {
244   BT_DEBUG_ASSERT(type_ != Type::k128Bit);
245 
246   return le32toh(
247       *reinterpret_cast<const uint32_t*>(value_.data() + kBaseOffset));
248 }
249 
IsStringValidUuid(const std::string & uuid_string)250 bool IsStringValidUuid(const std::string& uuid_string) {
251   UInt128 bytes;
252   return ParseUuidString(uuid_string, &bytes);
253 }
254 
StringToUuid(const std::string & uuid_string,UUID * out_uuid)255 bool StringToUuid(const std::string& uuid_string, UUID* out_uuid) {
256   BT_DEBUG_ASSERT(out_uuid);
257 
258   UInt128 bytes;
259   if (!ParseUuidString(uuid_string, &bytes))
260     return false;
261 
262   *out_uuid = UUID(bytes);
263   return true;
264 }
265 
266 }  // namespace bt
267