From 06f646aec4dbce64d28bae1be6111bd833f8e79e Mon Sep 17 00:00:00 2001 From: Paul Nettle Date: Fri, 25 Aug 2017 09:30:39 -0500 Subject: Initial version 1.0 --- src/GattUuid.h | 282 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 src/GattUuid.h (limited to 'src/GattUuid.h') diff --git a/src/GattUuid.h b/src/GattUuid.h new file mode 100644 index 0000000..ee89332 --- /dev/null +++ b/src/GattUuid.h @@ -0,0 +1,282 @@ +// Copyright 2017 Paul Nettle. +// +// This file is part of Gobbledegook. +// +// Gobbledegook is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Gobbledegook is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Gobbledegook. If not, see . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> INSIDE THIS FILE +// >> +// +// This class manages the creation of properly formatted GATT UUIDs +// +// >> +// >>> DISCUSSION +// >> +// +// A GATT UUID represents a unique identifier for a GATT service, characteristic or descriptor. UUIDs allow clients to recognize +// the purpose of a service (battery level, temperature data, etc.) Similarly, they are used to identify characteristics (the +// battery percentage or actual temperature value) and descriptors (for example, the format of a characteristic data item.) +// +// GATT UUIDs come in three flavors: 16-bit, 32-bit and 128-bit. In practice, you will see two different kinds of UUIDs: a short +// UUID (ex: "2901") and a long UUID (ex: "00000002-1E3C-FAD4-74E2-97A033F1BFAA"). +// +// The short form refer to 16-bit UUID that is assigned to a standard interface defined as part of the Bluetooth Wireless Standard. +// The short UUID "2901" refers to a standard (assigned by the Bluetooth Wireless Standard) descriptor named "Characteristic User +// Description". These are commonly called "Assigned Numbers". +// +// In fact, all UUIDs are 128-bit values. The 16-bit form is just an abbreviated way of specifying a standard interface that uses +// what Bluetooth calls the "Base Universally Unique Identifier (UUID)". This UUID is defined by the standard as +// "00000000-0000-1000-8000-00805f9b34fb". The 16-bit value "2901" would be inserted into the Base UUID like so: +// "00002901-0000-1000-8000-00805f9b34fb". +// +// When defining a custom service (or characteristic or descriptor) UUIDs are typically generated by the developer. These UUIDs are +// then used by the client software to recognize the custom service (or characteristic or descriptor.) +// +// The `GattUuid` class is a helper class for creating instances of UUIDs. By instantiating a GattUuid with a 4-character string, +// the GattUuid will treat the string as a 16-bit UUID and wrap it with the Base Universally Unique Identifier. Similarly, an 8- +// character string will be treated as a standard 32-bit UUID, also wrapping it with the Base Universally Unique Identifier. A 32- +// character string will be treated as a 128-bit GATT UUID. +// +// When specifying your UUIDs, feel free to use the format that suits you best (with or without dashes, dots in place of dashes, +// etc.) The string will first be sanitized, removing all non-hex characters, and then re-formatted appropriately with the dashes +// in their standard places: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx". +// +// By represetng a UUID in a custom class like this, we are able to give a UUID its own type, and use type safety to ensure that we +// don't confuse regular strings with GATT UUIDs throughout the codebase. +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +#pragma once + +#include +#include +#include +#include + +#include +#include "Logger.h" + +// "0000180A-0000-1000-8000-00805f9b34fb" +struct GattUuid +{ + static constexpr const char *kGattStandardUuidPart1Prefix = "0000"; + static constexpr const char *kGattStandardUuidSuffix = "-0000-1000-8000-00805f9b34fb"; + + // Construct a GattUuid from a partial or complete string UUID + // + // This constructor will do the best it can with the data it is given. It will first clean the input by removing all non-hex + // characters (see `clean`) and the remaining characters are processed in the following way: + // + // 4-character string is treated as a 16-bit UUID + // 8-character string is treated as a 32-bit UUID + // 32-character string is treated as a 128-bit UUID + // + // If the input string is not one of the above lengths, the UUID will be left uninitialized as an empty string with a bit + // count of 0. + // + // Finally, dashes are inserted into the string at the appropriate locations (see `dashify`). + GattUuid(const char *strUuid) + { + *this = GattUuid(std::string(strUuid)); + } + + // Construct a GattUuid from a partial or complete string UUID + // + // This constructor will do the best it can with the data it is given. It will first clean the input by removing all non-hex + // characters (see `clean`) and the remaining characters are processed in the following way: + // + // 4-character string is treated as a 16-bit UUID + // 8-character string is treated as a 32-bit UUID + // 32-character string is treated as a 128-bit UUID + // + // If the input string is not one of the above lengths, the UUID will be left uninitialized as an empty string with a bit + // count of 0. + // + // Finally, dashes are inserted into the string at the appropriate locations (see `dashify`). + GattUuid(std::string strUuid) + { + // Clean the string + strUuid = clean(strUuid); + + // It's hex, so each character represents 4 bits + bitCount = strUuid.length() * 4; + + if (bitCount == 16) + { + strUuid = kGattStandardUuidPart1Prefix + strUuid + kGattStandardUuidSuffix; + } + else if (bitCount == 32) + { + strUuid += kGattStandardUuidSuffix; + } + else if (bitCount != 128) + { + bitCount = 0; + strUuid = ""; + } + + uuid = dashify(strUuid); + } + + // Constructs a GattUuid from a 16-bit Uuid value + // + // The result will take the form: + // + // 0000????-0000-1000-8000-00805f9b34fb + // + // ...where "????" is replaced by the 4-digit hex value of `part` + GattUuid(const uint16_t part) + { + bitCount = 16; + char partStr[5]; + snprintf(partStr, sizeof(partStr), "%04x", part); + uuid = std::string(kGattStandardUuidPart1Prefix) + partStr + kGattStandardUuidSuffix; + } + + // Constructs a GattUuid from a 32-bit Uuid value + // + // The result will take the form: + // + // ????????-0000-1000-8000-00805f9b34fb + // + // ...where "????????" is replaced by the 8-digit hex value of `part` + GattUuid(const uint32_t part) + { + bitCount = 32; + char partStr[9]; + snprintf(partStr, sizeof(partStr), "%04x", part); + uuid = std::string(partStr) + kGattStandardUuidSuffix; + } + + // Constructs a GattUuid from a 5-part set of input values + // + // The result will take the form: + // + // 11111111-2222-3333-4444-555555555555 + // + // ...where each digit represents the part from which its hex digits will be pulled from. + // + // Note that `part5` is a 48-bit value and will be masked such that only the lower 48-bits of `part5` are used with all other + // bits ignored. + GattUuid(const uint32_t part1, const uint16_t part2, const uint16_t part3, const uint16_t part4, const uint64_t part5) + { + bitCount = 128; + char partsStr[37]; + uint32_t part5a = (part5 >> 4) & 0xffffffff; + uint32_t part5b = part5 & 0xffff; + snprintf(partsStr, sizeof(partsStr), "%08x-%04x-%04x-%04x-%08x%04x", part1, part2, part3, part4, part5a, part5b); + uuid = std::string(partsStr); + } + + // Returns the bit count of the input when the GattUuid was constructed. Valid values are 16, 32, 128. + // + // If the GattUuid was constructed imporperly, this method will return 0. + int getBitCount() const + { + return bitCount; + } + + // Returns the 16-bit portion of the GATT UUID or an empty string if the GattUuid was not created correctly + // + // Note that a 16-bit GATT UUID is only valid for standarg GATT UUIDs (prefixed with "0000" and ending with + // "0000-1000-8000-00805f9b34fb"). + std::string toString16() const + { + if (uuid.empty()) { return uuid; } + return uuid.substr(4, 4); + } + + // Returns the 32-bit portion of the GATT UUID or an empty string if the GattUuid was not created correctly + // + // Note that a 32-bit GATT UUID is only valid for standarg GATT UUIDs (ending with "0000-1000-8000-00805f9b34fb"). + std::string toString32() const + { + if (uuid.empty()) { return uuid; } + return uuid.substr(0, 8); + } + + // Returns the full 128-bit GATT UUID or an empty string if the GattUuid was not created correctly + std::string toString128() const + { + return uuid; + } + + // Returns a string form of the UUID, based on the bit count used when the UUID was created. A 16-bit UUID will return a + // 4-character hex string. A 32-bit UUID will return an 8-character hex string. Otherwise the UUID is assumed to be 128 bits + // (which is only true if it was created correctly) and the full UUID is returned + std::string toString() const + { + if (bitCount == 16) return toString16(); + if (bitCount == 32) return toString32(); + return toString128(); + } + + // Returns a new string containing the lower case contents of `strUuid` with all non-hex characters (0-9, A-F) removed + static std::string clean(const std::string &strUuid) + { + if (strUuid.empty()) return strUuid; + + // Lower case + std::string cleanStr = strUuid; + std::transform(cleanStr.begin(), cleanStr.end(), cleanStr.begin(), ::tolower); + + // Remove all non-hex characters + cleanStr.erase + ( + std::remove_if + ( + cleanStr.begin(), + cleanStr.end(), + [](char c) + { + return !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); + } + ), cleanStr.end() + ); + + return cleanStr; + } + + // Returns a clean string (see `clean`) that has dashes ('-') inserted at the proper locations. If the string is not a full + // UUID, this routine will cleanup and add as many dashes as possible until it runs out of characters. + // + // Example transforms: + // + // "0000180A-0000-1000-8000-00805f9b34fb" -> "0000180a-0000-1000-8000-00805f9b34fb" + // "0000180A00001000800000805f9b34fb" -> "0000180a-0000-1000-8000-00805f9b34fb" + // "0000180A/0000.1000_zzzzzz_8000+00805f9b34fb" -> "0000180a-0000-1000-8000-00805f9b34fb" + // "0000180A" -> "0000180a" + // "0000180A.0000.100" -> "0000180a-0000-100" + // "rqzp" -> "" + // + static std::string dashify(const std::string &str) + { + // Ensure we have a clean string to start with + std::string dashed = clean(str); + + // Add each dash, provided there are enough characters + if (dashed.length() > 8) { dashed.insert(8, 1, '-'); } + if (dashed.length() > 13) { dashed.insert(13, 1, '-'); } + if (dashed.length() > 18) { dashed.insert(18, 1, '-'); } + if (dashed.length() > 23) { dashed.insert(23, 1, '-'); } + return dashed; + } + +private: + + std::string uuid; + int bitCount; +}; -- cgit v1.2.3