Skip to content

Commit

Permalink
pw_bluetooth: Make Uuid a class with constructors and helper methods
Browse files Browse the repository at this point in the history
The Bluetooth Core Spec defines 16-bit, 32-bit and full 128-bit
representations of 128-bit UUID values. In several cases the protocol
allows to set a 128-bit value or shorter 16-bit value when using one of
the pre-assigned short 16-bit values.

This patch makes Uuid a class of the same 128-bit size as before but
adds constructors and helper methods to easily handle these
representations as well as printing and parsing human-readable hex
representations of the same.

Test: Added unittests for the new methods.
Change-Id: I93607b58a85eb8a3388a8e2f462aa0245b812c00
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/112932
Reviewed-by: Wyatt Hepler <[email protected]>
Reviewed-by: Ben Lawson <[email protected]>
Reviewed-by: Marie Janssen <[email protected]>
Commit-Queue: Alex Deymo <[email protected]>
  • Loading branch information
deymo authored and CQ Bot Account committed Oct 21, 2022
1 parent 7009bed commit cc55c96
Show file tree
Hide file tree
Showing 7 changed files with 391 additions and 3 deletions.
13 changes: 13 additions & 0 deletions pw_bluetooth/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pw_cc_library(
"public/pw_bluetooth/gatt/types.h",
"public/pw_bluetooth/hci.h",
"public/pw_bluetooth/host.h",
"public/pw_bluetooth/internal/hex.h",
"public/pw_bluetooth/low_energy/advertising_data.h",
"public/pw_bluetooth/low_energy/bond_data.h",
"public/pw_bluetooth/low_energy/central.h",
Expand All @@ -43,13 +44,15 @@ pw_cc_library(
"public/pw_bluetooth/peer.h",
"public/pw_bluetooth/result.h",
"public/pw_bluetooth/types.h",
"public/pw_bluetooth/uuid.h",
],
includes = ["public"],
deps = [
"//pw_chrono:system_clock",
"//pw_containers",
"//pw_function",
"//pw_status",
"//pw_string:string",
],
)

Expand All @@ -72,3 +75,13 @@ pw_cc_test(
"pw_bluetooth",
],
)

