Skip to content

Commit 3e1b9e0

Browse files
committed
Refactor notify/indicate
This refactors the handling of sending notifications and indications for greater efficiency. * Adds client subscription state tracking to NimBLECharacteristic rather than relying on the stack. * Notifications/indications are now sent directly, no longer calling the callback to read the values. This avoids delays and flash writes in the stack, allowing for greater throughput.
1 parent 66fbe50 commit 3e1b9e0

File tree

4 files changed

+216
-92
lines changed

4 files changed

+216
-92
lines changed

src/NimBLECharacteristic.cpp

Lines changed: 141 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
* limitations under the License.
1616
*/
1717

18-
# include "NimBLECharacteristic.h"
18+
#include "NimBLECharacteristic.h"
1919
#if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL)
2020

21-
#if defined(CONFIG_NIMBLE_CPP_IDF)
22-
# if !defined(ESP_IDF_VERSION_MAJOR) || ESP_IDF_VERSION_MAJOR < 5
23-
# define ble_gatts_notify_custom ble_gattc_notify_custom
24-
# define ble_gatts_indicate_custom ble_gattc_indicate_custom
21+
# if defined(CONFIG_NIMBLE_CPP_IDF)
22+
# if !defined(ESP_IDF_VERSION_MAJOR) || ESP_IDF_VERSION_MAJOR < 5
23+
# define ble_gatts_notify_custom ble_gattc_notify_custom
24+
# define ble_gatts_indicate_custom ble_gattc_indicate_custom
25+
# endif
2526
# endif
26-
#endif
2727

