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 */
227227bool 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 */
249250bool 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 */
273275bool 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+ */
331420void 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+ */
335430void NimBLECharacteristic::writeEvent (const uint8_t * val, uint16_t len, NimBLEConnInfo& connInfo) {
336431 setValue (val, len);
337432 m_pCallbacks->onWrite (this , connInfo);
0 commit comments