esp-nimble-cpp 2.6.0
Loading...
Searching...
No Matches
NimBLEAttValue.h
1/*
2 * Copyright 2020-2025 Ryan Powell <ryan@nable-embedded.io> and
3 * esp-nimble-cpp, NimBLE-Arduino contributors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18#ifndef NIMBLE_CPP_ATTVALUE_H
19#define NIMBLE_CPP_ATTVALUE_H
20
21#include "syscfg/syscfg.h"
22#if CONFIG_BT_NIMBLE_ENABLED
23
24# ifdef USING_NIMBLE_ARDUINO_HEADERS
25# include "nimble/nimble/include/nimble/nimble_npl.h"
26# else
27# include "nimble/nimble_npl.h"
28# endif
29
30/* Enables the use of Arduino String class for attribute values */
31# ifndef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
32# define NIMBLE_CPP_ARDUINO_STRING_AVAILABLE (__has_include(<Arduino.h>))
33# endif
34
35# if NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
36# include <WString.h>
37# endif
38
39# include <string>
40# include <vector>
41# include <ctime>
42# include <cstring>
43# include <cstdint>
44# include <atomic>
45
46# ifndef MYNEWT_VAL_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
47# ifndef CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
48# define MYNEWT_VAL_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED 0
49# else
50# define MYNEWT_VAL_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
51# endif
52# endif
53
54# ifndef BLE_ATT_ATTR_MAX_LEN
55# define BLE_ATT_ATTR_MAX_LEN 512
56# endif
57
58# ifndef MYNEWT_VAL_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH
59# ifndef CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH
60# define MYNEWT_VAL_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH 20
61# else
62# define MYNEWT_VAL_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH
63# endif
64# endif
65
66# if MYNEWT_VAL(NIMBLE_CPP_ATT_VALUE_INIT_LENGTH) > BLE_ATT_ATTR_MAX_LEN
67# error NIMBLE_CPP_ATT_VALUE_INIT_LENGTH cannot be larger than 512 (BLE_ATT_ATTR_MAX_LEN)
68# elif MYNEWT_VAL(NIMBLE_CPP_ATT_VALUE_INIT_LENGTH) < 1
69# error NIMBLE_CPP_ATT_VALUE_INIT_LENGTH cannot be less than 1; Range = 1 : 512
70# endif
71
72/* Used to determine if the type passed to a template has a data() and size() method. */
73template <typename T, typename = void, typename = void>
74struct Has_data_size : std::false_type {};
75
76template <typename T>
77struct Has_data_size<T, decltype(void(std::declval<T&>().data())), decltype(void(std::declval<T&>().size()))>
78 : std::true_type {};
79
80/* Used to determine if the type passed to a template has a c_str() and length() method. */
81template <typename T, typename = void, typename = void>
82struct Has_c_str_length : std::false_type {};
83
84template <typename T>
85struct Has_c_str_length<T, decltype(void(std::declval<T&>().c_str())), decltype(void(std::declval<T&>().length()))>
86 : std::true_type {};
87
88/* Used to determine if the type passed to a template has a value_type member (std::vector, std::array, std::string, etc.). */
89template <typename T, typename = void>
90struct Has_value_type : std::false_type {};
91
92template <typename T>
93struct Has_value_type<T, decltype(void(sizeof(typename T::value_type)))>
94 : std::true_type {};
95
103 uint8_t* m_attr_value{};
104 uint16_t m_attr_max_len{};
105 uint16_t m_attr_len{};
106 uint16_t m_capacity{};
107# if MYNEWT_VAL(NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED)
108 time_t m_timestamp{};
109# endif
110 bool deepCopy(const NimBLEAttValue& source);
111
112 public:
113 class Lock {
114 static ble_npl_mutex m_mutex;
115 static std::atomic<bool> m_initialized;
116 static std::atomic<int> m_activeCount;
117 bool m_locked{false};
118
119 public:
120 Lock() {
121 // Increment active count to signal that a constructor is running
122 m_activeCount.fetch_add(1);
123
124 if (m_initialized.load()) {
125 m_locked = (ble_npl_mutex_pend(&m_mutex, BLE_NPL_TIME_FOREVER) == BLE_NPL_OK);
126
127 // Post-lock check: If deinit flipped the flag while we were waiting in line,
128 // we must abort, give it back, and act un-locked.
129 if (!m_initialized.load() && m_locked) {
130 ble_npl_mutex_release(&m_mutex);
131 m_locked = false;
132 }
133 }
134
135 // Decrement active count to signal that the constructor has finished processing
136 m_activeCount.fetch_sub(1);
137 }
138
139 ~Lock() {
140 if (m_locked) {
141 ble_npl_mutex_release(&m_mutex);
142 }
143 }
144
145 static bool init() {
146 if (!m_initialized.load()) {
147 if (ble_npl_mutex_init(&m_mutex) == BLE_NPL_OK) {
148 m_initialized.store(true);
149 }
150 }
151 return m_initialized.load();
152 }
153
154 static void deinit() {
155 if (m_initialized.load()) {
156 // Prevent any new instances from acquiring the lock while we deinitialize
157 m_initialized.store(false);
158
159 // Wait for any active users to finish
160 while (m_activeCount.load() > 0) {
161 ble_npl_time_delay(1);
162 }
163
164 // Acquire the mutex to ensure no other threads are holding it before deinitializing.
165 if (ble_npl_mutex_pend(&m_mutex, BLE_NPL_TIME_FOREVER) == BLE_NPL_OK) {
166 // Deinitialize the mutex now that we have exclusive ownership and no active users.
167 ble_npl_mutex_deinit(&m_mutex);
168 }
169 }
170 }
171 };
172
178 NimBLEAttValue(uint16_t init_len = MYNEWT_VAL(NIMBLE_CPP_ATT_VALUE_INIT_LENGTH), uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
179
186 NimBLEAttValue(const uint8_t* value, uint16_t len, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
187
193 NimBLEAttValue(const char* value, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN)
194 : NimBLEAttValue((uint8_t*)value, (uint16_t)strlen(value), max_len) {}
195
201 NimBLEAttValue(std::initializer_list<uint8_t> list, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN)
202 : NimBLEAttValue(list.begin(), list.size(), max_len) {}
203
209 NimBLEAttValue(const std::string str, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN)
210 : NimBLEAttValue(reinterpret_cast<const uint8_t*>(&str[0]), str.length(), max_len) {}
211
217 NimBLEAttValue(const std::vector<uint8_t> vec, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN)
218 : NimBLEAttValue(&vec[0], vec.size(), max_len) {}
219
220# if NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
226 NimBLEAttValue(const String str, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN)
227 : NimBLEAttValue(reinterpret_cast<const uint8_t*>(str.c_str()), str.length(), max_len) {}
228# endif
229
231 NimBLEAttValue(const NimBLEAttValue& source) { deepCopy(source); }
232
234 NimBLEAttValue(NimBLEAttValue&& source) { *this = std::move(source); }
235
238
240 uint16_t max_size() const { return m_attr_max_len; }
241
243 uint16_t capacity() const { return m_capacity; }
244
246 uint16_t length() const { return m_attr_len; }
247
249 uint16_t size() const { return m_attr_len; }
250
256 const uint8_t* data() const { return m_attr_value; }
257
261 const char* c_str() const { return reinterpret_cast<const char*>(m_attr_value); }
262
264 const uint8_t* begin() const { return m_attr_value; }
265
267 const uint8_t* end() const { return m_attr_value + m_attr_len; }
268
269# if MYNEWT_VAL(NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED)
271 time_t getTimeStamp() const { return m_timestamp; }
272
274 void setTimeStamp() { m_timestamp = time(nullptr); }
275
280 void setTimeStamp(time_t t) { m_timestamp = t; }
281# else
282 time_t getTimeStamp() const { return 0; }
283 void setTimeStamp() {}
284 void setTimeStamp(time_t t) {}
285# endif
286
293 bool setValue(const uint8_t* value, uint16_t len);
294
300 bool setValue(const char* s, uint16_t len = 0) {
301 if (len == 0) {
302 len = strlen(s);
303 }
304 return setValue(reinterpret_cast<const uint8_t*>(s), len);
305 }
306
307 const NimBLEAttValue& getValue(time_t* timestamp = nullptr) const {
308 if (timestamp != nullptr) {
309# if MYNEWT_VAL(NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED)
310 *timestamp = m_timestamp;
311# else
312 *timestamp = 0;
313# endif
314 }
315 return *this;
316 }
317
324 NimBLEAttValue& append(const uint8_t* value, uint16_t len);
325
331 bool copyFrom(const NimBLEAttValue& source) { return deepCopy(source); }
332
333 /*********************** Template Functions ************************/
334
335# if __cplusplus < 201703L
341 template <typename T>
342# ifdef _DOXYGEN_
343 bool
344# else
345 typename std::enable_if<std::is_array<T>::value && std::is_same<typename std::remove_extent<T>::type, char>::value, bool>::type
346# endif
347 setValue(const T& s) {
348 return setValue(reinterpret_cast<const uint8_t*>(s), strnlen(s, sizeof(T)));
349 }
350
357 template <typename T>
358# ifdef _DOXYGEN_
359 bool
360# else
361 typename std::enable_if<!std::is_pointer<T>::value && !Has_c_str_length<T>::value && !Has_data_size<T>::value &&
362 !(std::is_array<T>::value && std::is_same<typename std::remove_extent<T>::type, char>::value),
363 bool>::type
364# endif
365 setValue(const T& v) {
366 return setValue(reinterpret_cast<const uint8_t*>(&v), sizeof(T));
367 }
368
374 template <typename T>
375# ifdef _DOXYGEN_
376 bool
377# else
378 typename std::enable_if<Has_c_str_length<T>::value && !Has_data_size<T>::value, bool>::type
379# endif
380 setValue(const T& s) {
381 return setValue(reinterpret_cast<const uint8_t*>(s.c_str()), s.length());
382 }
383
390 template <typename T>
391# ifdef _DOXYGEN_
392 bool
393# else
394 typename std::enable_if<Has_data_size<T>::value && Has_value_type<T>::value, bool>::type
395# endif
396 setValue(const T& v) {
397 return setValue(
398 reinterpret_cast<const uint8_t*>(v.data()),
399 v.size() * sizeof(typename T::value_type)
400 );
401 }
402
408 template <typename T>
409# ifdef _DOXYGEN_
410 bool
411# else
412 typename std::enable_if<Has_data_size<T>::value && !Has_value_type<T>::value, bool>::type
413# endif
414 setValue(const T& v) {
415 return setValue(reinterpret_cast<const uint8_t*>(v.data()), v.size());
416 }
417
418# else
424 template <typename T>
425 typename std::enable_if<!std::is_pointer<T>::value, bool>::type setValue(const T& s) {
426 if constexpr (Has_data_size<T>::value) {
427 if constexpr (Has_value_type<T>::value) {
428 return setValue(reinterpret_cast<const uint8_t*>(s.data()), s.size() * sizeof(typename T::value_type));
429 } else {
430 return setValue(reinterpret_cast<const uint8_t*>(s.data()), s.size());
431 }
432 } else if constexpr (Has_c_str_length<T>::value) {
433 return setValue(reinterpret_cast<const uint8_t*>(s.c_str()), s.length());
434 } else if constexpr (std::is_array<T>::value && std::is_same<typename std::remove_extent<T>::type, char>::value) {
435 return setValue(reinterpret_cast<const uint8_t*>(s), strnlen(s, sizeof(s)));
436 } else {
437 return setValue(reinterpret_cast<const uint8_t*>(&s), sizeof(s));
438 }
439 }
440# endif
441
452 template <typename T>
453 T getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const {
454 Lock lock;
455 if (timestamp != nullptr) {
456# if MYNEWT_VAL(NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED)
457 *timestamp = m_timestamp;
458# else
459 *timestamp = 0;
460# endif
461 }
462
463 if (!skipSizeCheck && size() < sizeof(T)) {
464 return T();
465 }
466 return *(reinterpret_cast<const T*>(m_attr_value));
467 }
468
469 /*********************** Operators ************************/
470
472 uint8_t operator[](int pos) const;
473
475 operator std::vector<uint8_t>() const {
476 Lock lock;
477 return std::vector<uint8_t>(m_attr_value, m_attr_value + m_attr_len);
478 }
479
481 operator std::string() const {
482 Lock lock;
483 return std::string(reinterpret_cast<char*>(m_attr_value), m_attr_len);
484 }
485
487 operator const uint8_t*() const { return m_attr_value; }
488
491 Lock lock; // Recursive; keeps the source data and size consistent while appending
492 if (&source == this) {
493 // append() may reallocate the buffer, so append from a copy of our own data
494 std::vector<uint8_t> copy(m_attr_value, m_attr_value + m_attr_len);
495 return append(copy.data(), copy.size());
496 }
497 return append(source.data(), source.size());
498 }
499
501 NimBLEAttValue& operator=(const std::string& source) {
502 setValue(reinterpret_cast<const uint8_t*>(&source[0]), source.size());
503 return *this;
504 }
505
508
511
513 bool operator==(const NimBLEAttValue& source) const {
514 Lock lock;
515 return (m_attr_len == source.size()) ? memcmp(m_attr_value, source.data(), m_attr_len) == 0 : false;
516 }
517
519 bool operator!=(const NimBLEAttValue& source) const { return !(*this == source); }
520
521# if NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
523 operator String() const {
524 Lock lock;
525 return String(reinterpret_cast<char*>(m_attr_value));
526 }
527# endif
528};
529
530#endif // CONFIG_BT_NIMBLE_ENABLED
531#endif // NIMBLE_CPP_ATTVALUE_H_
A specialized container class to hold BLE attribute values.
Definition NimBLEAttValue.h:102
NimBLEAttValue(const std::vector< uint8_t > vec, uint16_t max_len=BLE_ATT_ATTR_MAX_LEN)
Construct with an initial value from a std::vector<uint8_t>.
Definition NimBLEAttValue.h:217
~NimBLEAttValue()
Destructor.
Definition NimBLEAttValue.cpp:91
uint16_t length() const
Returns the current length of the value in bytes.
Definition NimBLEAttValue.h:246
NimBLEAttValue(const char *value, uint16_t max_len=BLE_ATT_ATTR_MAX_LEN)
Construct with an initial value from a const char string.
Definition NimBLEAttValue.h:193
uint16_t capacity() const
Returns the currently allocated capacity in bytes.
Definition NimBLEAttValue.h:243
const char * c_str() const
Returns a pointer to the internal buffer of the value as a const char*.
Definition NimBLEAttValue.h:261
NimBLEAttValue(const std::string str, uint16_t max_len=BLE_ATT_ATTR_MAX_LEN)
Construct with an initial value from a std::string.
Definition NimBLEAttValue.h:209
bool setValue(const uint8_t *value, uint16_t len)
Set the value from a buffer.
Definition NimBLEAttValue.cpp:141
NimBLEAttValue(std::initializer_list< uint8_t > list, uint16_t max_len=BLE_ATT_ATTR_MAX_LEN)
Construct with an initializer list.
Definition NimBLEAttValue.h:201
NimBLEAttValue & operator+=(const NimBLEAttValue &source)
Operator; Append another NimBLEAttValue.
Definition NimBLEAttValue.h:490
NimBLEAttValue(const NimBLEAttValue &source)
Copy constructor.
Definition NimBLEAttValue.h:231
NimBLEAttValue(NimBLEAttValue &&source)
Move constructor.
Definition NimBLEAttValue.h:234
const uint8_t * data() const
Returns a pointer to the internal buffer of the value.
Definition NimBLEAttValue.h:256
uint8_t operator[](int pos) const
Subscript operator.
Definition NimBLEAttValue.cpp:192
const uint8_t * end() const
Iterator end; see data() for thread safety restrictions.
Definition NimBLEAttValue.h:267
bool setValue(const T &v)
Template to set value to the value of <type>val.
Definition NimBLEAttValue.h:365
bool copyFrom(const NimBLEAttValue &source)
Copy the value from another NimBLEAttValue instance.
Definition NimBLEAttValue.h:331
NimBLEAttValue & append(const uint8_t *value, uint16_t len)
Append data to the value.
Definition NimBLEAttValue.cpp:150
uint16_t size() const
Returns the current size of the value in bytes.
Definition NimBLEAttValue.h:249
bool setValue(const char *s, uint16_t len=0)
Set value to the value of const char*.
Definition NimBLEAttValue.h:300
T getValue(time_t *timestamp=nullptr, bool skipSizeCheck=false) const
Template to return the value as a <type>.
Definition NimBLEAttValue.h:453
uint16_t max_size() const
Returns the max size in bytes.
Definition NimBLEAttValue.h:240
bool setValue(const T &s)
Template to set value to the value of a char array using strnlen.
Definition NimBLEAttValue.h:347
bool operator==(const NimBLEAttValue &source) const
Equality operator.
Definition NimBLEAttValue.h:513
NimBLEAttValue & operator=(const std::string &source)
Operator; Set the value from a std::string source.
Definition NimBLEAttValue.h:501
const uint8_t * begin() const
Iterator begin; see data() for thread safety restrictions.
Definition NimBLEAttValue.h:264
bool operator!=(const NimBLEAttValue &source) const
Inequality operator.
Definition NimBLEAttValue.h:519