2828
# include "NimBLE2904.h"
2929
# include "NimBLEDevice.h"
@@ -225,7 +225,8 @@ void NimBLECharacteristic::setService(NimBLEService* pService) {
225225
* @return True if the indication was sent successfully, false otherwise.
226226
*/
227227
bool NimBLECharacteristic::indicate(uint16_t connHandle) const {
228-
return sendValue(nullptr, 0, false, connHandle);
228+
auto value{m_value}; // make a copy to avoid issues if the value is changed while indicating
229+
return sendValue(value.data(), value.size(), false, connHandle);
229230
} // indicate
230231

231232
/**
@@ -247,7 +248,8 @@ bool NimBLECharacteristic::indicate(const uint8_t* value, size_t length, uint16_
247248
* @return True if the notification was sent successfully, false otherwise.
248249
*/
249250
bool NimBLECharacteristic::notify(uint16_t connHandle) const {
250-
return sendValue(nullptr, 0, true, connHandle);
251+
auto value{m_value}; // make a copy to avoid issues if the value is changed while notifying
252+
return sendValue(value.data(), value.size(), true, connHandle);
251253
} // notify
252254

253255
/**
@@ -271,55 +273,50 @@ bool NimBLECharacteristic::notify(const uint8_t* value, size_t length, uint16_t
271273
* @return True if the value was sent successfully, false otherwise.
272274
*/
273275
bool NimBLECharacteristic::sendValue(const uint8_t* value, size_t length, bool isNotification, uint16_t connHandle) const {
274-
int rc = 0;
275-
276-
if (value != nullptr && length > 0) { // custom notification value
277-
os_mbuf* om = nullptr;
278-
279-
if (connHandle != BLE_HS_CONN_HANDLE_NONE) { // only sending to specific peer
280-
om = ble_hs_mbuf_from_flat(value, length);
281-
if (!om) {
282-
rc = BLE_HS_ENOMEM;
283-
goto done;
284-
}
285-
286-
// Null buffer will read the value from the characteristic
287-
if (isNotification) {
288-
rc = ble_gatts_notify_custom(connHandle, m_handle, om);
289-
} else {
290-
rc = ble_gatts_indicate_custom(connHandle, m_handle, om);
291-
}
276+
ble_npl_hw_enter_critical();
277+
const auto subs = getSubscribers(); // make a copy to avoid issues if subscribers change while sending
278+
ble_npl_hw_exit_critical(0);
279+
280+
bool chSpecified = connHandle != BLE_HS_CONN_HANDLE_NONE;
281+
bool requireSecure = m_properties & (BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_READ_AUTHEN | BLE_GATT_CHR_F_READ_AUTHOR);
282+
int rc = chSpecified ? BLE_HS_ENOENT : 0; // if handle specified, assume not found until sent
283+
284+
// Notify all connected peers unless a specific handle is provided
285+
for (const auto& entry : subs) {
286+
uint16_t ch = entry.getConnHandle();
287+
if (ch == BLE_HS_CONN_HANDLE_NONE || (chSpecified && ch != connHandle)) {
288+
continue;
289+
}
292290

293-
goto done;
291+
if (requireSecure && !entry.isSecured()) {
292+
NIMBLE_LOGW(LOG_TAG, "skipping notify/indicate to connHandle=%d, link not secured", entry.getConnHandle());
293+
continue;
294294
}
295295

296-
// Notify all connected peers unless a specific handle is provided
297-
for (const auto& ch : NimBLEDevice::getServer()->getPeerDevices()) {
298-
// Must re-create the data buffer on each iteration because it is freed by the calls bellow.
296+
// Must re-create the data buffer on each iteration because it is freed by the calls below.
297+
uint8_t retries = 10; // wait up to 10ms for a free buffer
298+
os_mbuf* om = ble_hs_mbuf_from_flat(value, length);
299+
while (!om && --retries) {
300+
ble_npl_time_delay(ble_npl_time_ms_to_ticks32(1));
299301
om = ble_hs_mbuf_from_flat(value, length);
300-
if (!om) {
301-
rc = BLE_HS_ENOMEM;
302-
goto done;
303-
}
302+
}
304303

305-
if (isNotification) {
306-
rc = ble_gatts_notify_custom(ch, m_handle, om);
307-
} else {
308-
rc = ble_gatts_indicate_custom(ch, m_handle, om);
309-
}
304+
if (!om) {
305+
rc = BLE_HS_ENOMEM;
306+
break;
310307
}
311-
} else if (connHandle != BLE_HS_CONN_HANDLE_NONE) {
312-
// Null buffer will read the value from the characteristic
308+
313309
if (isNotification) {
314-
rc = ble_gatts_notify_custom(connHandle, m_handle, nullptr);
310+
rc = ble_gatts_notify_custom(ch, m_handle, om);
315311
} else {
316-
rc = ble_gatts_indicate_custom(connHandle, m_handle, nullptr);
312+
rc = ble_gatts_indicate_custom(ch, m_handle, om);
313+
}
314+
315+
if (rc != 0 || chSpecified) {
316+
break;
317317
}
318-
} else { // Notify or indicate to all connected peers the characteristic value
319-
ble_gatts_chr_updated(m_handle);
320318
}
321319

322-
done:
323320
if (rc != 0) {
324321
NIMBLE_LOGE(LOG_TAG, "failed to send value, rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
325322
return false;
@@ -328,10 +325,108 @@ bool NimBLECharacteristic::sendValue(const uint8_t* value, size_t length, bool i
328325
return true;
329326
} // sendValue
330327

328+
/**
329+
* @brief Process a subscription or unsubscription request from a peer.
330+
* @param[in] connInfo A reference to the connection info of the peer.
331+
* @param[in] subVal The subscription value (bitmask).
332+
*/
333+
void NimBLECharacteristic::processSubRequest(NimBLEConnInfo& connInfo, uint8_t subVal) const {
334+
// Only allocate subscribers for characteristics that support notify or indicate.
335+
const uint16_t props = getProperties();
336+
if (!(props & (BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_INDICATE))) {
337+
return;
338+
}
339+
340+
auto found = false;
341+
auto firstFree = -1;
342+
for (auto& entry : m_subPeers) {
343+
if (entry.getConnHandle() == connInfo.getConnHandle()) {
344+
found = true;
345+
if (!subVal) {
346+
ble_npl_hw_enter_critical();
347+
entry = SubPeerEntry{}; // unsubscribed, reset entry
348+
ble_npl_hw_exit_critical(0);
349+
}
350+
break;
351+
}
352+
353+
if (firstFree == -1 && entry.getConnHandle() == BLE_HS_CONN_HANDLE_NONE) {
354+
firstFree = &entry - &m_subPeers[0];
355+
}
356+
}
357+
358+
if (!found && subVal) {
359+
if (firstFree >= 0) {
360+
ble_npl_hw_enter_critical();
361+
m_subPeers[firstFree].setConnHandle(connInfo.getConnHandle());
362+
m_subPeers[firstFree].setSubNotify(subVal & 0x1);
363+
m_subPeers[firstFree].setSubIndicate(subVal & 0x2);
364+
m_subPeers[firstFree].setSecured(connInfo.isEncrypted() || connInfo.isAuthenticated() || connInfo.isBonded());
365+
if (m_properties & (BLE_GATT_CHR_F_READ_AUTHEN | BLE_GATT_CHR_F_READ_AUTHOR | BLE_GATT_CHR_F_READ_ENC)) {
366+
// characteristic requires security/authorization
367+
if (!m_subPeers[firstFree].isSecured()) {
368+
m_subPeers[firstFree].setAwaitingSecure(true);
369+
ble_npl_hw_exit_critical(0);
370+
NimBLEDevice::startSecurity(connInfo.getConnHandle());
371+
NIMBLE_LOGD(LOG_TAG,
372+
"Subscription deferred until link is secured for connHandle=%d",
373+
connInfo.getConnHandle());
374+
return;
375+
}
376+
}
377+
ble_npl_hw_exit_critical(0);
378+
} else {
379+
// should never happen, but log just in case
380+
NIMBLE_LOGE(LOG_TAG, "No free subscription slots");
381+
return;
382+
}
383+
}
384+
385+
m_pCallbacks->onSubscribe(const_cast<NimBLECharacteristic*>(this), const_cast<NimBLEConnInfo&>(connInfo), subVal);
386+
}
387+
388+
/**
389+
* @brief Update the security status of a subscribed peer.
390+
* @param[in] connHandle The connection handle of the peer.
391+
* @param[in] peerInfo A reference to the connection info of the peer.
392+
*/
393+
void NimBLECharacteristic::updatePeerStatus(const NimBLEConnInfo& peerInfo) const {
394+
if (!(getProperties() & (NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::INDICATE))) {
395+
return;
396+
}
397+
398+
ble_npl_hw_enter_critical();
399+
for (auto& entry : m_subPeers) {
400+
if (entry.getConnHandle() == peerInfo.getConnHandle()) {
401+
entry.setSecured(peerInfo.isEncrypted() || peerInfo.isAuthenticated() || peerInfo.isBonded());
402+
if (entry.isAwaitingSecure()) {
403+
entry.setAwaitingSecure(false);
404+
ble_npl_hw_exit_critical(0);
405+
m_pCallbacks->onSubscribe(const_cast<NimBLECharacteristic*>(this),
406+
const_cast<NimBLEConnInfo&>(peerInfo),
407+
entry.isSubNotify() | (entry.isSubIndicate() << 1));
408+
return;
409+
}
410+
break;
411+
}
412+
}
413+
ble_npl_hw_exit_critical(0);
414+
}
415+
416+
/**
417+
* @brief Handle a read event from a client.
418+
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
419+
*/
331420
void NimBLECharacteristic::readEvent(NimBLEConnInfo& connInfo) {
332421
m_pCallbacks->onRead(this, connInfo);
333422
} // readEvent
334423

424+
/**
425+
* @brief Handle a write event from a client.
426+
* @param [in] val A pointer to the data written by the client.
427+
* @param [in] len The length of the data written by the client.
428+
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
429+
*/
335430
void NimBLECharacteristic::writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) {
336431
setValue(val, len);
337432
m_pCallbacks->onWrite(this, connInfo);

src/NimBLECharacteristic.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class NimBLE2904;
3131

3232
# include <string>
3333
# include <vector>
34+
# include <array>
3435

3536
/**
3637
* @brief The model of a BLE Characteristic.
@@ -233,9 +234,34 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
233234
bool is_notification = true,
234235
uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const;
235236

237+
struct SubPeerEntry {
238+
enum : uint8_t { AWAITING_SECURE = 1 << 0, SECURE = 1 << 1, SUB_NOTIFY = 1 << 2, SUB_INDICATE = 1 << 3 };
239+
void setConnHandle(uint16_t connHandle) { m_connHandle = connHandle; }
240+
uint16_t getConnHandle() const { return m_connHandle; }
241+
void setAwaitingSecure(bool awaiting) { awaiting ? m_flags |= AWAITING_SECURE : m_flags &= ~AWAITING_SECURE; }
242+
void setSecured(bool secure) { secure ? m_flags |= SECURE : m_flags &= ~SECURE; }
243+
void setSubNotify(bool notify) { notify ? m_flags |= SUB_NOTIFY : m_flags &= ~SUB_NOTIFY; }
244+
void setSubIndicate(bool indicate) { indicate ? m_flags |= SUB_INDICATE : m_flags &= ~SUB_INDICATE; }
245+
bool isSubNotify() const { return m_flags & SUB_NOTIFY; }
246+
bool isSubIndicate() const { return m_flags & SUB_INDICATE; }
247+
bool isSecured() const { return m_flags & SECURE; }
248+
bool isAwaitingSecure() const { return m_flags & AWAITING_SECURE; }
249+
250+
private:
251+
uint16_t m_connHandle{BLE_HS_CONN_HANDLE_NONE};
252+
uint8_t m_flags{0};
253+
};
254+
__attribute__((packed));
255+
256+
using SubPeerArray = std::array<SubPeerEntry, MYNEWT_VAL(BLE_MAX_CONNECTIONS)>;
257+
SubPeerArray getSubscribers() const { return m_subPeers; }
258+
void processSubRequest(NimBLEConnInfo& connInfo, uint8_t subVal) const;
259+
void updatePeerStatus(const NimBLEConnInfo& peerInfo) const;
260+
236261
NimBLECharacteristicCallbacks* m_pCallbacks{nullptr};
237262
NimBLEService* m_pService{nullptr};
238263
std::vector<NimBLEDescriptor*> m_vDescriptors{};
264+
mutable SubPeerArray m_subPeers{};
239265
}; // NimBLECharacteristic
240266

241267
/**

0 commit comments

Comments
 (0)