From c2e0ca791dde5846a4f35d990b41ac9438cd56d2 Mon Sep 17 00:00:00 2001 From: Pawel Wasowski Date: Fri, 28 Feb 2020 14:02:25 +0100 Subject: [PATCH] [Bluetooth] Add UUID class UUID class will handle validation of string UUIDs and conversions between different UUID formats. Verification: unit tests (added in the next commit) pass rate: 100% Change-Id: Ie075379d7c5a4567646c04097888d5ae2f560b5b Signed-off-by: Pawel Wasowski --- src/bluetooth/bluetooth.gyp | 2 + src/bluetooth/uuid.cc | 160 ++++++++++++++++++++++++++++++++++++ src/bluetooth/uuid.h | 107 ++++++++++++++++++++++++ 3 files changed, 269 insertions(+) create mode 100644 src/bluetooth/uuid.cc create mode 100644 src/bluetooth/uuid.h diff --git a/src/bluetooth/bluetooth.gyp b/src/bluetooth/bluetooth.gyp index 89813063..07c310ea 100644 --- a/src/bluetooth/bluetooth.gyp +++ b/src/bluetooth/bluetooth.gyp @@ -41,6 +41,8 @@ 'bluetooth_util.h', 'bluetooth_le_device.cc', 'bluetooth_le_device.h', + 'uuid.cc', + 'uuid.h' ], 'includes': [ '../common/pkg-config.gypi', diff --git a/src/bluetooth/uuid.cc b/src/bluetooth/uuid.cc new file mode 100644 index 00000000..b692b883 --- /dev/null +++ b/src/bluetooth/uuid.cc @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * + * 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 + * + * http://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. + */ + +#include +#include + +#include "bluetooth_util.h" +#include "common/scope_exit.h" +#include "common/tools.h" +#include "uuid.h" + +using common::PlatformResult; +using common::ErrorCode; + +namespace { + +const std::regex kCanonical128BitFormat{ + "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"}; +const std::regex k16BitFormat{"[0-9a-fA-F]{4}"}; +const std::regex k32BitFormat{"[0-9a-fA-F]{8}"}; +const std::regex kConvertibleTo16Bit{"0000[0-9a-fA-F]{4}-0000-1000-8000-00805[fF]9[bB]34[fF][bB]"}; +const std::regex kConvertibleTo32Bit{"[0-9a-fA-F]{8}-0000-1000-8000-00805[fF]9[bB]34[fF][bB]"}; +const std::string kBaseUuidLast96Bits = "-0000-1000-8000-00805f9b34fb"; +const optional NO_UUID{}; +const optional NO_UUID_STR{}; + +const int k16BitUUIDBegin = 4; +const int k16BitUUIDLen = 4; +const int k32BitUUIDBegin = 0; +const int k32BitUUIDLen = 8; + +}; // unnamed namespace + +namespace extension { + +namespace bluetooth { + +UUID::UUID(std::string&& uuid_128_bit) : uuid_128_bit{uuid_128_bit} { + ScopeLogger("Input: %s", uuid_128_bit.c_str()); +} + +optional UUID::create(const std::string& uuid) { + ScopeLogger("Input: %s", uuid.c_str()); + + auto uuid_128_bit = To128Bit(uuid); + if (uuid_128_bit.empty()) { + return NO_UUID; + } + + return UUID{std::move(uuid_128_bit)}; +} + +optional UUID::createFromGatt(const bt_gatt_h gatt_handle) { + ScopeLogger("Input: "); + + char* uuid_buffer{nullptr}; + SCOPE_EXIT { + free(uuid_buffer); + }; + + auto ret = bt_gatt_get_uuid(gatt_handle, &uuid_buffer); + if (BT_ERROR_NONE != ret) { + LoggerE("Couldn't get UUID. Error: %s", util::GetBluetoothErrorMessage(ret).c_str()); + return NO_UUID; + } + + if (!uuid_buffer) { + LoggerE("Couldn't get UUID. Returned uuid is a nullptr"); + return NO_UUID; + } + + LoggerD("uuid_buffer: %s", uuid_buffer); + + return {create(std::string{uuid_buffer})}; +} + +optional UUID::To16Bit() const { + ScopeLogger("128 bit UUID: %s", uuid_128_bit.c_str()); + + if (std::regex_match(uuid_128_bit, kConvertibleTo16Bit)) { + return {uuid_128_bit.substr(k16BitUUIDBegin, k16BitUUIDLen)}; + } + + return NO_UUID_STR; +} + +optional UUID::To32Bit() const { + ScopeLogger("128 bit UUID: %s", uuid_128_bit.c_str()); + + if (std::regex_match(uuid_128_bit, kConvertibleTo32Bit)) { + return {uuid_128_bit.substr(k32BitUUIDBegin, k32BitUUIDLen)}; + } + + return NO_UUID_STR; +} + +std::string UUID::ShortestPossibleFormat() const { + ScopeLogger("128 bit UUID: %s", uuid_128_bit.c_str()); + + if (std::regex_match(uuid_128_bit, kConvertibleTo16Bit)) { + return {uuid_128_bit.substr(k16BitUUIDBegin, k16BitUUIDLen)}; + } + + if (std::regex_match(uuid_128_bit, kConvertibleTo32Bit)) { + return {uuid_128_bit.substr(k32BitUUIDBegin, k32BitUUIDLen)}; + } + + return uuid_128_bit; +} + +bool UUID::operator==(const UUID& other) const { + return uuid_128_bit == other.uuid_128_bit; +} + +UUID::Format UUID::GetFormat(const std::string& uuid) { + ScopeLogger("uuid: %s", uuid.c_str()); + + if (std::regex_match(uuid, k16BitFormat)) { + return Format::UUID_16_BIT; + } else if (std::regex_match(uuid, k32BitFormat)) { + return Format::UUID_32_BIT; + } else if (std::regex_match(uuid, kCanonical128BitFormat)) { + return Format::UUID_128_BIT; + } + + return Format::UUID_INVALID; +} + +std::string UUID::To128Bit(const std::string& uuid) { + ScopeLogger("uuid: %s", uuid.c_str()); + + auto uuid_lower_case = common::tools::ConvertToLowerCase(uuid); + switch (GetFormat(uuid_lower_case)) { + case Format::UUID_16_BIT: + return "0000" + uuid_lower_case + kBaseUuidLast96Bits; + case Format::UUID_32_BIT: + return uuid_lower_case + kBaseUuidLast96Bits; + case Format::UUID_128_BIT: + return uuid_lower_case; + default: + LoggerE("Error: uuid format unknown"); + return ""; + } +} + +} // bluetooth +} // extension diff --git a/src/bluetooth/uuid.h b/src/bluetooth/uuid.h new file mode 100644 index 00000000..ddfeb479 --- /dev/null +++ b/src/bluetooth/uuid.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * + * 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 + * + * http://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. + */ + +#ifndef UUID_H_ +#define UUID_H_ + +#include + +#include + +#include "common/platform_result.h" +#if __cplusplus > 201402L +#include +using std::optional; +#else +#include "common/optional.h" +using common::optional; +#endif + +using common::PlatformResult; + +namespace extension { + +namespace bluetooth { + +struct UUID { + /* + * A UUID object is only created, if the string containing it + * is in a proper format or it can be created from the bt_gatt_h. + * + * create() functions accept the following formats of strings: + * 1. 4 hexadecimal digits, e.g.: "018D" + * 2. 8 hexadecimal digits, e.g.: "018Dab17" + * 3. 128 bit canonical UUID format defined in + * https://tools.ietf.org/html/rfc4122, e.g.: + * "f95c593e-efd4-43c4-96e4-97186ee52f83" + */ + static optional create(const std::string&); + + /* + * This function does not validate passed bt_gatt_h. + * If the bt_gatt_h is not initialized, the function will likely crash. + * + * This function was purposedly not made an overload of create(). + * If it was an overload, (const) char* could be implicitly converted to + * const void* (instead of std::string), which is the underlying type of + * bt_gatt_h. + */ + static optional createFromGatt(const bt_gatt_h); + + UUID() = delete; + UUID(const UUID& uuid) = default; + UUID(UUID&& uuid) = default; + + /* + * Only UUIDs built with kBaseUUID are convertible to 32 bit format + * and only a subset of them are convertible to 16 bit format. + * + * These functions always return hexadecimal lower case letters as digits. + */ + optional To16Bit() const; + optional To32Bit() const; + + /* + * This function checks if the UUID is built with kBaseUUID and tries to + * return the UUID in different formats in the following order: + * 1. 16 bit + * 2. 32 bit + * 3. 128 bit + */ + std::string ShortestPossibleFormat() const; + + bool operator==(const UUID&) const; + /* + * Hexadecimal letter digits are always stored in lower case. + */ + const std::string uuid_128_bit; + + private: + /* + * These functions don't validate passed arguments. + */ + UUID(std::string&& uuid_128_bit); + static std::string To128Bit(const std::string& uuid); + + enum class Format : int { UUID_16_BIT, UUID_32_BIT, UUID_128_BIT, UUID_INVALID }; + + static Format GetFormat(const std::string& uuid); +}; + +} // bluetooth +} // extension + +#endif // UUID_H_ -- 2.34.1