pw_cc_test(
name = "uuid_test",
srcs = [
"uuid_test.cc",
],
deps = [
"pw_bluetooth",
],
)
9 changes: 9 additions & 0 deletions pw_bluetooth/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pw_source_set("pw_bluetooth") {
"public/pw_bluetooth/gatt/types.h",
"public/pw_bluetooth/hci.h",
"public/pw_bluetooth/host.h",
"public/pw_bluetooth/internal/hex.h",
"public/pw_bluetooth/low_energy/advertising_data.h",
"public/pw_bluetooth/low_energy/bond_data.h",
"public/pw_bluetooth/low_energy/central.h",
Expand All @@ -49,9 +50,11 @@ pw_source_set("pw_bluetooth") {
"public/pw_bluetooth/peer.h",
"public/pw_bluetooth/result.h",
"public/pw_bluetooth/types.h",
"public/pw_bluetooth/uuid.h",
]
public_deps = [
"$dir_pw_chrono:system_clock",
"$dir_pw_string:string",
dir_pw_containers,
dir_pw_function,
dir_pw_span,
Expand All @@ -64,6 +67,7 @@ pw_test_group("tests") {
tests = [
":api_test",
":result_test",
":uuid_test",
]
}

Expand All @@ -76,3 +80,8 @@ pw_test("result_test") {
sources = [ "result_test.cc" ]
deps = [ ":pw_bluetooth" ]
}

pw_test("uuid_test") {
sources = [ "uuid_test.cc" ]
deps = [ ":pw_bluetooth" ]
}
14 changes: 13 additions & 1 deletion pw_bluetooth/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pw_add_module_library(pw_bluetooth
public/pw_bluetooth/gatt/error.h
public/pw_bluetooth/gatt/server.h
public/pw_bluetooth/gatt/types.h
public/pw_bluetooth/internal/hex.h
public/pw_bluetooth/low_energy/advertising_data.h
public/pw_bluetooth/low_energy/bond_data.h
public/pw_bluetooth/low_energy/central.h
Expand All @@ -34,12 +35,14 @@ pw_add_module_library(pw_bluetooth
public/pw_bluetooth/peer.h
public/pw_bluetooth/result.h
public/pw_bluetooth/types.h
public/pw_bluetooth/uuid.h
PUBLIC_INCLUDES
public
PUBLIC_DEPS
pw_containers
pw_function
pw_status
pw_string.string
pw_chrono.system_clock
)

Expand All @@ -59,4 +62,13 @@ pw_add_test(pw_bluetooth.result_test
pw_bluetooth
GROUPS
pw_bluetooth
)
)

pw_add_test(pw_bluetooth.uuid_test
SOURCES
uuid_test.cc
DEPS
pw_bluetooth
GROUPS
pw_bluetooth
)
46 changes: 46 additions & 0 deletions pw_bluetooth/public/pw_bluetooth/internal/hex.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2022 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once

#include <cstdint>

namespace pw::bluetooth::internal {

// Parse a hexadecimal character to a 0-15 number. If the hex char is invalid
// returns 0x100.
constexpr uint16_t HexToNibble(char hex) {
if (hex >= '0' && hex <= '9') {
return hex - '0';
}
if (hex >= 'A' && hex <= 'F') {
return hex - 'A' + 10;
}
if (hex >= 'a' && hex <= 'f') {
return hex - 'a' + 10;
}
return 0x100;
}

// Convert a nibble (0 to 15 value) to its lowercase hexadecimal representation.
constexpr char NibbleToHex(uint8_t value) {
if (value < 10) {
return '0' + value;
}
if (value < 16) {
return 'a' + value - 10;
}
return '?';
}

} // namespace pw::bluetooth::internal
4 changes: 2 additions & 2 deletions pw_bluetooth/public/pw_bluetooth/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include <cstdint>
#include <string_view>

#include "pw_bluetooth/uuid.h"

namespace pw::bluetooth {

// 64-bit unique value used by the system to identify peer devices.
Expand All @@ -27,8 +29,6 @@ using Address = std::array<uint8_t, 6>;

using DeviceName = std::string_view;

using Uuid = std::array<uint8_t, 16>;

// A 128-bit secret key.
using Key = std::array<uint8_t, 16>;

Expand Down
198 changes: 198 additions & 0 deletions pw_bluetooth/public/pw_bluetooth/uuid.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// Copyright 2022 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once

#include <array>
#include <climits>
#include <cstdint>

#include "pw_assert/assert.h"
#include "pw_bluetooth/internal/hex.h"
#include "pw_span/span.h"
#include "pw_string/string.h"

namespace pw::bluetooth {

// A 128-bit Universally Unique Identifier (UUID).
// See Core Spec v5.3 Volume 3, Part B, Section 2.5.1.
//
// Bluetooth defines 16-bit, 32-bit and 128-bit UUID representations for a
// 128-bit UUID, all of which are used in the protocol. 16-bit UUIDs values
// define only the "YYYY" portion in the following UUID pattern (with XXXX set
// as 0), while 32-bit UUID value define the "XXXXYYYY" portion. When using
// these short UUIDs, the remaining bits are set by the Bluetooth_Base_UUID as
// follows:
// XXXXYYYY-0000-1000-8000-00805f9b34fb
//
// This class always stores UUID in their 128-bit representation in little
// endian format.
class Uuid {
public:
// String size of a hexadecimal representation of a UUID, not including the
// null terminator.
static constexpr size_t kHexStringSize = 36;

// Create a UUID from a span of 128-bit data. UUIDs are represented as
// little endian bytes.
explicit constexpr Uuid(const span<const uint8_t, 16> uuid_span) : uuid_() {
for (size_t i = 0; i < sizeof(uuid_); i++) {
uuid_[i] = uuid_span[i];
}
}

// Create a UUID from its string representation. This is parsed manually here
// so it can be parsed at compile time with constexpr. A valid UUID is a hex
// string with hyphen separators at the 9th, 14th, 19th, and 24th
// positions, for example:
// "0000180a-0000-1000-8000-00805f9b34fb"
// The `str` parameter is byte longer than kHexStringSize so it can be
// initialized with literal strings including the null terminator, as in
// BluetoothBase().
constexpr Uuid(const char (&str)[kHexStringSize + 1]) : uuid_() {
size_t out_hex_index = 2 * sizeof(uuid_); // UUID is stored little-endian.
for (size_t i = 0; i < kHexStringSize; i++) {
// Indices at which we expect to find a hyphen ('-') in a UUID string.
if (i == 8 || i == 13 || i == 18 || i == 23) {
PW_ASSERT(str[i] == '-');
continue;
}
PW_ASSERT(str[i] != 0);
out_hex_index--;
uint16_t value = internal::HexToNibble(str[i]);
PW_ASSERT(value <= 0xf);
if (out_hex_index % 2 == 0) {
uuid_[out_hex_index / 2] |= value;
} else {
uuid_[out_hex_index / 2] = value << 4;
}
}
}

// The Bluetooth_Base_UUID defined by the specification. This is the base for
// all 16-bit and 32-bit short UUIDs.
static constexpr const Uuid& BluetoothBase();

// Create a UUID combining 96-bits from a base UUID with a 16-bit or 32-bit
// value. 16-bit values will be extended to 32-bit ones, meaning the that the
// 16 most significant bits will be set to 0 regardless of the value on the
// base UUID.
constexpr Uuid(uint32_t short_uuid, const Uuid& base_uuid)
: uuid_(base_uuid.uuid_) {
uuid_[kBaseOffset] = short_uuid & 0xff;
uuid_[kBaseOffset + 1] = (short_uuid >> CHAR_BIT) & 0xff;
uuid_[kBaseOffset + 2] = (short_uuid >> CHAR_BIT * 2) & 0xff;
uuid_[kBaseOffset + 3] = (short_uuid >> CHAR_BIT * 3) & 0xff;
}

// Create a short UUID (32-bit or 16-bit) using the standard Bluetooth base
// UUID.
explicit constexpr Uuid(uint32_t short_uuid)
: Uuid(short_uuid, BluetoothBase()) {}

constexpr Uuid(const Uuid&) = default;
constexpr Uuid& operator=(const Uuid&) = default;

// Return a 2-byte span containing the 16-bit little endian representation of
// the UUID. This is useful when Same112BitBase(BluetoothBase()) is true.
constexpr span<const uint8_t, 2> As16BitSpan() const {
return span<const uint8_t, 2>{uuid_.data() + kBaseOffset, 2u};
}

// Return a 4-byte span containing the 32-bit little endian representation of
// the UUID. This is useful when Same96BitBase(BluetoothBase()) is true.
constexpr span<const uint8_t, 4> As32BitSpan() const {
return span<const uint8_t, 4>{uuid_.data() + kBaseOffset, 4u};
}

// Return the 128-bit (16-byte) little endian representation of the UUID.
constexpr span<const uint8_t, 16> As128BitSpan() const {
return span<const uint8_t, 16>{uuid_.data(), 16u};
}

// Return whether the UUID shares the same 112-bit base with another UUID.
// Sharing the same 112-bit base with BluetoothBase() means that this UUID
// can be resented as a 16-bit UUID.
constexpr bool Same112BitBase(const Uuid& other) const {
return Same96BitBase(other) && uuid_[14] == other.uuid_[14] &&
uuid_[15] == other.uuid_[15];
}

// Return whether the UUID shares the same 96-bit base with another UUID.
// Sharing the same 96-bit base with BluetoothBase() means that this UUID
// can be resented as a 32-bit UUID.
constexpr bool Same96BitBase(const Uuid& other) const {
for (size_t i = 0; i < 12; i++) {
if (uuid_[i] != other.uuid_[i])
return false;
}
return true;
}

// Return whether the UUID is a 16-bit UUID represented as 128-bit using the
// BluetoothBase() as the base.
constexpr bool Is16BitUuid() const { return Same112BitBase(BluetoothBase()); }

// Return whether the UUID is a 32-bit UUID represented as 128-bit using the
// BluetoothBase() as the base.
constexpr bool Is32BitUuid() const { return Same96BitBase(BluetoothBase()); }

// Return an inline pw_string representation of the UUID in hexadecimal.
constexpr InlineString<kHexStringSize> ToString() const {
InlineString<kHexStringSize> ret;
for (size_t i = uuid_.size(); i-- != 0;) {
ret += internal::NibbleToHex(uuid_[i] >> 4);
ret += internal::NibbleToHex(uuid_[i] & 0xf);
if ((i == 12) || (i == 10) || (i == 8) || (i == 6)) {
ret += '-';
}
}
return ret;
}

private:
// Offset at which the short 16-bit and 32-bit UUID little-endian data starts
// in the uuid_ array.
static constexpr size_t kBaseOffset = 12;

std::array<uint8_t, 16> uuid_;
};

namespace internal {
// When BluetoothBase() is used in constexpr expressions it would normally be
// evaluated to a final different Uuid, such as when used in Uuid(uint32_t),
// however if a reference to the return value of BluetoothBase() is needed this
// variable would be the only global symbol that provides it even if it is used
// from multiple translation units.
constexpr Uuid kBluetoothBaseUuid{"00000000-0000-1000-8000-00805F9B34FB"};
} // namespace internal

inline constexpr const Uuid& Uuid::BluetoothBase() {
return internal::kBluetoothBaseUuid;
}

// Uuid comparators:
constexpr bool operator==(const Uuid& a, const Uuid& b) {
const auto a_span = a.As128BitSpan();
const auto b_span = b.As128BitSpan();
for (size_t i = 0; i < a_span.size(); i++) {
if (a_span[i] != b_span[i]) {
return false;
}
}
return true;
}

constexpr bool operator!=(const Uuid& a, const Uuid& b) { return !(a == b); }

} // namespace pw::bluetooth
Loading

0 comments on commit cc55c96

Please sign in to comment.