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
# 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>
struct Has_c_str_len : std::false_type {};
struct Has_data_size : std::false_type {};
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 {};
/**
@ -216,39 +224,18 @@ class NimBLEAttValue {
/**
* @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 sizeof(T).
* @details Only used for types without a `c_str()` method.
* @note This function is only availabe if the type T is not a pointer.
*/
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 = sizeof(T);
typename std::enable_if<!std::is_pointer<T>::value, bool>::type
setValue(const T& s) {
if constexpr (Has_data_size<T>::value) {
return setValue(reinterpret_cast<const uint8_t*>(s.data()), s.size());
} else if constexpr (Has_c_str_length<T>::value) {
return setValue(reinterpret_cast<const uint8_t*>(s.c_str()), s.length());
} else {
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);
} // 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.
* @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);
} // 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.
* @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 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 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(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,
uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
@ -77,36 +75,47 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
/*********************** 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.
* @param[in] value The <type\>value to set.
* @param[in] is_notification if true sends a notification, false sends an indication.
* @details Only used if the <type\> has a `c_str()` method.
* @param[in] conn_handle The connection handle to send the notification to.
* @note This function is only available if the type T is not a pointer.
*/
template <typename T>
# ifdef _DOXYGEN_
void
# else
typename std::enable_if<Has_c_str_len<T>::value, void>::type
# endif
notify(const T& value, bool is_notification = true) const {
notify(reinterpret_cast<const uint8_t*>(value.c_str()), value.length(), is_notification);
typename std::enable_if<!std::is_pointer<T>::value, void>::type
notify(const T& value, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const {
if constexpr (Has_data_size<T>::value) {
notify(reinterpret_cast<const uint8_t*>(value.data()), value.size(), conn_handle);
} else if constexpr (Has_c_str_length<T>::value) {
notify(reinterpret_cast<const uint8_t*>(value.c_str()), value.length(), conn_handle);
} 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.
* @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>
# ifdef _DOXYGEN_
void
# else
typename std::enable_if<Has_c_str_len<T>::value, void>::type
# endif
indicate(const T& value) const {
indicate(reinterpret_cast<const uint8_t*>(value.c_str()), value.length());
typename std::enable_if<!std::is_pointer<T>::value, void>::type
indicate(const T& value, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const {
if constexpr (Has_data_size<T>::value) {
indicate(reinterpret_cast<const uint8_t*>(value.data()), value.size(), conn_handle);
} else if constexpr (Has_c_str_length<T>::value) {
indicate(reinterpret_cast<const uint8_t*>(value.c_str()), value.length(), conn_handle);
} else {
indicate(reinterpret_cast<const uint8_t*>(&value), sizeof(value), conn_handle);
}
}
private:

View file

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

View file

@ -60,7 +60,7 @@ public:
void setBatteryLevel(uint8_t level);
//NimBLECharacteristic* reportMap();
NimBLECharacteristic* reportMap();
NimBLECharacteristic* hidControl();
NimBLECharacteristic* inputReport(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;
/**
* @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*.
* @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.
* @param [in] s The value to write.
* @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>
# ifdef _DOXYGEN_
bool
# else
typename std::enable_if<!std::is_array<T>::value && !Has_c_str_len<T>::value, bool>::type
# endif
typename std::enable_if<!std::is_pointer<T>::value, bool>::type
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);
} else {
return writeValue(reinterpret_cast<const uint8_t*>(&v), sizeof(v), response);
}
/**
* @brief Template to set the remote characteristic value to <type\>val.
* @param [in] s The value to write.
* @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);
}
/**