/* * NimBLEAttValue.h * * Created: on March 18, 2021 * Author H2zero * */ #ifndef MAIN_NIMBLEATTVALUE_H_ #define MAIN_NIMBLEATTVALUE_H_ #include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) #ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE #include #endif #include "NimBLELog.h" /**** FIX COMPILATION ****/ #undef min #undef max /**************************/ #include #include #ifndef CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED # define CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED 0 #endif #if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED # include #endif #if !defined(CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH) # define CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH 20 #elif CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH > BLE_ATT_ATTR_MAX_LEN # error CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH cannot be larger than 512 (BLE_ATT_ATTR_MAX_LEN) #elif CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH < 1 # 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. */ template struct Has_c_str_len : std::false_type {}; template struct Has_c_str_len().c_str())), decltype(void(std::declval().length()))> : std::true_type {}; /** * @brief A specialized container class to hold BLE attribute values. * @details This class is designed to be more memory efficient than using\n * standard container types for value storage, while being convertible to\n * many different container classes. */ class NimBLEAttValue { uint8_t* m_attr_value = nullptr; uint16_t m_attr_max_len = 0; uint16_t m_attr_len = 0; uint16_t m_capacity = 0; #if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED time_t m_timestamp = 0; #endif void deepCopy(const NimBLEAttValue & source); public: /** * @brief Default constructor. * @param[in] init_len The initial size in bytes. * @param[in] max_len The max size in bytes that the value can be. */ NimBLEAttValue(uint16_t init_len = CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); /** * @brief Construct with an initial value from a buffer. * @param value A pointer to the initial value to set. * @param[in] len The size in bytes of the value to set. * @param[in] max_len The max size in bytes that the value can be. */ NimBLEAttValue(const uint8_t *value, uint16_t len, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); /** * @brief Construct with an initializer list. * @param list An initializer list containing the initial value to set. * @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(), (uint16_t)list.size(), max_len){} /** * @brief Construct with an initial value from a const char string. * @param value A pointer to the initial value to set. * @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){} /** * @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((uint8_t*)str.data(), (uint16_t)str.length(), 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], (uint16_t)vec.size(), max_len){} #ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE /** * @brief Construct with an initial value from an Arduino String. * @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((uint8_t*)str.c_str(), str.length(), max_len){} #endif /** @brief Copy constructor */ NimBLEAttValue(const NimBLEAttValue & source) { deepCopy(source); } /** @brief Move constructor */ NimBLEAttValue(NimBLEAttValue && source) { *this = std::move(source); } /** @brief Destructor */ ~NimBLEAttValue(); /** @brief Returns the max size in bytes */ uint16_t max_size() const { return m_attr_max_len; } /** @brief Returns the currently allocated capacity in bytes */ uint16_t capacity() const { return m_capacity; } /** @brief Returns the current length of the value in bytes */ uint16_t length() const { return m_attr_len; } /** @brief Returns the current size of the value in bytes */ uint16_t size() const { return m_attr_len; } /** @brief Returns a pointer to the internal buffer of the value */ const uint8_t* data() const { return m_attr_value; } /** @brief Returns a pointer to the internal buffer of the value as a const char* */ const char* c_str() const { return (const char*)m_attr_value; } /** @brief Iterator begin */ const uint8_t* begin() const { return m_attr_value; } /** @brief Iterator end */ const uint8_t* end() const { return m_attr_value + m_attr_len; } #if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED /** @brief Returns a timestamp of when the value was last updated */ time_t getTimeStamp() const { return m_timestamp; } /** @brief Set the timestamp to the current time */ void setTimeStamp() { m_timestamp = time(nullptr); } /** * @brief Set the timestamp to the specified time * @param[in] t The timestamp value to set */ void setTimeStamp(time_t t) { m_timestamp = t; } #else time_t getTimeStamp() const { return 0; } void setTimeStamp() { } void setTimeStamp(time_t t) { } #endif /** * @brief Set the value from a buffer * @param[in] value A ponter to a buffer containing the value. * @param[in] len The length of the value in bytes. * @returns True if successful. */ bool setValue(const uint8_t *value, uint16_t len); /** * @brief Set value to the value of const char*. * @param [in] s A ponter to a const char value to set. */ bool setValue(const char* s) { return setValue((uint8_t*)s, (uint16_t)strlen(s)); } /** * @brief Get a pointer to the value buffer with timestamp. * @param[in] timestamp A ponter to a time_t variable to store the timestamp. * @returns A pointer to the internal value buffer. */ const uint8_t* getValue(time_t *timestamp); /** * @brief Append data to the value. * @param[in] value A ponter to a data buffer with the value to append. * @param[in] len The length of the value to append in bytes. * @returns A reference to the appended NimBLEAttValue. */ NimBLEAttValue& append(const uint8_t *value, uint16_t len); /*********************** Template Functions ************************/ /** * @brief Template to set value to the value of val. * @param [in] s The value to set. * @details Only used for types without a `c_str()` method. */ template #ifdef _DOXYGEN_ bool #else typename std::enable_if::value, bool>::type #endif setValue(const T &s) { return setValue((uint8_t*)&s, 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, bool>::type #endif setValue(const T & s) { return setValue((uint8_t*)s.c_str(), (uint16_t)s.length()); } /** * @brief Template to return the value as a . * @tparam T The type to convert the data to. * @param [in] timestamp A pointer to a time_t struct to store the time the value was read. * @param [in] skipSizeCheck If true it will skip checking if the data size is less than\n * sizeof(). * @return The data converted to or NULL if skipSizeCheck is false and the data is\n * less than sizeof(). * @details Use: getValue(×tamp, skipSizeCheck); */ template T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) { if(!skipSizeCheck && size() < sizeof(T)) { return T(); } return *((T *)getValue(timestamp)); } /*********************** Operators ************************/ /** @brief Subscript operator */ uint8_t operator [](int pos) const { assert(pos < m_attr_len && "out of range"); return m_attr_value[pos]; } /** @brief Operator; Get the value as a std::vector. */ operator std::vector() const { return std::vector(m_attr_value, m_attr_value + m_attr_len); } /** @brief Operator; Get the value as a std::string. */ operator std::string() const { return std::string((char*)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; } /** @brief Operator; Append another NimBLEAttValue. */ NimBLEAttValue& operator +=(const NimBLEAttValue & source) { return append(source.data(), source.size()); } /** @brief Operator; Set the value from a std::string source. */ NimBLEAttValue& operator =(const std::string & source) { setValue((uint8_t*)source.data(), (uint16_t)source.size()); return *this; } /** @brief Move assignment operator */ NimBLEAttValue& operator =(NimBLEAttValue && source); /** @brief Copy assignment operator */ NimBLEAttValue& operator =(const NimBLEAttValue & source); /** @brief Equality operator */ bool operator ==(const NimBLEAttValue & source) { return (m_attr_len == source.size()) ? memcmp(m_attr_value, source.data(), m_attr_len) == 0 : false; } /** @brief Inequality operator */ bool operator !=(const NimBLEAttValue & source){ return !(*this == source); } #ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE /** @brief Operator; Get the value as an Arduino String value. */ operator String() const { return String((char*)m_attr_value); } #endif }; inline NimBLEAttValue::NimBLEAttValue(uint16_t init_len, uint16_t max_len) { m_attr_value = (uint8_t*)calloc(init_len + 1, 1); assert(m_attr_value && "No Mem"); m_attr_max_len = std::min(BLE_ATT_ATTR_MAX_LEN, (int)max_len); m_attr_len = 0; m_capacity = init_len; setTimeStamp(0); } inline NimBLEAttValue::NimBLEAttValue(const uint8_t *value, uint16_t len, uint16_t max_len) : NimBLEAttValue(len, max_len) { memcpy(m_attr_value, value, len); m_attr_value[len] = '\0'; m_attr_len = len; } inline NimBLEAttValue::~NimBLEAttValue() { if(m_attr_value != nullptr) { free(m_attr_value); } } inline NimBLEAttValue& NimBLEAttValue::operator =(NimBLEAttValue && source) { if (this != &source){ free(m_attr_value); 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; } return *this; } inline NimBLEAttValue& NimBLEAttValue::operator =(const NimBLEAttValue & source) { if (this != &source) { deepCopy(source); } return *this; } inline void NimBLEAttValue::deepCopy(const NimBLEAttValue & source) { uint8_t* res = (uint8_t*)realloc( m_attr_value, source.m_capacity + 1); assert(res && "deepCopy: realloc failed"); ble_npl_hw_enter_critical(); m_attr_value = res; m_attr_max_len = source.m_attr_max_len; m_attr_len = source.m_attr_len; m_capacity = source.m_capacity; setTimeStamp(source.getTimeStamp()); memcpy(m_attr_value, source.m_attr_value, m_attr_len + 1); ble_npl_hw_exit_critical(0); } inline const uint8_t* NimBLEAttValue::getValue(time_t *timestamp) { if(timestamp != nullptr) { #if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED *timestamp = m_timestamp; #else *timestamp = 0; #endif } return m_attr_value; } inline bool NimBLEAttValue::setValue(const uint8_t *value, uint16_t len) { if (len > m_attr_max_len) { NIMBLE_LOGE("NimBLEAttValue", "value exceeds max, len=%u, max=%u", len, m_attr_max_len); return false; } uint8_t *res = m_attr_value; if (len > m_capacity) { res = (uint8_t*)realloc(m_attr_value, (len + 1)); m_capacity = len; } assert(res && "setValue: realloc failed"); #if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED time_t t = time(nullptr); #else time_t t = 0; #endif ble_npl_hw_enter_critical(); m_attr_value = res; memcpy(m_attr_value, value, len); m_attr_value[len] = '\0'; m_attr_len = len; setTimeStamp(t); ble_npl_hw_exit_critical(0); return true; } inline NimBLEAttValue& NimBLEAttValue::append(const uint8_t *value, uint16_t len) { if (len < 1) { return *this; } if ((m_attr_len + len) > m_attr_max_len) { NIMBLE_LOGE("NimBLEAttValue", "val > max, len=%u, max=%u", len, m_attr_max_len); return *this; } uint8_t* res = m_attr_value; uint16_t new_len = m_attr_len + len; if (new_len > m_capacity) { res = (uint8_t*)realloc(m_attr_value, (new_len + 1)); m_capacity = new_len; } assert(res && "append: realloc failed"); #if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED time_t t = time(nullptr); #else time_t t = 0; #endif ble_npl_hw_enter_critical(); m_attr_value = res; memcpy(m_attr_value + m_attr_len, value, len); m_attr_len = new_len; m_attr_value[m_attr_len] = '\0'; setTimeStamp(t); ble_npl_hw_exit_critical(0); return *this; } #endif /*(CONFIG_BT_ENABLED) */ #endif /* MAIN_NIMBLEATTVALUE_H_ */