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