fix: #200 Enable use of data()/size() before trying c_str()/length() (#201)

* fix: #200 Use `data()`/`size()` instead of `c_str()`/`length()`

* Reduce duplication, only allow template function in characteristic and remote value attr if the type is not a pointer (otherwise sizeof is useless). add appropriate notes

* clean up AttValue::setValue to remove unnecessary length parameter enabling requirement of non-pointer type
This commit is contained in:
William Emfinger 2024-10-14 18:02:21 -05:00 committed by GitHub
parent 987a69f544
commit 3820f57076
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 63 additions and 111 deletions

View file

@ -38,12 +38,20 @@
# error CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH cannot be less than 1; Range = 1 : 512 # error CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH cannot be less than 1; Range = 1 : 512
# endif # endif
/* Used to determine if the type passed to a template has a c_str() and length() method. */ /* Used to determine if the type passed to a template has a data() and size() method. */
template <typename T, typename = void, typename = void> template <typename T, typename = void, typename = void>
struct Has_c_str_len : std::false_type {}; struct Has_data_size : std::false_type {};
template <typename T> template <typename T>
struct Has_c_str_len<T, decltype(void(std::declval<T&>().c_str())), decltype(void(std::declval<T&>().length()))> struct Has_data_size<T, decltype(void(std::declval<T&>().data())), decltype(void(std::declval<T&>().size()))>
: std::true_type {};
/* Used to determine if the type passed to a template has a c_str() and length() method. */
template <typename T, typename = void, typename = void>
struct Has_c_str_length : std::false_type {};
template <typename T>
struct Has_c_str_length<T, decltype(void(std::declval<T&>().c_str())), decltype(void(std::declval<T&>().length()))>
: std::true_type {}; : std::true_type {};
/** /**
@ -216,39 +224,18 @@ class NimBLEAttValue {
/** /**
* @brief Template to set value to the value of <type\>val. * @brief Template to set value to the value of <type\>val.
* @param [in] s The <type\>value to set. * @param [in] s The <type\>value to set.
* @param [in] len The length of the value in bytes, defaults to sizeof(T). * @note This function is only availabe if the type T is not a pointer.
* @details Only used for types without a `c_str()` method.
*/ */
template <typename T> template <typename T>
# ifdef _DOXYGEN_ typename std::enable_if<!std::is_pointer<T>::value, bool>::type
bool setValue(const T& s) {
# else if constexpr (Has_data_size<T>::value) {
typename std::enable_if<!Has_c_str_len<T>::value, bool>::type return setValue(reinterpret_cast<const uint8_t*>(s.data()), s.size());
# endif } else if constexpr (Has_c_str_length<T>::value) {
setValue(const T& s, uint16_t len = 0) { return setValue(reinterpret_cast<const uint8_t*>(s.c_str()), s.length());
if (len == 0) { } else {
len = sizeof(T); return setValue(reinterpret_cast<const uint8_t*>(&s), sizeof(s));
} }
return setValue(reinterpret_cast<const uint8_t*>(&s), len);
}
/**
* @brief Template to set value to the value of <type\>val.
* @param [in] s The <type\>value to set.
* @param [in] len The length of the value in bytes, defaults to string.length().
* @details Only used if the <type\> has a `c_str()` method.
*/
template <typename T>
# ifdef _DOXYGEN_
bool
# else
typename std::enable_if<Has_c_str_len<T>::value, bool>::type
# endif
setValue(const T& s, uint16_t len = 0) {
if (len == 0) {
len = s.length();
}
return setValue(reinterpret_cast<const uint8_t*>(s.c_str()), len);
} }
/** /**

View file

@ -266,16 +266,6 @@ void NimBLECharacteristic::indicate(const uint8_t* value, size_t length, uint16_
sendValue(value, length, false, conn_handle); sendValue(value, length, false, conn_handle);
} // indicate } // indicate
/**
* @brief Send an indication.
* @param[in] value A std::vector<uint8_t> containing the value to send as the notification value.
* @param[in] conn_handle Connection handle to send an individual indication, or BLE_HS_CONN_HANDLE_NONE to send
* the indication to all subscribed clients.
*/
void NimBLECharacteristic::indicate(const std::vector<uint8_t>& value, uint16_t conn_handle) const {
sendValue(value.data(), value.size(), false, conn_handle);
} // indicate
/** /**
* @brief Send a notification. * @brief Send a notification.
* @param[in] conn_handle Connection handle to send an individual notification, or BLE_HS_CONN_HANDLE_NONE to send * @param[in] conn_handle Connection handle to send an individual notification, or BLE_HS_CONN_HANDLE_NONE to send
@ -296,16 +286,6 @@ void NimBLECharacteristic::notify(const uint8_t* value, size_t length, uint16_t
sendValue(value, length, true, conn_handle); sendValue(value, length, true, conn_handle);
} // indicate } // indicate
/**
* @brief Send a notification.
* @param[in] value A std::vector<uint8_t> containing the value to send as the notification value.
* @param[in] conn_handle Connection handle to send an individual notification, or BLE_HS_CONN_HANDLE_NONE to send
* the notification to all subscribed clients.
*/
void NimBLECharacteristic::notify(const std::vector<uint8_t>& value, uint16_t conn_handle) const {
sendValue(value.data(), value.size(), true, conn_handle);
} // notify
/** /**
* @brief Sends a notification or indication. * @brief Sends a notification or indication.
* @param[in] value A pointer to the data to send. * @param[in] value A pointer to the data to send.

View file

@ -56,10 +56,8 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks); void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks);
void indicate(uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const; void indicate(uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
void indicate(const uint8_t* value, size_t length, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const; void indicate(const uint8_t* value, size_t length, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
void indicate(const std::vector<uint8_t>& value, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
void notify(uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const; void notify(uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
void notify(const uint8_t* value, size_t length, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const; void notify(const uint8_t* value, size_t length, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
void notify(const std::vector<uint8_t>& value, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
NimBLEDescriptor* createDescriptor(const char* uuid, NimBLEDescriptor* createDescriptor(const char* uuid,
uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE, uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
@ -77,36 +75,47 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
/*********************** Template Functions ************************/ /*********************** Template Functions ************************/
/** /**
* @brief Template to send a notification from a class type that has a c_str() and length() method. * @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. * @tparam T The a reference to a class containing the data to send.
* @param[in] value The <type\>value to set. * @param[in] value The <type\>value to set.
* @param[in] is_notification if true sends a notification, false sends an indication. * @param[in] conn_handle The connection handle to send the notification to.
* @details Only used if the <type\> has a `c_str()` method. * @note This function is only available if the type T is not a pointer.
*/ */
template <typename T> template <typename T>
# ifdef _DOXYGEN_ typename std::enable_if<!std::is_pointer<T>::value, void>::type
void notify(const T& value, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const {
# else if constexpr (Has_data_size<T>::value) {
typename std::enable_if<Has_c_str_len<T>::value, void>::type notify(reinterpret_cast<const uint8_t*>(value.data()), value.size(), conn_handle);
# endif } else if constexpr (Has_c_str_length<T>::value) {
notify(const T& value, bool is_notification = true) const { notify(reinterpret_cast<const uint8_t*>(value.c_str()), value.length(), conn_handle);
notify(reinterpret_cast<const uint8_t*>(value.c_str()), value.length(), is_notification); } else {
notify(reinterpret_cast<const uint8_t*>(&value), sizeof(value), conn_handle);
}
} }
/** /**
* @brief Template to send an indication from a class type that has a c_str() and length() method. * @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. * @tparam T The a reference to a class containing the data to send.
* @param[in] value The <type\>value to set. * @param[in] value The <type\>value to set.
* @details Only used if the <type\> has a `c_str()` method. * @param[in] conn_handle The connection handle to send the indication to.
* @note This function is only available if the type T is not a pointer.
*/ */
template <typename T> template <typename T>
# ifdef _DOXYGEN_ typename std::enable_if<!std::is_pointer<T>::value, void>::type
void indicate(const T& value, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const {
# else if constexpr (Has_data_size<T>::value) {
typename std::enable_if<Has_c_str_len<T>::value, void>::type indicate(reinterpret_cast<const uint8_t*>(value.data()), value.size(), conn_handle);
# endif } else if constexpr (Has_c_str_length<T>::value) {
indicate(const T& value) const { indicate(reinterpret_cast<const uint8_t*>(value.c_str()), value.length(), conn_handle);
indicate(reinterpret_cast<const uint8_t*>(value.c_str()), value.length()); } else {
indicate(reinterpret_cast<const uint8_t*>(&value), sizeof(value), conn_handle);
}
} }
private: private:

