diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f0b45e6..9691cb8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ # Changelog All notable changes to this project will be documented in this file. +## [Unreleased] 3.0.0-dev + +## Fixed +- `NimBLEUUID` construction from C strings now initializes the UUID type and parses 16-, 32-, and 128-bit UUID strings correctly. +- Converting `NimBLEAttValue` to Arduino `String` now preserves the full value length, including embedded null bytes. + +## Changed +- **C++20 is now required.** Support for C++11/14/17 has been removed; builds must use `-std=gnu++20` or newer. +- The `Has_data_size`, `Has_c_str_length` and `Has_value_type` SFINAE traits and the pre-C++17 `enable_if` overloads were replaced with C++20 concepts (`NimBLEDataContainer`, `NimBLEStringLike`, `NimBLECharArray`, `NimBLEByteValue`) and a single `NimBLEAsBytes` helper. +- `NimBLEAddress(std::string_view, uint8_t)` and the advertising/HID string setters (`setName`, `setShortName`, `setURI`, `setManufacturerData`, `setServiceData`, `addData`, `setManufacturer`) now take `std::string_view`. +- `NimBLEAdvertisedDevice::getManufacturerData`/`getServiceData` are now restricted to trivially copyable types and copy the bytes instead of casting the buffer. + +## Added +- `NimBLEAttValue` can be constructed from and converted to `std::string_view`. +- `std::string_view` and `std::span` can be passed to the `setValue`, `writeValue`, `notify` and `indicate` templates. + ## [2.5.0] 2026-04-01 ## Fixed diff --git a/src/NimBLEAddress.cpp b/src/NimBLEAddress.cpp index a631b25d..da895aac 100644 --- a/src/NimBLEAddress.cpp +++ b/src/NimBLEAddress.cpp @@ -76,7 +76,7 @@ NimBLEAddress::NimBLEAddress(ble_addr_t address) : ble_addr_t{address} {} * * BLE_ADDR_PUBLIC (0) * * BLE_ADDR_RANDOM (1) */ -NimBLEAddress::NimBLEAddress(const std::string& addr, uint8_t type) { +NimBLEAddress::NimBLEAddress(std::string_view addr, uint8_t type) { this->type = type; if (addr.length() == BLE_DEV_ADDR_LEN) { @@ -93,7 +93,7 @@ NimBLEAddress::NimBLEAddress(const std::string& addr, uint8_t type) { } *this = NimBLEAddress{}; - NIMBLE_LOGE(LOG_TAG, "Invalid address '%s'", addr.c_str()); + NIMBLE_LOGE(LOG_TAG, "Invalid address '%.*s'", static_cast(addr.length()), addr.data()); } // NimBLEAddress /** diff --git a/src/NimBLEAddress.h b/src/NimBLEAddress.h index 7e1701c6..a81e80a9 100644 --- a/src/NimBLEAddress.h +++ b/src/NimBLEAddress.h @@ -33,7 +33,7 @@ /**************************/ # include - +# include /** * @brief A BLE device address. * @@ -47,7 +47,7 @@ class NimBLEAddress : private ble_addr_t { NimBLEAddress() = default; NimBLEAddress(const ble_addr_t address); NimBLEAddress(const uint8_t address[BLE_DEV_ADDR_LEN], uint8_t type); - NimBLEAddress(const std::string& stringAddress, uint8_t type); + NimBLEAddress(std::string_view stringAddress, uint8_t type); NimBLEAddress(const uint64_t& address, uint8_t type); bool isRpa() const; diff --git a/src/NimBLEAdvertisedDevice.h b/src/NimBLEAdvertisedDevice.h index 6a41394e..81c4021c 100644 --- a/src/NimBLEAdvertisedDevice.h +++ b/src/NimBLEAdvertisedDevice.h @@ -34,8 +34,14 @@ # endif # include +# include +# include +# include +# include +# include class NimBLEScan; + /** * @brief A representation of a %BLE advertised device found by a scan. * @@ -109,11 +115,9 @@ class NimBLEAdvertisedDevice { * @details Use: getManufacturerData(skipSizeCheck); */ template + requires std::is_trivially_copyable_v T getManufacturerData(bool skipSizeCheck = false) const { - std::string data = getManufacturerData(); - if (!skipSizeCheck && data.size() < sizeof(T)) return T(); - const char* pData = data.data(); - return *((T*)pData); + return fromBytes(getManufacturerData(), skipSizeCheck); } /** @@ -126,11 +130,9 @@ class NimBLEAdvertisedDevice { * @details Use: getServiceData(skipSizeCheck); */ template + requires std::is_trivially_copyable_v T getServiceData(uint8_t index = 0, bool skipSizeCheck = false) const { - std::string data = getServiceData(index); - if (!skipSizeCheck && data.size() < sizeof(T)) return T(); - const char* pData = data.data(); - return *((T*)pData); + return fromBytes(getServiceData(index), skipSizeCheck); } /** @@ -143,16 +145,23 @@ class NimBLEAdvertisedDevice { * @details Use: getServiceData(skipSizeCheck); */ template + requires std::is_trivially_copyable_v T getServiceData(const NimBLEUUID& uuid, bool skipSizeCheck = false) const { - std::string data = getServiceData(uuid); - if (!skipSizeCheck && data.size() < sizeof(T)) return T(); - const char* pData = data.data(); - return *((T*)pData); + return fromBytes(getServiceData(uuid), skipSizeCheck); } private: friend class NimBLEScan; + // Copies the bytes into a T instead of reinterpreting the buffer, avoiding unaligned/aliased access. + template + static T fromBytes(std::string_view data, bool skipSizeCheck) { + T value{}; + if (!skipSizeCheck && data.size() < sizeof(T)) return value; + std::memcpy(&value, data.data(), std::min(data.size(), sizeof(T))); + return value; + } + NimBLEAdvertisedDevice(const ble_gap_event* event, uint8_t eventType); void update(const ble_gap_event* event, uint8_t eventType); uint8_t findAdvField(uint8_t type, uint8_t index = 0, size_t* data_loc = nullptr) const; diff --git a/src/NimBLEAdvertisementData.cpp b/src/NimBLEAdvertisementData.cpp index 5f598978..2171f0d8 100644 --- a/src/NimBLEAdvertisementData.cpp +++ b/src/NimBLEAdvertisementData.cpp @@ -284,7 +284,7 @@ bool NimBLEAdvertisementData::setManufacturerData(const uint8_t* data, size_t le * @param [in] data The manufacturer data to advertise. * @return True if successful. */ -bool NimBLEAdvertisementData::setManufacturerData(const std::string& data) { +bool NimBLEAdvertisementData::setManufacturerData(std::string_view data) { return setManufacturerData(reinterpret_cast(data.data()), data.length()); } // setManufacturerData @@ -302,7 +302,7 @@ bool NimBLEAdvertisementData::setManufacturerData(const std::vector& da * @param [in] uri The uri to advertise. * @return True if successful. */ -bool NimBLEAdvertisementData::setURI(const std::string& uri) { +bool NimBLEAdvertisementData::setURI(std::string_view uri) { if (uri.length() > BLE_HS_ADV_MAX_FIELD_SZ) { NIMBLE_LOGE(LOG_TAG, "URI too long"); return false; @@ -312,7 +312,7 @@ bool NimBLEAdvertisementData::setURI(const std::string& uri) { uint8_t length = 2 + uri.length(); data[0] = length - 1; data[1] = BLE_HS_ADV_TYPE_URI; - memcpy(&data[2], uri.c_str(), uri.length()); + memcpy(&data[2], uri.data(), uri.length()); return addData(data, length); } // setURI @@ -324,7 +324,7 @@ bool NimBLEAdvertisementData::setURI(const std::string& uri) { * and the data type will be set to incomplete name. * @return True if successful. */ -bool NimBLEAdvertisementData::setName(const std::string& name, bool isComplete) { +bool NimBLEAdvertisementData::setName(std::string_view name, bool isComplete) { if (name.length() > BLE_HS_ADV_MAX_FIELD_SZ) { NIMBLE_LOGE(LOG_TAG, "Name too long - truncating"); isComplete = false; @@ -334,7 +334,7 @@ bool NimBLEAdvertisementData::setName(const std::string& name, bool isComplete) uint8_t length = 2 + std::min(name.length(), BLE_HS_ADV_MAX_FIELD_SZ); data[0] = length - 1; data[1] = isComplete ? BLE_HS_ADV_TYPE_COMP_NAME : BLE_HS_ADV_TYPE_INCOMP_NAME; - memcpy(&data[2], name.c_str(), std::min(name.length(), BLE_HS_ADV_MAX_FIELD_SZ)); + memcpy(&data[2], name.data(), std::min(name.length(), BLE_HS_ADV_MAX_FIELD_SZ)); return addData(data, length); } // setName @@ -343,7 +343,7 @@ bool NimBLEAdvertisementData::setName(const std::string& name, bool isComplete) * @param [in] name The short name of the device. * @return True if successful. */ -bool NimBLEAdvertisementData::setShortName(const std::string& name) { +bool NimBLEAdvertisementData::setShortName(std::string_view name) { return setName(name, false); } // setShortName @@ -501,7 +501,7 @@ bool NimBLEAdvertisementData::setServiceData(const NimBLEUUID& uuid, const uint8 * @return True if the service data was set successfully. * @note If data length is 0 the service data will not be advertised. */ -bool NimBLEAdvertisementData::setServiceData(const NimBLEUUID& uuid, const std::string& data) { +bool NimBLEAdvertisementData::setServiceData(const NimBLEUUID& uuid, std::string_view data) { return setServiceData(uuid, reinterpret_cast(data.data()), data.length()); } // setServiceData diff --git a/src/NimBLEAdvertisementData.h b/src/NimBLEAdvertisementData.h index 9c1ae3f7..6813a8e8 100644 --- a/src/NimBLEAdvertisementData.h +++ b/src/NimBLEAdvertisementData.h @@ -23,6 +23,7 @@ # include # include +# include # include class NimBLEUUID; @@ -46,11 +47,11 @@ class NimBLEAdvertisementData { bool removeServiceUUID(const char* serviceUUID); bool removeServices(); bool setManufacturerData(const uint8_t* data, size_t length); - bool setManufacturerData(const std::string& data); + bool setManufacturerData(std::string_view data); bool setManufacturerData(const std::vector& data); - bool setURI(const std::string& uri); - bool setName(const std::string& name, bool isComplete = true); - bool setShortName(const std::string& name); + bool setURI(std::string_view uri); + bool setName(std::string_view name, bool isComplete = true); + bool setShortName(std::string_view name); bool setCompleteServices(const NimBLEUUID& uuid); bool setCompleteServices16(const std::vector& uuids); bool setCompleteServices32(const std::vector& uuids); @@ -58,7 +59,7 @@ class NimBLEAdvertisementData { bool setPartialServices16(const std::vector& uuids); bool setPartialServices32(const std::vector& uuids); bool setServiceData(const NimBLEUUID& uuid, const uint8_t* data, size_t length); - bool setServiceData(const NimBLEUUID& uuid, const std::string& data); + bool setServiceData(const NimBLEUUID& uuid, std::string_view data); bool setServiceData(const NimBLEUUID& uuid, const std::vector& data); bool removeData(uint8_t type); void clearData(); diff --git a/src/NimBLEAdvertising.cpp b/src/NimBLEAdvertising.cpp index b196c153..97702aa1 100644 --- a/src/NimBLEAdvertising.cpp +++ b/src/NimBLEAdvertising.cpp @@ -516,7 +516,7 @@ bool NimBLEAdvertising::addTxPower() { * @note If the name is too long it will be truncated. * @details If scan response is enabled the name will be set in the scan response data. */ -bool NimBLEAdvertising::setName(const std::string& name) { +bool NimBLEAdvertising::setName(std::string_view name) { if (m_scanResp && m_scanData.setName(name)) { m_advDataSet = false; return true; @@ -552,7 +552,7 @@ bool NimBLEAdvertising::setManufacturerData(const uint8_t* data, size_t length) * @param [in] data The data to advertise. * @return True if the manufacturer data was set successfully. */ -bool NimBLEAdvertising::setManufacturerData(const std::string& data) { +bool NimBLEAdvertising::setManufacturerData(std::string_view data) { return setManufacturerData(reinterpret_cast(data.data()), data.length()); } // setManufacturerData @@ -570,7 +570,7 @@ bool NimBLEAdvertising::setManufacturerData(const std::vector& data) { * @param [in] uri The URI to advertise. * @return True if the URI was set successfully. */ -bool NimBLEAdvertising::setURI(const std::string& uri) { +bool NimBLEAdvertising::setURI(std::string_view uri) { if (!m_advData.setURI(uri)) { if (!m_scanData.setURI(uri)) { return false; @@ -618,7 +618,7 @@ bool NimBLEAdvertising::setServiceData(const NimBLEUUID& uuid, const std::vector * @return True if the service data was set successfully. * @note If data length is 0 the service data will not be advertised. */ -bool NimBLEAdvertising::setServiceData(const NimBLEUUID& uuid, const std::string& data) { +bool NimBLEAdvertising::setServiceData(const NimBLEUUID& uuid, std::string_view data) { return setServiceData(uuid, reinterpret_cast(data.data()), data.length()); } // setServiceData diff --git a/src/NimBLEAdvertising.h b/src/NimBLEAdvertising.h index 6ddeae26..6247cb85 100644 --- a/src/NimBLEAdvertising.h +++ b/src/NimBLEAdvertising.h @@ -38,6 +38,7 @@ # include # include +# include # include class NimBLEAdvertising; @@ -79,13 +80,13 @@ class NimBLEAdvertising { bool setAppearance(uint16_t appearance); bool setPreferredParams(uint16_t minInterval, uint16_t maxInterval); bool addTxPower(); - bool setName(const std::string& name); + bool setName(std::string_view name); bool setManufacturerData(const uint8_t* data, size_t length); - bool setManufacturerData(const std::string& data); + bool setManufacturerData(std::string_view data); bool setManufacturerData(const std::vector& data); - bool setURI(const std::string& uri); + bool setURI(std::string_view uri); bool setServiceData(const NimBLEUUID& uuid, const uint8_t* data, size_t length); - bool setServiceData(const NimBLEUUID& uuid, const std::string& data); + bool setServiceData(const NimBLEUUID& uuid, std::string_view data); bool setServiceData(const NimBLEUUID& uuid, const std::vector& data); private: diff --git a/src/NimBLEAttValue.cpp b/src/NimBLEAttValue.cpp index 9ccf7c21..7ae1a0f1 100644 --- a/src/NimBLEAttValue.cpp +++ b/src/NimBLEAttValue.cpp @@ -58,13 +58,10 @@ unsigned char __atomic_exchange_1(volatile void* ptr, unsigned char val, int) { # endif static const char* LOG_TAG = "NimBLEAttValue"; -ble_npl_mutex NimBLEAttValue::Lock::m_mutex{}; -std::atomic NimBLEAttValue::Lock::m_initialized{false}; -std::atomic NimBLEAttValue::Lock::m_activeCount{0}; // Default constructor implementation. NimBLEAttValue::NimBLEAttValue(uint16_t init_len, uint16_t max_len) - : m_attr_value{static_cast(calloc(init_len + 1, 1))}, + : m_attr_value{init_len > 0 ? static_cast(calloc(init_len + 1, 1)) : &EMPTY_VALUE}, m_attr_max_len{std::min(BLE_ATT_ATTR_MAX_LEN, max_len)}, m_attr_len{}, m_capacity{init_len} @@ -75,16 +72,23 @@ NimBLEAttValue::NimBLEAttValue(uint16_t init_len, uint16_t max_len) { NIMBLE_CPP_DEBUG_ASSERT(m_attr_value); if (m_attr_value == nullptr) { + // Keep max_len so the object can still allocate later; callers detect the failure by size() != requested len. NIMBLE_LOGE(LOG_TAG, "Failed to allocate value buffer"); - m_attr_len = 0; - m_capacity = 0; - m_attr_max_len = 0; + m_attr_value = &EMPTY_VALUE; + m_capacity = 0; } } // Value constructor implementation. -NimBLEAttValue::NimBLEAttValue(const uint8_t* value, uint16_t len, uint16_t max_len) : NimBLEAttValue(len, max_len) { - if (m_attr_value != nullptr && len > 0) { +NimBLEAttValue::NimBLEAttValue(const uint8_t* value, uint16_t len, uint16_t max_len) + : NimBLEAttValue(len > std::min(max_len, BLE_ATT_ATTR_MAX_LEN) ? 0 : len, max_len) { + // Same behavior as append(): a value larger than max_len is rejected, leaving the value empty. + if (len > m_attr_max_len) { + NIMBLE_LOGE(LOG_TAG, "val > max, len=%u, max=%u", len, m_attr_max_len); + return; + } + + if (len > 0 && m_capacity >= len) { memcpy(m_attr_value, value, len); m_attr_len = len; } @@ -92,22 +96,23 @@ NimBLEAttValue::NimBLEAttValue(const uint8_t* value, uint16_t len, uint16_t max_ // Destructor implementation. NimBLEAttValue::~NimBLEAttValue() { - if (m_attr_value != nullptr) { - free(m_attr_value); - } + free(ownedBuffer()); } // Move assignment operator implementation. NimBLEAttValue& NimBLEAttValue::operator=(NimBLEAttValue&& source) { if (this != &source) { Lock lock; - free(m_attr_value); + free(ownedBuffer()); m_attr_value = source.m_attr_value; m_attr_max_len = source.m_attr_max_len; m_attr_len = source.m_attr_len; m_capacity = source.m_capacity; setTimeStamp(source.getTimeStamp()); - source.m_attr_value = nullptr; + source.m_attr_value = &EMPTY_VALUE; + source.m_attr_len = 0; + source.m_capacity = 0; + source.setTimeStamp(0); } return *this; @@ -123,8 +128,12 @@ NimBLEAttValue& NimBLEAttValue::operator=(const NimBLEAttValue& source) { // Copy all the data from the source object to this object, including allocated space. bool NimBLEAttValue::deepCopy(const NimBLEAttValue& source) { + if (this == &source) { + return true; // Self-copy; reallocating would leave an aliased, uninitialized buffer when source is EMPTY_VALUE. + } + Lock lock; - uint8_t* res = static_cast(realloc(m_attr_value, source.m_capacity + 1)); + uint8_t* res = static_cast(realloc(ownedBuffer(), source.m_capacity + 1)); NIMBLE_CPP_DEBUG_ASSERT(res); if (res == nullptr) { NIMBLE_LOGE(LOG_TAG, "Failed to realloc deepCopy"); @@ -144,9 +153,11 @@ bool NimBLEAttValue::deepCopy(const NimBLEAttValue& source) { bool NimBLEAttValue::setValue(const uint8_t* value, uint16_t len) { Lock lock; m_attr_len = 0; // Just set the value length to 0 and append instead of repeating code. - m_attr_value[0] = '\0'; // Set the first byte to 0 incase the len of the new value is 0. + if (m_attr_value != &EMPTY_VALUE) { + m_attr_value[0] = '\0'; // Set the first byte to 0 incase the len of the new value is 0. + } append(value, len); - return memcmp(m_attr_value, value, len) == 0 && m_attr_len == len; + return m_attr_len == len && memcmp(m_attr_value, value, len) == 0; } // Append the new data, allocate as necessary. @@ -165,7 +176,7 @@ NimBLEAttValue& NimBLEAttValue::append(const uint8_t* value, uint16_t len) { uint16_t new_len = m_attr_len + len; bool grow = new_len > m_capacity; if (grow) { - res = static_cast(realloc(m_attr_value, (new_len + 1))); + res = static_cast(realloc(ownedBuffer(), (new_len + 1))); } NIMBLE_CPP_DEBUG_ASSERT(res); if (res == nullptr) { diff --git a/src/NimBLEAttValue.h b/src/NimBLEAttValue.h index 4850940a..98ec4949 100644 --- a/src/NimBLEAttValue.h +++ b/src/NimBLEAttValue.h @@ -37,6 +37,10 @@ # endif # include +# include +# include +# include +# include # include # include # include @@ -69,28 +73,44 @@ # error NIMBLE_CPP_ATT_VALUE_INIT_LENGTH cannot be less than 1; Range = 1 : 512 # endif -/* Used to determine if the type passed to a template has a data() and size() method. */ -template -struct Has_data_size : std::false_type {}; - +/** @brief Satisfied by types with `data()` and `size()` methods (std::vector, std::array, std::string, std::span, etc.). */ template -struct Has_data_size().data())), decltype(void(std::declval().size()))> - : std::true_type {}; +concept NimBLEDataContainer = requires(const T& t) { + t.data(); + t.size(); +}; -/* Used to determine if the type passed to a template has a c_str() and length() method. */ -template -struct Has_c_str_length : std::false_type {}; +/** @brief Satisfied by types with `c_str()` and `length()` methods (Arduino String, etc.). */ +template +concept NimBLEStringLike = requires(const T& t) { + t.c_str(); + t.length(); +}; +/** @brief Satisfied by arrays of char, e.g. string literals and char buffers. */ template -struct Has_c_str_length().c_str())), decltype(void(std::declval().length()))> - : std::true_type {}; +concept NimBLECharArray = std::is_bounded_array_v && std::same_as, char>; -/* Used to determine if the type passed to a template has a value_type member (std::vector, std::array, std::string, etc.). */ -template -struct Has_value_type : std::false_type {}; +/** @brief Satisfied by types that can be sent/stored as raw bytes through the value templates. */ +template +concept NimBLEByteValue = !std::is_pointer_v; +/** + * @brief Get the bytes of a value as a span. + * @details Containers with `data()` and `size()` are viewed element-wise (size is scaled by the element size), + * types with `c_str()` and `length()` are viewed as characters, everything else is viewed as its object representation. + * The returned span references the argument and must not outlive it. + */ template -struct Has_value_type : std::true_type {}; +std::span NimBLEAsBytes(const T& v) { + if constexpr (NimBLEDataContainer) { + return {reinterpret_cast(v.data()), v.size() * sizeof(*v.data())}; + } else if constexpr (NimBLEStringLike) { + return {reinterpret_cast(v.c_str()), static_cast(v.length())}; + } else { + return {reinterpret_cast(&v), sizeof(T)}; + } +} /** * @brief A specialized container class to hold BLE attribute values. @@ -99,7 +119,10 @@ struct Has_value_type : std:: * many different container classes. */ class NimBLEAttValue { - uint8_t* m_attr_value{}; + // Shared, never-freed buffer that m_attr_value points to when nothing is allocated (reset or allocation failure). + // Non-const so it can be pointed at by a uint8_t*. + static inline uint8_t EMPTY_VALUE{0}; + uint8_t* m_attr_value{&EMPTY_VALUE}; uint16_t m_attr_max_len{}; uint16_t m_attr_len{}; uint16_t m_capacity{}; @@ -108,11 +131,19 @@ class NimBLEAttValue { # endif bool deepCopy(const NimBLEAttValue& source); + // The heap buffer owned by this object, or nullptr if m_attr_value points at EMPTY_VALUE. + uint8_t* ownedBuffer() const { return m_attr_value == &EMPTY_VALUE ? nullptr : m_attr_value; } + + // Clamp so a large container size can't wrap when narrowed to uint16_t; the constructor then rejects it as > max_len. + static constexpr uint16_t clampLen(size_t len) { + return len > UINT16_MAX ? UINT16_MAX : static_cast(len); + } + public: class Lock { - static ble_npl_mutex m_mutex; - static std::atomic m_initialized; - static std::atomic m_activeCount; + static inline ble_npl_mutex m_mutex{}; + static inline std::atomic m_initialized{false}; + static inline std::atomic m_activeCount{0}; bool m_locked{false}; public: @@ -191,7 +222,7 @@ class NimBLEAttValue { * @param[in] max_len The max size in bytes that the value can be. */ NimBLEAttValue(const char* value, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) - : NimBLEAttValue((uint8_t*)value, (uint16_t)strlen(value), max_len) {} + : NimBLEAttValue(reinterpret_cast(value), clampLen(strlen(value)), max_len) {} /** * @brief Construct with an initializer list. @@ -199,23 +230,31 @@ class NimBLEAttValue { * @param[in] max_len The max size in bytes that the value can be. */ NimBLEAttValue(std::initializer_list list, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) - : NimBLEAttValue(list.begin(), list.size(), max_len) {} + : NimBLEAttValue(list.begin(), clampLen(list.size()), max_len) {} /** * @brief Construct with an initial value from a std::string. * @param str A std::string containing to the initial value to set. * @param[in] max_len The max size in bytes that the value can be. */ - NimBLEAttValue(const std::string str, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) - : NimBLEAttValue(reinterpret_cast(&str[0]), str.length(), max_len) {} + NimBLEAttValue(const std::string& str, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + : NimBLEAttValue(reinterpret_cast(str.data()), clampLen(str.length()), max_len) {} + + /** + * @brief Construct with an initial value from a std::string_view. + * @param str A std::string_view containing the initial value to set. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(std::string_view str, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + : NimBLEAttValue(reinterpret_cast(str.data()), clampLen(str.size()), max_len) {} /** * @brief Construct with an initial value from a std::vector. * @param vec A std::vector containing to the initial value to set. * @param[in] max_len The max size in bytes that the value can be. */ - NimBLEAttValue(const std::vector vec, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) - : NimBLEAttValue(&vec[0], vec.size(), max_len) {} + NimBLEAttValue(const std::vector& vec, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + : NimBLEAttValue(vec.data(), clampLen(vec.size()), max_len) {} # if NIMBLE_CPP_ARDUINO_STRING_AVAILABLE /** @@ -223,8 +262,16 @@ class NimBLEAttValue { * @param str An Arduino String containing to the initial value to set. * @param[in] max_len The max size in bytes that the value can be. */ - NimBLEAttValue(const String str, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) - : NimBLEAttValue(reinterpret_cast(str.c_str()), str.length(), max_len) {} + NimBLEAttValue(const String& str, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + : NimBLEAttValue(reinterpret_cast(str.c_str()), clampLen(str.length()), max_len) {} + + /** + * @brief Construct with an initial value from an Arduino String rvalue. + * @param str An Arduino String containing to the initial value to set. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(String&& str, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + : NimBLEAttValue(reinterpret_cast(str.c_str()), clampLen(str.length()), max_len) {} # endif /** @brief Copy constructor */ @@ -233,6 +280,26 @@ class NimBLEAttValue { /** @brief Move constructor */ NimBLEAttValue(NimBLEAttValue&& source) { *this = std::move(source); } + /** + * @brief Construct with an initial value from a std::string rvalue. + * @param value A std::string containing the initial value to set. + * @param[in] max_len The max size in bytes that the value can be. + * @note The bytes are copied into this object's own buffer; the string's storage is not taken over. + * If the string is larger than max_len the value is left empty. + */ + NimBLEAttValue(std::string&& value, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) noexcept + : NimBLEAttValue(reinterpret_cast(value.data()), clampLen(value.size()), max_len) {} + + /** + * @brief Construct with an initial value from a std::vector rvalue. + * @param value A std::vector containing the initial value to set. + * @param[in] max_len The max size in bytes that the value can be. + * @note The bytes are copied into this object's own buffer; the vector's storage is not taken over. + * If the vector is larger than max_len the value is left empty. + */ + NimBLEAttValue(std::vector&& value, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) noexcept + : NimBLEAttValue(value.data(), clampLen(value.size()), max_len) {} + /** @brief Destructor */ ~NimBLEAttValue(); @@ -332,109 +399,23 @@ class NimBLEAttValue { /*********************** Template Functions ************************/ -# if __cplusplus < 201703L - /** - * @brief Template to set value to the value of a char array using strnlen. - * @param [in] s A reference to a char array. - * @details Only used for char array types to correctly determine length via strnlen. - */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && std::is_same::type, char>::value, bool>::type -# endif - setValue(const T& s) { - return setValue(reinterpret_cast(s), strnlen(s, sizeof(T))); - } - /** - * @brief Template to set value to the value of val. - * @param [in] v The value to set. - * @details Only used for types without a `c_str()` and `length()` or `data()` and `size()` method. - * size must be evaluatable by `sizeof()`. + * @brief Template to set the value from a container, string, char array or trivially copyable object. + * @tparam T The type of the value, must not be a pointer. + * @param [in] s The value to set. + * @details Containers with `data()` and `size()` and types with `c_str()` and `length()` (including + * `std::string_view` and `std::span`) are copied by their contents, char arrays are measured with `strnlen`, + * anything else is copied by its object representation using `sizeof()`. */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && !Has_c_str_length::value && !Has_data_size::value && - !(std::is_array::value && std::is_same::type, char>::value), - bool>::type -# endif - setValue(const T& v) { - return setValue(reinterpret_cast(&v), sizeof(T)); - } - - /** - * @brief Template to set value to the value of val. - * @param [in] s The value to set. - * @details Only used if the has a `c_str()` method. - */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && !Has_data_size::value, bool>::type -# endif - setValue(const T& s) { - return setValue(reinterpret_cast(s.c_str()), s.length()); - } - - /** - * @brief Template to set value to the value of val. - * @param [in] v The value to set. - * @details Only used if the has a `data()` and `size()` method with `value_type`. - * Correctly calculates byte size for containers with multi-byte element types. - */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && Has_value_type::value, bool>::type -# endif - setValue(const T& v) { - return setValue(reinterpret_cast(v.data()), v.size() * sizeof(typename T::value_type)); - } - - /** - * @brief Template to set value to the value of val. - * @param [in] v The value to set. - * @details Only used if the has a `data()` and `size()` method without `value_type`. - */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && !Has_value_type::value, bool>::type -# endif - setValue(const T& v) { - return setValue(reinterpret_cast(v.data()), v.size()); - } - -# else - /** - * @brief Template to set value to the value of val. - * @param [in] s The value to set. - * @note This function is only available if the type T is not a pointer. - */ - template - typename std::enable_if::value, bool>::type setValue(const T& s) { - if constexpr (Has_data_size::value) { - if constexpr (Has_value_type::value) { - return setValue(reinterpret_cast(s.data()), s.size() * sizeof(typename T::value_type)); - } else { - return setValue(reinterpret_cast(s.data()), s.size()); - } - } else if constexpr (Has_c_str_length::value) { - return setValue(reinterpret_cast(s.c_str()), s.length()); - } else if constexpr (std::is_array::value && std::is_same::type, char>::value) { + template + bool setValue(const T& s) { + if constexpr (NimBLECharArray) { return setValue(reinterpret_cast(s), strnlen(s, sizeof(s))); } else { - return setValue(reinterpret_cast(&s), sizeof(s)); + const auto bytes = NimBLEAsBytes(s); + return setValue(bytes.data(), clampLen(bytes.size())); } } -# endif /** * @brief Template to return the value as a . @@ -480,6 +461,12 @@ class NimBLEAttValue { return std::string(reinterpret_cast(m_attr_value), m_attr_len); } + /** + * @brief Operator; Get a std::string_view of the value. + * @note See data() for thread safety restrictions, the view is invalidated by a concurrent update. + */ + operator std::string_view() const { return std::string_view(reinterpret_cast(m_attr_value), m_attr_len); } + /** @brief Operator; Get the value as a const uint8_t*. */ operator const uint8_t*() const { return m_attr_value; } @@ -519,7 +506,7 @@ class NimBLEAttValue { /** @brief Operator; Get the value as an Arduino String value. */ operator String() const { Lock lock; - return String(reinterpret_cast(m_attr_value)); + return String(reinterpret_cast(m_attr_value), m_attr_len); } # endif }; diff --git a/src/NimBLECharacteristic.h b/src/NimBLECharacteristic.h index f81c2dde..d82097cb 100644 --- a/src/NimBLECharacteristic.h +++ b/src/NimBLECharacteristic.h @@ -80,193 +80,34 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute { /*********************** Template Functions ************************/ -# if __cplusplus < 201703L /** - * @brief Template to send a notification with a value from a struct or array. - * @param [in] v The value to send. + * @brief Template to send a notification from a container, string or trivially copyable object. + * @tparam T The type of the value, must not be a pointer or array. + * @param [in] value The value to send. * @param [in] connHandle Optional, a connection handle to send the notification to. - * @details size must be evaluatable by `sizeof()`. + * @details Types with `data()` and `size()` or `c_str()` and `length()` (including `std::string_view` and + * `std::span`) are sent by their contents, anything else is sent by its object representation using `sizeof()`. */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && !std::is_array::value && !Has_c_str_length::value && - !Has_data_size::value, - bool>::type -# endif - notify(const T& v, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const { - return notify(reinterpret_cast(&v), sizeof(T), connHandle); + template + requires(!std::is_array_v) + bool notify(const T& value, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const { + const auto bytes = NimBLEAsBytes(value); + return notify(bytes.data(), bytes.size(), connHandle); } /** - * @brief Template to send a notification with a value from a class that has a c_str() and length() method. - * @param [in] s The value to send. - * @param [in] connHandle Optional, a connection handle to send the notification to. - */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && !Has_data_size::value, bool>::type -# endif - notify(const T& s, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const { - return notify(reinterpret_cast(s.c_str()), s.length(), connHandle); - } - - /** - * @brief Template to send a notification with a value from a class that has a data() and size() method with value_type. - * @param [in] v The value to send. - * @param [in] connHandle Optional, a connection handle to send the notification to. - * @details Correctly calculates byte size for containers with multi-byte element types. - */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && Has_value_type::value, bool>::type -# endif - notify(const T& v, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const { - return notify(reinterpret_cast(v.data()), v.size() * sizeof(typename T::value_type), connHandle); - } - - /** - * @brief Template to send a notification with a value from a class that has a data() and size() method without value_type. - * @param [in] v The value to send. - * @param [in] connHandle Optional, a connection handle to send the notification to. - */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && !Has_value_type::value, bool>::type -# endif - notify(const T& v, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const { - return notify(reinterpret_cast(v.data()), v.size(), connHandle); - } - - /** - * @brief Template to send an indication with a value from a struct or array. - * @param [in] v The value to send. - * @param [in] connHandle Optional, a connection handle to send the notification to. - * @details size must be evaluatable by `sizeof()`. - */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && !std::is_array::value && !Has_c_str_length::value && - !Has_data_size::value, - bool>::type -# endif - indicate(const T& v, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const { - return indicate(reinterpret_cast(&v), sizeof(T), connHandle); - } - - /** - * @brief Template to send a indication with a value from a class that has a c_str() and length() method. - * @param [in] s The value to send. - * @param [in] connHandle Optional, a connection handle to send the notification to. - */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && !Has_data_size::value, bool>::type -# endif - indicate(const T& s, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const { - return indicate(reinterpret_cast(s.c_str()), s.length(), connHandle); - } - - /** - * @brief Template to send a indication with a value from a class that has a data() and size() method with value_type. - * @param [in] v The value to send. - * @param [in] connHandle Optional, a connection handle to send the notification to. - * @details Correctly calculates byte size for containers with multi-byte element types. - */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && Has_value_type::value, bool>::type -# endif - indicate(const T& v, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const { - return indicate(reinterpret_cast(v.data()), v.size() * sizeof(typename T::value_type), connHandle); - } - - /** - * @brief Template to send a indication with a value from a class that has a data() and size() method without value_type. - * @param [in] v The value to send. - * @param [in] connHandle Optional, a connection handle to send the notification to. - */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && !Has_value_type::value, bool>::type -# endif - indicate(const T& v, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const { - return indicate(reinterpret_cast(v.data()), v.size(), connHandle); - } - -# else - - /** - * @brief Template to send a notification for classes which may have - * data()/size() or c_str()/length() methods. Falls back to sending - * the data by casting the first element of the array to a uint8_t - * pointer and getting the length of the array using sizeof. - * @tparam T The a reference to a class containing the data to send. - * @param[in] value The value to set. - * @param[in] connHandle The connection handle to send the notification to. - * @note This function is only available if the type T is not a pointer. - */ - template - typename std::enable_if::value && !std::is_array::value, bool>::type notify( - const T& value, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const { - if constexpr (Has_data_size::value) { - if constexpr (Has_value_type::value) { - return notify(reinterpret_cast(value.data()), - value.size() * sizeof(typename T::value_type), - connHandle); - } else { - return notify(reinterpret_cast(value.data()), value.size(), connHandle); - } - } else if constexpr (Has_c_str_length::value) { - return notify(reinterpret_cast(value.c_str()), value.length(), connHandle); - } else { - return notify(reinterpret_cast(&value), sizeof(value), connHandle); - } - } - - /** - * @brief Template to send an indication for classes which may have - * data()/size() or c_str()/length() methods. Falls back to sending - * the data by casting the first element of the array to a uint8_t - * pointer and getting the length of the array using sizeof. - * @tparam T The a reference to a class containing the data to send. - * @param[in] value The value to set. - * @param[in] connHandle The connection handle to send the indication to. - * @note This function is only available if the type T is not a pointer. + * @brief Template to send an indication from a container, string or trivially copyable object. + * @tparam T The type of the value, must not be a pointer or array. + * @param [in] value The value to send. + * @param [in] connHandle Optional, a connection handle to send the indication to. + * @details See notify(const T&, uint16_t) for how the bytes are determined. */ - template - typename std::enable_if::value && !std::is_array::value, bool>::type indicate( - const T& value, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const { - if constexpr (Has_data_size::value) { - if constexpr (Has_value_type::value) { - return indicate(reinterpret_cast(value.data()), - value.size() * sizeof(typename T::value_type), - connHandle); - } else { - return indicate(reinterpret_cast(value.data()), value.size(), connHandle); - } - } else if constexpr (Has_c_str_length::value) { - return indicate(reinterpret_cast(value.c_str()), value.length(), connHandle); - } else { - return indicate(reinterpret_cast(&value), sizeof(value), connHandle); - } + template + requires(!std::is_array_v) + bool indicate(const T& value, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const { + const auto bytes = NimBLEAsBytes(value); + return indicate(bytes.data(), bytes.size(), connHandle); } -# endif private: friend class NimBLEServer; diff --git a/src/NimBLEClient.cpp b/src/NimBLEClient.cpp index 98d045e6..298f782b 100644 --- a/src/NimBLEClient.cpp +++ b/src/NimBLEClient.cpp @@ -1184,7 +1184,7 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) { } NimBLEAttValue value(totalLen, BLE_ATT_ATTR_MAX_LEN); - if (value.data() == nullptr) { + if (value.capacity() < totalLen) { NIMBLE_LOGE(LOG_TAG, "Failed to allocate notification value"); return BLE_ATT_ERR_INSUFFICIENT_RES; } diff --git a/src/NimBLECppVersion.h b/src/NimBLECppVersion.h index 4df53be7..479fe014 100644 --- a/src/NimBLECppVersion.h +++ b/src/NimBLECppVersion.h @@ -18,6 +18,11 @@ #ifndef NIMBLE_CPP_VERSION_H_ #define NIMBLE_CPP_VERSION_H_ +// GCC 10 reports 201709L for -std=c++20 (then called c++2a). +#if defined(__cplusplus) && __cplusplus < 201709L +# error "NimBLE-Arduino requires C++20 or later; compile with -std=gnu++20 (or newer)." +#endif + /** @brief NimBLE-Arduino library major version number. */ #define NIMBLE_CPP_VERSION_MAJOR 3 diff --git a/src/NimBLEExtAdvertising.cpp b/src/NimBLEExtAdvertising.cpp index 12f88057..b570ecf8 100644 --- a/src/NimBLEExtAdvertising.cpp +++ b/src/NimBLEExtAdvertising.cpp @@ -554,7 +554,7 @@ bool NimBLEExtAdvertisement::addData(const uint8_t* data, size_t length) { * @param [in] data The data to be added to the payload. * @return True if successful, false if the data is too large. */ -bool NimBLEExtAdvertisement::addData(const std::string& data) { +bool NimBLEExtAdvertisement::addData(std::string_view data) { if (m_payload.size() + data.length() > MYNEWT_VAL(BLE_EXT_ADV_MAX_SIZE)) { return false; } @@ -633,7 +633,7 @@ bool NimBLEExtAdvertisement::setManufacturerData(const uint8_t* data, size_t len * @param [in] data The manufacturer data to advertise. * @return True if successful. */ -bool NimBLEExtAdvertisement::setManufacturerData(const std::string& data) { +bool NimBLEExtAdvertisement::setManufacturerData(std::string_view data) { return setManufacturerData(reinterpret_cast(data.data()), data.length()); } // setManufacturerData @@ -651,7 +651,7 @@ bool NimBLEExtAdvertisement::setManufacturerData(const std::vector& dat * @param [in] uri The uri to advertise. * @return True if successful. */ -bool NimBLEExtAdvertisement::setURI(const std::string& uri) { +bool NimBLEExtAdvertisement::setURI(std::string_view uri) { if (uri.length() > 0xFF - 1) { NIMBLE_LOGE(LOG_TAG, "URI too long!"); return false; @@ -674,7 +674,7 @@ bool NimBLEExtAdvertisement::setURI(const std::string& uri) { * @param [in] isComplete If true the name is complete, if false it is shortened. * @return True if successful. */ -bool NimBLEExtAdvertisement::setName(const std::string& name, bool isComplete) { +bool NimBLEExtAdvertisement::setName(std::string_view name, bool isComplete) { if (name.length() > 0xFF - 1) { NIMBLE_LOGE(LOG_TAG, "Name too long!"); return false; @@ -976,7 +976,7 @@ bool NimBLEExtAdvertisement::setServiceData(const NimBLEUUID& uuid, const uint8_ * @return True if the service data was set successfully. * @note If data length is 0 the service data will not be advertised. */ -bool NimBLEExtAdvertisement::setServiceData(const NimBLEUUID& uuid, const std::string& data) { +bool NimBLEExtAdvertisement::setServiceData(const NimBLEUUID& uuid, std::string_view data) { return setServiceData(uuid, reinterpret_cast(data.data()), data.length()); } // setServiceData @@ -996,7 +996,7 @@ bool NimBLEExtAdvertisement::setServiceData(const NimBLEUUID& uuid, const std::v * @param [in] name The short name of the device. * @return True if successful. */ -bool NimBLEExtAdvertisement::setShortName(const std::string& name) { +bool NimBLEExtAdvertisement::setShortName(std::string_view name) { return setName(name, false); } // setShortName diff --git a/src/NimBLEExtAdvertising.h b/src/NimBLEExtAdvertising.h index 79f8e5b2..56f2cf80 100644 --- a/src/NimBLEExtAdvertising.h +++ b/src/NimBLEExtAdvertising.h @@ -35,6 +35,7 @@ # include "NimBLEAddress.h" # include +# include # include class NimBLEExtAdvertisingCallbacks; @@ -57,20 +58,20 @@ class NimBLEExtAdvertisement { bool setCompleteServices32(const std::vector& uuids); bool setFlags(uint8_t flag); bool setManufacturerData(const uint8_t* data, size_t length); - bool setManufacturerData(const std::string& data); + bool setManufacturerData(std::string_view data); bool setManufacturerData(const std::vector& data); - bool setURI(const std::string& uri); - bool setName(const std::string& name, bool isComplete = true); + bool setURI(std::string_view uri); + bool setName(std::string_view name, bool isComplete = true); bool setPartialServices(const NimBLEUUID& uuid); bool setPartialServices16(const std::vector& uuids); bool setPartialServices32(const std::vector& uuids); bool setServiceData(const NimBLEUUID& uuid, const uint8_t* data, size_t length); - bool setServiceData(const NimBLEUUID& uuid, const std::string& data); + bool setServiceData(const NimBLEUUID& uuid, std::string_view data); bool setServiceData(const NimBLEUUID& uuid, const std::vector& data); - bool setShortName(const std::string& name); + bool setShortName(std::string_view name); bool setData(const uint8_t* data, size_t length); bool addData(const uint8_t* data, size_t length); - bool addData(const std::string& data); + bool addData(std::string_view data); bool setPreferredParams(uint16_t min, uint16_t max); bool addTxPower(); void setLegacyAdvertising(bool enable); diff --git a/src/NimBLEHIDDevice.cpp b/src/NimBLEHIDDevice.cpp index af0d38ed..0545ed98 100644 --- a/src/NimBLEHIDDevice.cpp +++ b/src/NimBLEHIDDevice.cpp @@ -92,7 +92,7 @@ void NimBLEHIDDevice::startServices() { * @details The characteristic will be created if not already existing. * @returns True if the name was set and/or the characteristic created. */ -bool NimBLEHIDDevice::setManufacturer(const std::string& name) { +bool NimBLEHIDDevice::setManufacturer(std::string_view name) { if (m_manufacturerChr == nullptr) { m_manufacturerChr = m_deviceInfoSvc->createCharacteristic(m_manufacturerChrUuid, NIMBLE_PROPERTY::READ); } diff --git a/src/NimBLEHIDDevice.h b/src/NimBLEHIDDevice.h index b479c461..f561eb4d 100644 --- a/src/NimBLEHIDDevice.h +++ b/src/NimBLEHIDDevice.h @@ -51,7 +51,7 @@ class NimBLEHIDDevice { void setReportMap(uint8_t* map, uint16_t); void startServices() __attribute__((deprecated("Services are now started by the server when start() is called, " "this function is no longer needed and will be removed in a future release."))); - bool setManufacturer(const std::string& name); + bool setManufacturer(std::string_view name); void setPnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version); void setHidInfo(uint8_t country, uint8_t flags); void setBatteryLevel(uint8_t level, bool notify = false); diff --git a/src/NimBLERemoteValueAttribute.h b/src/NimBLERemoteValueAttribute.h index 06e89881..e5cfbed3 100644 --- a/src/NimBLERemoteValueAttribute.h +++ b/src/NimBLERemoteValueAttribute.h @@ -71,100 +71,20 @@ class NimBLERemoteValueAttribute : public NimBLEValueAttribute, public NimBLEAtt return writeValue(reinterpret_cast(str), length ? length : strlen(str), response); } -# if __cplusplus < 201703L /** - * @brief Template to set the remote characteristic value to val. + * @brief Template to write a container, string or trivially copyable object to the remote attribute. + * @tparam T The type of the value, must not be a pointer. * @param [in] v The value to write. * @param [in] response True == request write response. - * @details Only used for types without a `c_str()` and `length()` or `data()` and `size()` method. - * size must be evaluatable by `sizeof()` if no length is provided. + * @details Types with `data()` and `size()` or `c_str()` and `length()` (including `std::string_view` and + * `std::span`) are written by their contents, anything else is written by its object representation using `sizeof()`. */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && !Has_c_str_length::value && !Has_data_size::value, bool>::type -# endif - writeValue(const T& v, bool response = false) const { - return writeValue(reinterpret_cast(&v), sizeof(T), response); - } - - /** - * @brief Template to set the remote characteristic value to val. - * @param [in] s The value to write. - * @param [in] response True == request write response. - * @details Only used if the has a `c_str()` and `length()` method. - */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && !Has_data_size::value, bool>::type -# endif - writeValue(const T& s, bool response = false) const { - return writeValue(reinterpret_cast(s.c_str()), s.length(), response); + template + bool writeValue(const T& v, bool response = false) const { + const auto bytes = NimBLEAsBytes(v); + return writeValue(bytes.data(), bytes.size(), response); } - /** - * @brief Template to set the remote characteristic value to val. - * @param [in] v The value to write. - * @param [in] response True == request write response. - * @details Only used if the has a `data()` and `size()` method with `value_type`. - * Correctly calculates byte size for containers with multi-byte element types. - */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && Has_value_type::value, bool>::type -# endif - writeValue(const T& v, bool response = false) const { - return writeValue( - reinterpret_cast(v.data()), - v.size() * sizeof(typename T::value_type), - response - ); - } - - /** - * @brief Template to set the remote characteristic value to val. - * @param [in] v The value to write. - * @param [in] response True == request write response. - * @details Only used if the has a `data()` and `size()` method without `value_type`. - */ - template -# ifdef _DOXYGEN_ - bool -# else - typename std::enable_if::value && !Has_value_type::value, bool>::type -# endif - writeValue(const T& v, bool response = false) const { - return writeValue(reinterpret_cast(v.data()), v.size(), response); - } - -# else - /** - * @brief Template to set the remote characteristic value to val. - * @param [in] s The value to write. - * @param [in] response True == request write response. - * @note This function is only available if the type T is not a pointer. - */ - template - typename std::enable_if::value, bool>::type writeValue(const T& v, bool response = false) const { - if constexpr (Has_data_size::value) { - if constexpr (Has_value_type::value) { - return writeValue(reinterpret_cast(v.data()), v.size() * sizeof(typename T::value_type), response); - } else { - return writeValue(reinterpret_cast(v.data()), v.size(), response); - } - } else if constexpr (Has_c_str_length::value) { - return writeValue(reinterpret_cast(v.c_str()), v.length(), response); - } else { - return writeValue(reinterpret_cast(&v), sizeof(v), response); - } - } -# endif - /** * @brief Template to convert the remote characteristic data to . * @tparam T The type to convert the data to. diff --git a/src/NimBLEServer.cpp b/src/NimBLEServer.cpp index 2e54f088..aa1b6bce 100644 --- a/src/NimBLEServer.cpp +++ b/src/NimBLEServer.cpp @@ -784,7 +784,7 @@ int NimBLEServer::handleGattEvent(uint16_t connHandle, uint16_t attrHandle, ble_ } NimBLEAttValue buf(ctxt->om->om_data, len, maxLen); - if (buf.data() == nullptr || buf.size() != len) { + if (buf.size() != len) { return BLE_ATT_ERR_INSUFFICIENT_RES; } diff --git a/src/NimBLEStream.h b/src/NimBLEStream.h index bbee9394..31f9fcd0 100644 --- a/src/NimBLEStream.h +++ b/src/NimBLEStream.h @@ -28,6 +28,7 @@ # endif # include +# include # include # include @@ -81,8 +82,9 @@ class NimBLEStream : public Stream { virtual size_t write(uint8_t data) override { return write(&data, 1); } // Template for other integral types (char, int, long, etc.) - template - typename std::enable_if::value && !std::is_same::value, size_t>::type write(T data) { + template + requires(!std::is_same_v) + size_t write(T data) { return write(static_cast(data)); } diff --git a/src/NimBLEUUID.cpp b/src/NimBLEUUID.cpp index 6f7792ab..99941216 100644 --- a/src/NimBLEUUID.cpp +++ b/src/NimBLEUUID.cpp @@ -32,6 +32,12 @@ static const char* LOG_TAG = "NimBLEUUID"; static const uint8_t ble_base_uuid[] = { 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +/** + * @brief Create a UUID from a null-terminated string. + * @param [in] uuidStr The string representation of the UUID. + */ +NimBLEUUID::NimBLEUUID(const char* uuidStr) : NimBLEUUID(std::string(uuidStr != nullptr ? uuidStr : "")) {} + /** * @brief Create a UUID from the native UUID. * @param [in] uuid The native UUID. diff --git a/src/NimBLEUUID.h b/src/NimBLEUUID.h index 10844f84..f509b616 100644 --- a/src/NimBLEUUID.h +++ b/src/NimBLEUUID.h @@ -44,6 +44,7 @@ class NimBLEUUID { * @brief Created a blank UUID. */ NimBLEUUID() = default; + explicit NimBLEUUID(const char* uuidStr); NimBLEUUID(const ble_uuid_any_t& uuid); NimBLEUUID(const ble_uuid_t* uuid); NimBLEUUID(const std::string& uuid); @@ -67,6 +68,8 @@ class NimBLEUUID { bool operator!=(const NimBLEUUID& rhs) const; operator std::string() const; + + private: ble_uuid_any_t m_uuid{}; }; // NimBLEUUID diff --git a/src/NimBLEValueAttribute.h b/src/NimBLEValueAttribute.h index d6c7d214..67f78429 100644 --- a/src/NimBLEValueAttribute.h +++ b/src/NimBLEValueAttribute.h @@ -58,8 +58,8 @@ class NimBLEValueAttribute { * Used for types that are trivially copyable and convertible to NimBLEAttValue. */ template - typename std::enable_if::value, T>::type - getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const { + requires std::is_trivially_copyable_v + T getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const { return m_value.getValue(timestamp, skipSizeCheck); } @@ -73,8 +73,8 @@ class NimBLEValueAttribute { * Used for types that are not trivially copyable and convertible to NimBLEAttValue via it's operators. */ template - typename std::enable_if::value && std::is_convertible::value, T>::type - getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const { + requires(!std::is_trivially_copyable_v && std::is_convertible_v) + T getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const { return m_value; }