blob: b102dbd439f6a9d08eaab22e7a45af5097fefe79 [file] [log] [blame]
// 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();
constexpr Uuid() : uuid_() {}
// 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