Refactor L2Cap (style)

This commit is contained in:
h2zero
2025-05-05 18:14:19 -06:00
committed by Ryan Powell
parent a5702746c5
commit e29d1ce3cf
4 changed files with 69 additions and 65 deletions

View File

@ -7,21 +7,18 @@
#include "NimBLELog.h" #include "NimBLELog.h"
#include "NimBLEUtils.h" #include "NimBLEUtils.h"
#include "nimble/nimble_port.h"
// L2CAP buffer block size // L2CAP buffer block size
#define L2CAP_BUF_BLOCK_SIZE (250) #define L2CAP_BUF_BLOCK_SIZE (250)
#define L2CAP_BUF_SIZE_MTUS_PER_CHANNEL (3) #define L2CAP_BUF_SIZE_MTUS_PER_CHANNEL (3)
// Round-up integer division // Round-up integer division
#define CEIL_DIVIDE(a, b) (((a) + (b) - 1) / (b)) #define CEIL_DIVIDE(a, b) (((a) + (b) - 1) / (b))
#define ROUND_DIVIDE(a, b) (((a) + (b) / 2) / (b)) #define ROUND_DIVIDE(a, b) (((a) + (b) / 2) / (b))
// Retry // Retry
constexpr uint32_t RetryTimeout = 50; constexpr uint32_t RetryTimeout = 50;
constexpr int RetryCounter = 3; constexpr int RetryCounter = 3;
NimBLEL2CAPChannel::NimBLEL2CAPChannel(uint16_t psm, uint16_t mtu, NimBLEL2CAPChannelCallbacks* callbacks) NimBLEL2CAPChannel::NimBLEL2CAPChannel(uint16_t psm, uint16_t mtu, NimBLEL2CAPChannelCallbacks* callbacks)
:psm(psm), mtu(mtu), callbacks(callbacks) { : psm(psm), mtu(mtu), callbacks(callbacks) {
assert(mtu); // fail here, if MTU is too little assert(mtu); // fail here, if MTU is too little
assert(callbacks); // fail here, if no callbacks are given assert(callbacks); // fail here, if no callbacks are given
assert(setupMemPool()); // fail here, if the memory pool could not be setup assert(setupMemPool()); // fail here, if the memory pool could not be setup
@ -30,14 +27,12 @@ NimBLEL2CAPChannel::NimBLEL2CAPChannel(uint16_t psm, uint16_t mtu, NimBLEL2CAPCh
}; };
NimBLEL2CAPChannel::~NimBLEL2CAPChannel() { NimBLEL2CAPChannel::~NimBLEL2CAPChannel() {
teardownMemPool(); teardownMemPool();
NIMBLE_LOGI(LOG_TAG, "L2CAP COC 0x%04X shutdown and freed.", this->psm); NIMBLE_LOGI(LOG_TAG, "L2CAP COC 0x%04X shutdown and freed.", this->psm);
} }
bool NimBLEL2CAPChannel::setupMemPool() { bool NimBLEL2CAPChannel::setupMemPool() {
const size_t buf_blocks = CEIL_DIVIDE(mtu, L2CAP_BUF_BLOCK_SIZE) * L2CAP_BUF_SIZE_MTUS_PER_CHANNEL; const size_t buf_blocks = CEIL_DIVIDE(mtu, L2CAP_BUF_BLOCK_SIZE) * L2CAP_BUF_SIZE_MTUS_PER_CHANNEL;
NIMBLE_LOGD(LOG_TAG, "Computed number of buf_blocks = %d", buf_blocks); NIMBLE_LOGD(LOG_TAG, "Computed number of buf_blocks = %d", buf_blocks);
@ -59,7 +54,7 @@ bool NimBLEL2CAPChannel::setupMemPool() {
return false; return false;
} }
this->receiveBuffer = (uint8_t*) malloc(mtu); this->receiveBuffer = (uint8_t*)malloc(mtu);
if (!this->receiveBuffer) { if (!this->receiveBuffer) {
NIMBLE_LOGE(LOG_TAG, "Can't malloc receive buffer: %d, %s", errno, strerror(errno)); NIMBLE_LOGE(LOG_TAG, "Can't malloc receive buffer: %d, %s", errno, strerror(errno));
return false; return false;
@ -69,14 +64,18 @@ bool NimBLEL2CAPChannel::setupMemPool() {
} }
void NimBLEL2CAPChannel::teardownMemPool() { void NimBLEL2CAPChannel::teardownMemPool() {
if (this->callbacks) {
if (this->callbacks) { delete this->callbacks; } delete this->callbacks;
if (this->receiveBuffer) { free(this->receiveBuffer); } }
if (_coc_memory) { free(_coc_memory); } if (this->receiveBuffer) {
free(this->receiveBuffer);
}
if (_coc_memory) {
free(_coc_memory);
}
} }
int NimBLEL2CAPChannel::writeFragment(std::vector<uint8_t>::const_iterator begin, std::vector<uint8_t>::const_iterator end) { int NimBLEL2CAPChannel::writeFragment(std::vector<uint8_t>::const_iterator begin, std::vector<uint8_t>::const_iterator end) {
auto toSend = end - begin; auto toSend = end - begin;
if (stalled) { if (stalled) {
@ -101,7 +100,6 @@ int NimBLEL2CAPChannel::writeFragment(std::vector<uint8_t>::const_iterator begin
auto retries = RetryCounter; auto retries = RetryCounter;
while (retries--) { while (retries--) {
auto txd = os_mbuf_get_pkthdr(&_coc_mbuf_pool, 0); auto txd = os_mbuf_get_pkthdr(&_coc_mbuf_pool, 0);
if (!txd) { if (!txd) {
NIMBLE_LOGE(LOG_TAG, "Can't os_mbuf_get_pkthdr."); NIMBLE_LOGE(LOG_TAG, "Can't os_mbuf_get_pkthdr.");
@ -115,10 +113,15 @@ int NimBLEL2CAPChannel::writeFragment(std::vector<uint8_t>::const_iterator begin
auto res = ble_l2cap_send(channel, txd); auto res = ble_l2cap_send(channel, txd);
switch (res) { switch (res) {
case 0:
NIMBLE_LOGD(LOG_TAG, "L2CAP COC 0x%04X sent %d bytes.", this->psm, toSend);
return 0;
case BLE_HS_ESTALLED: case BLE_HS_ESTALLED:
stalled = true; stalled = true;
NIMBLE_LOGD(LOG_TAG, "L2CAP COC 0x%04X sent %d bytes.", this->psm, toSend); NIMBLE_LOGD(LOG_TAG, "L2CAP COC 0x%04X sent %d bytes.", this->psm, toSend);
NIMBLE_LOGW(LOG_TAG, "ble_l2cap_send returned BLE_HS_ESTALLED. Next send will wait for unstalled event..."); NIMBLE_LOGW(LOG_TAG,
"ble_l2cap_send returned BLE_HS_ESTALLED. Next send will wait for unstalled event...");
return 0; return 0;
case BLE_HS_ENOMEM: case BLE_HS_ENOMEM:
@ -129,14 +132,9 @@ int NimBLEL2CAPChannel::writeFragment(std::vector<uint8_t>::const_iterator begin
ble_npl_time_delay(ble_npl_time_ms_to_ticks32(RetryTimeout)); ble_npl_time_delay(ble_npl_time_ms_to_ticks32(RetryTimeout));
continue; continue;
case ESP_OK:
NIMBLE_LOGD(LOG_TAG, "L2CAP COC 0x%04X sent %d bytes.", this->psm, toSend);
return 0;
default: default:
NIMBLE_LOGE(LOG_TAG, "ble_l2cap_send failed: %d", res); NIMBLE_LOGE(LOG_TAG, "ble_l2cap_send failed: %d", res);
return res; return res;
} }
} }
NIMBLE_LOGE(LOG_TAG, "Retries exhausted, dropping %d bytes to send.", toSend); NIMBLE_LOGE(LOG_TAG, "Retries exhausted, dropping %d bytes to send.", toSend);
@ -144,10 +142,14 @@ int NimBLEL2CAPChannel::writeFragment(std::vector<uint8_t>::const_iterator begin
} }
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
NimBLEL2CAPChannel* NimBLEL2CAPChannel::connect(NimBLEClient* client, uint16_t psm, uint16_t mtu, NimBLEL2CAPChannelCallbacks* callbacks) { NimBLEL2CAPChannel* NimBLEL2CAPChannel::connect(NimBLEClient* client,
uint16_t psm,
uint16_t mtu,
NimBLEL2CAPChannelCallbacks* callbacks) {
if (!client->isConnected()) { if (!client->isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Client is not connected. Before connecting via L2CAP, a GAP connection must have been established"); NIMBLE_LOGE(
LOG_TAG,
"Client is not connected. Before connecting via L2CAP, a GAP connection must have been established");
return nullptr; return nullptr;
}; };
@ -167,7 +169,6 @@ NimBLEL2CAPChannel* NimBLEL2CAPChannel::connect(NimBLEClient* client, uint16_t p
#endif // CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL #endif // CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL
bool NimBLEL2CAPChannel::write(const std::vector<uint8_t>& bytes) { bool NimBLEL2CAPChannel::write(const std::vector<uint8_t>& bytes) {
if (!this->channel) { if (!this->channel) {
NIMBLE_LOGW(LOG_TAG, "L2CAP Channel not open"); NIMBLE_LOGW(LOG_TAG, "L2CAP Channel not open");
return false; return false;
@ -177,7 +178,6 @@ bool NimBLEL2CAPChannel::write(const std::vector<uint8_t>& bytes) {
ble_l2cap_get_chan_info(channel, &info); ble_l2cap_get_chan_info(channel, &info);
auto mtu = info.peer_coc_mtu < info.our_coc_mtu ? info.peer_coc_mtu : info.our_coc_mtu; auto mtu = info.peer_coc_mtu < info.our_coc_mtu ? info.peer_coc_mtu : info.our_coc_mtu;
auto start = bytes.begin(); auto start = bytes.begin();
while (start != bytes.end()) { while (start != bytes.end()) {
auto end = start + mtu < bytes.end() ? start + mtu : bytes.end(); auto end = start + mtu < bytes.end() ? start + mtu : bytes.end();
@ -191,12 +191,16 @@ bool NimBLEL2CAPChannel::write(const std::vector<uint8_t>& bytes) {
// private // private
int NimBLEL2CAPChannel::handleConnectionEvent(struct ble_l2cap_event* event) { int NimBLEL2CAPChannel::handleConnectionEvent(struct ble_l2cap_event* event) {
channel = event->connect.chan; channel = event->connect.chan;
struct ble_l2cap_chan_info info; struct ble_l2cap_chan_info info;
ble_l2cap_get_chan_info(channel, &info); ble_l2cap_get_chan_info(channel, &info);
NIMBLE_LOGI(LOG_TAG, "L2CAP COC 0x%04X connected. Local MTU = %d [%d], remote MTU = %d [%d].", psm, NIMBLE_LOGI(LOG_TAG,
info.our_coc_mtu, info.our_l2cap_mtu, info.peer_coc_mtu, info.peer_l2cap_mtu); "L2CAP COC 0x%04X connected. Local MTU = %d [%d], remote MTU = %d [%d].",
psm,
info.our_coc_mtu,
info.our_l2cap_mtu,
info.peer_coc_mtu,
info.peer_l2cap_mtu);
if (info.our_coc_mtu > info.peer_coc_mtu) { if (info.our_coc_mtu > info.peer_coc_mtu) {
NIMBLE_LOGW(LOG_TAG, "L2CAP COC 0x%04X connected, but local MTU is bigger than remote MTU.", psm); NIMBLE_LOGW(LOG_TAG, "L2CAP COC 0x%04X connected, but local MTU is bigger than remote MTU.", psm);
} }
@ -212,7 +216,7 @@ int NimBLEL2CAPChannel::handleAcceptEvent(struct ble_l2cap_event* event) {
return -1; return -1;
} }
struct os_mbuf *sdu_rx = os_mbuf_get_pkthdr(&_coc_mbuf_pool, 0); struct os_mbuf* sdu_rx = os_mbuf_get_pkthdr(&_coc_mbuf_pool, 0);
assert(sdu_rx != NULL); assert(sdu_rx != NULL);
ble_l2cap_recv_ready(event->accept.chan, sdu_rx); ble_l2cap_recv_ready(event->accept.chan, sdu_rx);
return 0; return 0;
@ -264,8 +268,7 @@ int NimBLEL2CAPChannel::handleDisconnectionEvent(struct ble_l2cap_event* event)
} }
/* STATIC */ /* STATIC */
int NimBLEL2CAPChannel::handleL2capEvent(struct ble_l2cap_event *event, void *arg) { int NimBLEL2CAPChannel::handleL2capEvent(struct ble_l2cap_event* event, void* arg) {
NIMBLE_LOGD(LOG_TAG, "handleL2capEvent: handling l2cap event %d", event->type); NIMBLE_LOGD(LOG_TAG, "handleL2capEvent: handling l2cap event %d", event->type);
NimBLEL2CAPChannel* self = reinterpret_cast<NimBLEL2CAPChannel*>(arg); NimBLEL2CAPChannel* self = reinterpret_cast<NimBLEL2CAPChannel*>(arg);

View File

@ -3,20 +3,26 @@
// //
#pragma once #pragma once
#ifndef NIMBLEL2CAPCHANNEL_H #ifndef NIMBLEL2CAPCHANNEL_H
#define NIMBLEL2CAPCHANNEL_H # define NIMBLEL2CAPCHANNEL_H
#include "inttypes.h" # include "nimconfig.h"
#include "host/ble_l2cap.h"
#include "os/os_mbuf.h" # include "inttypes.h"
#include "os/os_mempool.h" # if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_l2cap.h"
# include "os/os_mbuf.h"
# else
# include "nimble/nimble/host/include/host/ble_l2cap.h"
# include "nimble/porting/nimble/include/os/os_mbuf.h"
# endif
/**** FIX COMPILATION ****/ /**** FIX COMPILATION ****/
# undef min # undef min
# undef max # undef max
/**************************/ /**************************/
#include <vector> # include <vector>
#include <atomic> # include <atomic>
class NimBLEClient; class NimBLEClient;
class NimBLEL2CAPChannelCallbacks; class NimBLEL2CAPChannelCallbacks;
@ -30,8 +36,7 @@ struct NimBLETaskData;
* (which opens the connection) point of view. * (which opens the connection) point of view.
*/ */
class NimBLEL2CAPChannel { class NimBLEL2CAPChannel {
public:
public:
/// @brief Open an L2CAP channel via the specified PSM and MTU. /// @brief Open an L2CAP channel via the specified PSM and MTU.
/// @param[in] psm The PSM to use. /// @param[in] psm The PSM to use.
/// @param[in] mtu The MTU to use. Note that this is the local MTU. Upon opening the channel, /// @param[in] mtu The MTU to use. Note that this is the local MTU. Upon opening the channel,
@ -53,8 +58,7 @@ public:
/// @return True, if the channel is connected. False, otherwise. /// @return True, if the channel is connected. False, otherwise.
bool isConnected() const { return !!channel; } bool isConnected() const { return !!channel; }
protected: protected:
NimBLEL2CAPChannel(uint16_t psm, uint16_t mtu, NimBLEL2CAPChannelCallbacks* callbacks); NimBLEL2CAPChannel(uint16_t psm, uint16_t mtu, NimBLEL2CAPChannelCallbacks* callbacks);
~NimBLEL2CAPChannel(); ~NimBLEL2CAPChannel();
@ -64,19 +68,19 @@ protected:
int handleTxUnstalledEvent(struct ble_l2cap_event* event); int handleTxUnstalledEvent(struct ble_l2cap_event* event);
int handleDisconnectionEvent(struct ble_l2cap_event* event); int handleDisconnectionEvent(struct ble_l2cap_event* event);
private: private:
friend class NimBLEL2CAPServer; friend class NimBLEL2CAPServer;
static constexpr const char* LOG_TAG = "NimBLEL2CAPChannel"; static constexpr const char* LOG_TAG = "NimBLEL2CAPChannel";
const uint16_t psm; // PSM of the channel const uint16_t psm; // PSM of the channel
const uint16_t mtu; // The requested (local) MTU of the channel, might be larger than negotiated MTU const uint16_t mtu; // The requested (local) MTU of the channel, might be larger than negotiated MTU
struct ble_l2cap_chan* channel = nullptr; struct ble_l2cap_chan* channel = nullptr;
NimBLEL2CAPChannelCallbacks* callbacks; NimBLEL2CAPChannelCallbacks* callbacks;
uint8_t* receiveBuffer = nullptr; // buffers a full (local) MTU uint8_t* receiveBuffer = nullptr; // buffers a full (local) MTU
// NimBLE memory pool // NimBLE memory pool
void* _coc_memory = nullptr; void* _coc_memory = nullptr;
struct os_mempool _coc_mempool; struct os_mempool _coc_mempool;
struct os_mbuf_pool _coc_mbuf_pool; struct os_mbuf_pool _coc_mbuf_pool;
// Runtime handling // Runtime handling
@ -91,16 +95,15 @@ private:
int writeFragment(std::vector<uint8_t>::const_iterator begin, std::vector<uint8_t>::const_iterator end); int writeFragment(std::vector<uint8_t>::const_iterator begin, std::vector<uint8_t>::const_iterator end);
// L2CAP event handler // L2CAP event handler
static int handleL2capEvent(struct ble_l2cap_event* event, void *arg); static int handleL2capEvent(struct ble_l2cap_event* event, void* arg);
}; };
/** /**
* @brief Callbacks base class for the L2CAP channel. * @brief Callbacks base class for the L2CAP channel.
*/ */
class NimBLEL2CAPChannelCallbacks { class NimBLEL2CAPChannelCallbacks {
public:
public: NimBLEL2CAPChannelCallbacks() = default;
NimBLEL2CAPChannelCallbacks() = default;
virtual ~NimBLEL2CAPChannelCallbacks() = default; virtual ~NimBLEL2CAPChannelCallbacks() = default;
/// Called when the client attempts to open a channel on the server. /// Called when the client attempts to open a channel on the server.

View File

@ -9,22 +9,21 @@
static const char* LOG_TAG = "NimBLEL2CAPServer"; static const char* LOG_TAG = "NimBLEL2CAPServer";
NimBLEL2CAPServer::NimBLEL2CAPServer() { NimBLEL2CAPServer::NimBLEL2CAPServer() {
// Nothing to do here... // Nothing to do here...
} }
NimBLEL2CAPServer::~NimBLEL2CAPServer() { NimBLEL2CAPServer::~NimBLEL2CAPServer() {
// Delete all services // Delete all services
for (auto service: this->services) { for (auto service : this->services) {
delete service; delete service;
} }
} }
NimBLEL2CAPChannel* NimBLEL2CAPServer::createService(const uint16_t psm, const uint16_t mtu, NimBLEL2CAPChannelCallbacks* callbacks) { NimBLEL2CAPChannel* NimBLEL2CAPServer::createService(const uint16_t psm,
const uint16_t mtu,
NimBLEL2CAPChannelCallbacks* callbacks) {
auto service = new NimBLEL2CAPChannel(psm, mtu, callbacks); auto service = new NimBLEL2CAPChannel(psm, mtu, callbacks);
auto rc = ble_l2cap_create_server(psm, mtu, NimBLEL2CAPChannel::handleL2capEvent, service); auto rc = ble_l2cap_create_server(psm, mtu, NimBLEL2CAPChannel::handleL2capEvent, service);
if (rc != 0) { if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Could not ble_l2cap_create_server: %d", rc); NIMBLE_LOGE(LOG_TAG, "Could not ble_l2cap_create_server: %d", rc);

View File

@ -11,15 +11,14 @@
class NimBLEL2CAPChannel; class NimBLEL2CAPChannel;
class NimBLEL2CAPChannelCallbacks; class NimBLEL2CAPChannelCallbacks;
/** /**
* @brief L2CAP server class. * @brief L2CAP server class.
* *
* Encapsulates a L2CAP server that can hold multiple services. Every service is represented by a channel object * Encapsulates a L2CAP server that can hold multiple services. Every service is represented by a channel object
* and an assorted set of callbacks. * and an assorted set of callbacks.
*/ */
class NimBLEL2CAPServer { class NimBLEL2CAPServer {
public: public:
/// @brief Register a new L2CAP service instance. /// @brief Register a new L2CAP service instance.
/// @param psm The port multiplexor service number. /// @param psm The port multiplexor service number.
/// @param mtu The maximum transmission unit. /// @param mtu The maximum transmission unit.
@ -27,7 +26,7 @@ public:
/// @return the newly created object, if the server registration was successful. /// @return the newly created object, if the server registration was successful.
NimBLEL2CAPChannel* createService(const uint16_t psm, const uint16_t mtu, NimBLEL2CAPChannelCallbacks* callbacks); NimBLEL2CAPChannel* createService(const uint16_t psm, const uint16_t mtu, NimBLEL2CAPChannelCallbacks* callbacks);
private: private:
NimBLEL2CAPServer(); NimBLEL2CAPServer();
~NimBLEL2CAPServer(); ~NimBLEL2CAPServer();
std::vector<NimBLEL2CAPChannel*> services; std::vector<NimBLEL2CAPChannel*> services;