View file

@ -225,12 +225,12 @@ NimBLECharacteristic* NimBLEHIDDevice::batteryLevel() {
return m_batteryLevelCharacteristic; return m_batteryLevelCharacteristic;
} }
/* NimBLECharacteristic* NimBLEHIDDevice::reportMap() {
BLECharacteristic* BLEHIDDevice::reportMap() {
return m_reportMapCharacteristic; return m_reportMapCharacteristic;
} }
/*
BLECharacteristic* BLEHIDDevice::pnp() { BLECharacteristic* BLEHIDDevice::pnp() {
return m_pnpCharacteristic; return m_pnpCharacteristic;
} }

View file

@ -60,7 +60,7 @@ public:
void setBatteryLevel(uint8_t level); void setBatteryLevel(uint8_t level);
//NimBLECharacteristic* reportMap(); NimBLECharacteristic* reportMap();
NimBLECharacteristic* hidControl(); NimBLECharacteristic* hidControl();
NimBLECharacteristic* inputReport(uint8_t reportID); NimBLECharacteristic* inputReport(uint8_t reportID);
NimBLECharacteristic* outputReport(uint8_t reportID); NimBLECharacteristic* outputReport(uint8_t reportID);

View file

@ -63,16 +63,6 @@ class NimBLERemoteValueAttribute : public NimBLEAttribute {
*/ */
bool writeValue(const uint8_t* data, size_t length, bool response = false) const; bool writeValue(const uint8_t* data, size_t length, bool response = false) const;
/**
* @brief Write a new value to the remote characteristic from a std::vector<uint8_t>.
* @param [in] vec A std::vector<uint8_t> value to write to the remote characteristic.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool writeValue(const std::vector<uint8_t>& v, bool response = false) const {
return writeValue(&v[0], v.size(), response);
}
/** /**
* @brief Write a new value to the remote characteristic from a const char*. * @brief Write a new value to the remote characteristic from a const char*.
* @param [in] str A character string to write to the remote characteristic. * @param [in] str A character string to write to the remote characteristic.
@ -88,32 +78,18 @@ class NimBLERemoteValueAttribute : public NimBLEAttribute {
* @brief Template to set the remote characteristic value to <type\>val. * @brief Template to set the remote characteristic value to <type\>val.
* @param [in] s The value to write. * @param [in] s The value to write.
* @param [in] response True == request write response. * @param [in] response True == request write response.
* @details Only used for non-arrays and types without a `c_str()` method. * @note This function is only available if the type T is not a pointer.
*/ */
template <typename T> template <typename T>
# ifdef _DOXYGEN_ typename std::enable_if<!std::is_pointer<T>::value, bool>::type
bool
# else
typename std::enable_if<!std::is_array<T>::value && !Has_c_str_len<T>::value, bool>::type
# endif
writeValue(const T& v, bool response = false) const { writeValue(const T& v, bool response = false) const {
return writeValue(reinterpret_cast<const uint8_t*>(&v), sizeof(T), response); if constexpr (Has_data_size<T>::value) {
} return writeValue(reinterpret_cast<const uint8_t*>(v.data()), v.size(), response);
} else if constexpr (Has_c_str_length<T>::value) {
/** return writeValue(reinterpret_cast<const uint8_t*>(v.c_str()), v.length(), response);
* @brief Template to set the remote characteristic value to <type\>val. } else {
* @param [in] s The value to write. return writeValue(reinterpret_cast<const uint8_t*>(&v), sizeof(v), response);
* @param [in] response True == request write response. }
* @details Only used if the <type\> has a `c_str()` method.
*/
template <typename T>
# ifdef _DOXYGEN_
bool
# else
typename std::enable_if<Has_c_str_len<T>::value, bool>::type
# endif
writeValue(const T& s, bool response = false) const {
return writeValue(reinterpret_cast<const uint8_t*>(s.c_str()), s.length(), response);
} }
/** /**