nimble/host: Rework L2CAP RX

This reworks L2CAP RX path to simplify flow and fix reassembly issues.

RX is now done in conn context instead of chan context. This allows to
save few bytes of memory per chan since RX data are now held per-conn
instead of per-chan.

The new code also fixes reassembly issue where we couldn't properly
reassemble L2CAP SDU if the first fragment was shorter than the L2CAP
header.
This commit is contained in:
Andrzej Kaczmarek
2026-02-03 12:39:04 +05:30
committed by Rahul Tank
parent eed4636870
commit 4f5675f517
9 changed files with 169 additions and 307 deletions
+2 -2
View File
@@ -584,7 +584,7 @@ ble_att_rx_extended(uint16_t conn_handle, uint16_t cid, struct os_mbuf **om)
}
static int
ble_att_rx(struct ble_l2cap_chan *chan)
ble_att_rx(struct ble_l2cap_chan *chan, struct os_mbuf **om)
{
uint16_t conn_handle;
@@ -593,7 +593,7 @@ ble_att_rx(struct ble_l2cap_chan *chan)
return BLE_HS_ENOTCONN;
}
return ble_att_rx_extended(conn_handle, chan->scid, &chan->rx_buf);
return ble_att_rx_extended(conn_handle, chan->scid, om);
}
uint16_t
+6 -15
View File
@@ -252,15 +252,6 @@ err:
void
ble_hs_conn_delete_chan(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan)
{
#if MYNEWT_VAL(BT_NIMBLE_MEM_OPTIMIZATION)
if (conn->bhc_rx_scid == chan->scid) {
conn->bhc_rx_scid = 0x0000;
#else
if (conn->bhc_rx_chan == chan) {
conn->bhc_rx_chan = NULL;
#endif
}
SLIST_REMOVE(&conn->bhc_channels, chan, ble_l2cap_chan, next);
ble_l2cap_chan_free(conn, chan);
}
@@ -294,6 +285,10 @@ ble_hs_conn_free(struct ble_hs_conn *conn)
return;
}
os_mbuf_free_chain(conn->rx_frags);
conn->rx_frags = NULL;
#if MYNEWT_VAL(BLE_GATTS)
ble_att_svr_prep_clear(&conn->bhc_att_svr.basc_prep_list);
#endif
@@ -583,12 +578,8 @@ ble_hs_conn_timer(void)
* passes after a partial packet is received, the connection is
* terminated.
*/
#if MYNEWT_VAL(BT_NIMBLE_MEM_OPTIMIZATION)
if (conn->bhc_rx_scid != 0x0000) {
#else
if (conn->bhc_rx_chan != NULL) {
#endif
time_diff = conn->bhc_rx_timeout - now;
if (conn->rx_len) {
time_diff = conn->rx_frag_tmo - now;
if (time_diff <= 0) {
/* ACL reassembly has timed out.*/
+11 -3
View File
@@ -76,10 +76,18 @@ struct ble_hs_conn {
ble_hs_conn_flags_t bhc_flags;
struct ble_l2cap_chan_list bhc_channels;
#if !MYNEWT_VAL(BT_NIMBLE_MEM_OPTIMIZATION)
struct ble_l2cap_chan *bhc_rx_chan; /* Channel rxing current packet. */
/* ACL RX fragments */
struct os_mbuf *rx_frags;
/* Expected data length for ACL RX */
uint16_t rx_len;
/* L2CAP Source CID for ACL RX */
uint16_t rx_cid;
#if MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT) != 0
/* Timeout for next fragment for ACL RX */
ble_npl_time_t rx_frag_tmo;
#endif
ble_npl_time_t bhc_rx_timeout;
#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM)
uint32_t l2cap_coc_cid_mask[BLE_HS_CONN_L2CAP_COC_CID_MASK_LEN];
#endif
+7 -53
View File
@@ -1858,18 +1858,14 @@ int
ble_hs_hci_evt_acl_process(struct os_mbuf *om)
{
struct hci_data_hdr hci_hdr;
struct ble_hs_conn *conn;
ble_l2cap_rx_fn *rx_cb;
#if MYNEWT_VAL(BT_NIMBLE_MEM_OPTIMIZATION)
struct ble_l2cap_chan *chan;
#endif
uint16_t conn_handle;
int reject_cid;
uint8_t pb;
int rc;
rc = ble_hs_hci_util_data_hdr_strip(om, &hci_hdr);
if (rc != 0) {
goto err;
os_mbuf_free_chain(om);
return rc;
}
#if (BLETEST_THROUGHPUT_TEST == 0)
@@ -1885,56 +1881,14 @@ ble_hs_hci_evt_acl_process(struct os_mbuf *om)
#endif
if (hci_hdr.hdh_len != OS_MBUF_PKTHDR(om)->omp_len) {
rc = BLE_HS_EBADDATA;
goto err;
os_mbuf_free_chain(om);
return BLE_HS_EBADDATA;
}
conn_handle = BLE_HCI_DATA_HANDLE(hci_hdr.hdh_handle_pb_bc);
pb = BLE_HCI_DATA_PB(hci_hdr.hdh_handle_pb_bc);
rc = ble_l2cap_rx(conn_handle, pb, om);
ble_hs_lock();
conn = ble_hs_conn_find(conn_handle);
if (conn == NULL) {
/* Peer not connected; quietly discard packet. */
rc = BLE_HS_ENOTCONN;
reject_cid = -1;
} else {
/* Forward ACL data to L2CAP. */
rc = ble_l2cap_rx(conn, &hci_hdr, om, &rx_cb, &reject_cid);
om = NULL;
}
ble_hs_unlock();
switch (rc) {
case 0:
/* Final fragment received. */
BLE_HS_DBG_ASSERT(rx_cb != NULL);
#if MYNEWT_VAL(BT_NIMBLE_MEM_OPTIMIZATION)
chan = ble_hs_conn_chan_find_by_scid(conn,conn->bhc_rx_scid);
rc = rx_cb(chan);
ble_l2cap_remove_rx(conn, chan);
#else
rc = rx_cb(conn->bhc_rx_chan);
ble_l2cap_remove_rx(conn, conn->bhc_rx_chan);
#endif
break;
case BLE_HS_EAGAIN:
/* More fragments on the way. */
break;
default:
if (reject_cid != -1) {
ble_l2cap_sig_reject_invalid_cid_tx(conn_handle, 0, 0, reject_cid);
}
goto err;
}
return 0;
err:
os_mbuf_free_chain(om);
return rc;
}
#endif
+136 -201
View File
@@ -113,7 +113,6 @@ ble_l2cap_chan_free(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan)
return;
}
os_mbuf_free_chain(chan->rx_buf);
ble_l2cap_coc_cleanup_chan(conn, chan);
#if MYNEWT_VAL(BLE_HS_DEBUG)
@@ -139,18 +138,17 @@ ble_l2cap_is_mtu_req_sent(const struct ble_l2cap_chan *chan)
}
int
ble_l2cap_parse_hdr(struct os_mbuf *om, int off,
struct ble_l2cap_hdr *l2cap_hdr)
ble_l2cap_parse_hdr(struct os_mbuf *om, struct ble_l2cap_hdr *hdr)
{
int rc;
rc = os_mbuf_copydata(om, off, sizeof *l2cap_hdr, l2cap_hdr);
rc = os_mbuf_copydata(om, 0, sizeof(*hdr), hdr);
if (rc != 0) {
return BLE_HS_EMSGSIZE;
}
l2cap_hdr->len = get_le16(&l2cap_hdr->len);
l2cap_hdr->cid = get_le16(&l2cap_hdr->cid);
hdr->len = get_le16(&hdr->len);
hdr->cid = get_le16(&hdr->cid);
return 0;
}
@@ -296,91 +294,12 @@ ble_l2cap_recv_ready(struct ble_l2cap_chan *chan, struct os_mbuf *sdu_rx)
return ble_l2cap_coc_recv_ready(chan, sdu_rx);
}
void
ble_l2cap_remove_rx(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan)
{
#if MYNEWT_VAL(BT_NIMBLE_MEM_OPTIMIZATION)
conn->bhc_rx_scid = 0x0000;
#else
conn->bhc_rx_chan = NULL;
#endif
if (chan->rx_buf == NULL) {
BLE_HS_LOG(INFO, "L2CAP remove_rx: rx_buf already NULL; "
"conn_handle=0x%04x scid=0x%04x\n",
conn->bhc_handle, chan->scid);
} else {
BLE_HS_LOG(INFO, "L2CAP remove_rx: freeing partial rx_buf; "
"conn_handle=0x%04x scid=0x%04x rx_len=%d\n",
conn->bhc_handle, chan->scid, chan->rx_len);
}
os_mbuf_free_chain(chan->rx_buf);
chan->rx_buf = NULL;
chan->rx_len = 0;
}
static void
ble_l2cap_append_rx(struct ble_l2cap_chan *chan, struct os_mbuf *frag)
{
#if MYNEWT_VAL(BLE_L2CAP_JOIN_RX_FRAGS)
struct os_mbuf *m;
/* Copy the data from the incoming fragment into the packet in progress. */
m = os_mbuf_pack_chains(chan->rx_buf, frag);
assert(m);
#else
/* Join disabled or append failed due to mbuf shortage. Just attach the
* mbuf to the end of the packet.
*/
os_mbuf_concat(chan->rx_buf, frag);
#endif
}
static int
ble_l2cap_rx_payload(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan,
struct os_mbuf *om,
ble_l2cap_rx_fn **out_rx_cb)
{
int len_diff;
int rc;
if (chan->rx_buf == NULL) {
/* First fragment in packet. */
chan->rx_buf = om;
} else {
/* Continuation of packet in progress. */
ble_l2cap_append_rx(chan, om);
}
/* Determine if packet is fully reassembled. */
len_diff = OS_MBUF_PKTLEN(chan->rx_buf) - chan->rx_len;
if (len_diff > 0) {
/* More data than expected; data corruption. */
ble_l2cap_remove_rx(conn, chan);
rc = BLE_HS_EBADDATA;
} else if (len_diff == 0) {
/* All fragments received. */
*out_rx_cb = chan->rx_fn;
rc = 0;
} else {
/* More fragments remain. */
#if MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT) != 0
conn->bhc_rx_timeout =
ble_npl_time_get() + MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT);
ble_hs_timer_resched();
#endif
rc = BLE_HS_EAGAIN;
}
return rc;
}
static uint16_t
ble_l2cap_get_mtu(struct ble_l2cap_chan *chan)
{
if (chan->scid == BLE_L2CAP_CID_ATT) {
/* In case of ATT chan->my_mtu keeps preferred MTU which is later
* used during exchange MTU procedure. Helper below will gives us actual
* used during exchange MTU procedure. Helper below gives us actual
* MTU on the channel, which is 23 or higher if exchange MTU has been
* done
*/
@@ -390,137 +309,153 @@ ble_l2cap_get_mtu(struct ble_l2cap_chan *chan)
return chan->my_mtu;
}
/**
* Processes an incoming L2CAP fragment.
*
* @param conn The connection the L2CAP fragment was sent
* over.
* @param hci_hdr The ACL data header that was at the start of
* the L2CAP fragment. This header has been
* stripped from the mbuf parameter.
* @param om An mbuf containing the L2CAP data. If this is
* the first fragment, the L2CAP header is at
* the start of the mbuf. For subsequent
* fragments, the mbuf starts with L2CAP
* payload data.
* @param out_rx_cb If a full L2CAP packet has been received, a
* pointer to the appropriate handler gets
* written here. The caller should pass the
* receive buffer to this callback.
* @param out_reject_cid Indicates whether an L2CAP Command Reject
* command should be sent. If this equals -1,
* no reject should get sent. Otherwise, the
* value indicates the CID that the outgoing
* reject should specify.
*
* @return 0 if a complete L2CAP packet has been received.
* BLE_HS_EAGAIN if a partial L2CAP packet has
* been received; more fragments are expected.
* Other value on error.
*/
int
ble_l2cap_rx(struct ble_hs_conn *conn,
struct hci_data_hdr *hci_hdr,
struct os_mbuf *om,
ble_l2cap_rx_fn **out_rx_cb,
int *out_reject_cid)
static void
ble_l2cap_rx_free(struct ble_hs_conn *conn)
{
struct ble_l2cap_chan *chan;
struct ble_l2cap_hdr l2cap_hdr;
uint8_t pb;
os_mbuf_free_chain(conn->rx_frags);
conn->rx_frags = NULL;
conn->rx_len = 0;
conn->rx_cid = 0;
}
static int
ble_l2cap_rx_frags_process(struct ble_hs_conn *conn)
{
int rem_rx_len;
int rc;
*out_reject_cid = -1;
rem_rx_len = conn->rx_len - OS_MBUF_PKTLEN(conn->rx_frags);
if (rem_rx_len == 0) {
rc = 0;
} else if (rem_rx_len > 0) {
#if MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT) != 0
conn->rx_frag_tmo =
ble_npl_time_get() +
ble_npl_time_ms_to_ticks32(MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT));
ble_hs_timer_resched();
#endif
rc = BLE_HS_EAGAIN;
} else {
ble_l2cap_rx_free(conn);
rc = BLE_HS_EBADDATA;
}
return rc;
}
int
ble_l2cap_rx(uint16_t conn_handle, uint8_t pb, struct os_mbuf *om)
{
struct ble_hs_conn *conn;
struct ble_l2cap_chan *chan;
struct ble_l2cap_hdr hdr;
struct os_mbuf *rx_frags;
uint16_t rx_len;
uint16_t rx_cid;
int rc;
ble_hs_lock();
conn = ble_hs_conn_find(conn_handle);
if (!conn) {
/* Invalid connection handle, discard packet */
os_mbuf_free_chain(om);
rc = BLE_HS_ENOTCONN;
goto done;
}
pb = BLE_HCI_DATA_PB(hci_hdr->hdh_handle_pb_bc);
switch (pb) {
case BLE_HCI_PB_FIRST_FLUSH:
/* First fragment. */
rc = ble_l2cap_parse_hdr(om, 0, &l2cap_hdr);
if (rc != 0) {
goto err;
}
/* Strip L2CAP header from the front of the mbuf. */
os_mbuf_adj(om, BLE_L2CAP_HDR_SZ);
chan = ble_hs_conn_chan_find_by_scid(conn, l2cap_hdr.cid);
if (chan == NULL) {
rc = BLE_HS_ENOENT;
/* Unsupported channel. If the target CID is the black hole
* channel, quietly drop the packet. Otherwise, send an invalid
* CID response.
*/
if (l2cap_hdr.cid != BLE_L2CAP_CID_BLACK_HOLE) {
BLE_HS_LOG(DEBUG, "rx on unknown L2CAP channel: %d\n",
l2cap_hdr.cid);
*out_reject_cid = l2cap_hdr.cid;
}
goto err;
}
/* For CIDs from dynamic range we check if SDU size isn't larger than MPS */
if (chan->dcid >= 0x0040 && chan->dcid <= 0x007F && l2cap_hdr.len > (chan->my_coc_mps + BLE_L2CAP_SDU_SZ)) {
/* Data exceeds MPS */
BLE_HS_LOG(ERROR, "error: sdu_len > chan->my_coc_mps (%d>%d)\n",
l2cap_hdr.len, chan->my_coc_mps);
ble_l2cap_disconnect(chan);
rc = BLE_HS_EBADDATA;
goto err;
}
if (chan->rx_buf != NULL) {
/* Previous data packet never completed. Discard old packet. */
ble_l2cap_remove_rx(conn, chan);
}
if (l2cap_hdr.len - BLE_L2CAP_SDU_SZ > ble_l2cap_get_mtu(chan)) {
/* More data than we expected on the channel.
* Disconnect peer with invalid behaviour
*/
rc = BLE_HS_EBADDATA;
ble_l2cap_disconnect(chan);
goto err;
}
/* Remember channel and length of L2CAP data for reassembly. */
#if MYNEWT_VAL(BT_NIMBLE_MEM_OPTIMIZATION)
conn->bhc_rx_scid = chan->scid;
#else
conn->bhc_rx_chan = chan;
#endif
chan->rx_len = l2cap_hdr.len;
break;
case BLE_HCI_PB_MIDDLE:
#if MYNEWT_VAL(BT_NIMBLE_MEM_OPTIMIZATION)
chan = ble_hs_conn_chan_find_by_scid(conn,conn->bhc_rx_scid);
#else
chan = conn->bhc_rx_chan;
#endif
if (chan == NULL || chan->rx_buf == NULL) {
/* Middle fragment without the start. Discard new packet. */
rc = BLE_HS_EBADDATA;
goto err;
if (conn->rx_frags) {
/* Previously received data is incomplete, discard it */
ble_l2cap_rx_free(conn);
}
conn->rx_frags = om;
break;
case BLE_HCI_PB_MIDDLE:
if (!conn->rx_frags) {
/* Received continuation without 1st packet, discard it.
* This can also happen if we received invalid data earlier which
* was discarded, so we'll just keep discarding until valid 1st
* packet is received.
*/
os_mbuf_free_chain(om);
rc = BLE_HS_EBADDATA;
goto done;
}
/* Append fragment to rx buffer */
#if MYNEWT_VAL(BLE_L2CAP_JOIN_RX_FRAGS)
os_mbuf_pack_chains(conn->rx_frags, om);
#else
os_mbuf_concat(conn->rx_frag, om);
#endif
break;
default:
/* Invalid PB, discard packet */
os_mbuf_free_chain(om);
ble_l2cap_rx_free(conn);
rc = BLE_HS_EBADDATA;
goto err;
goto done;
}
rc = ble_l2cap_rx_payload(conn, chan, om, out_rx_cb);
om = NULL;
if (rc != 0) {
goto err;
/* Parse L2CAP header if not yet done */
if (!conn->rx_len) {
rc = ble_l2cap_parse_hdr(conn->rx_frags, &hdr);
if (rc) {
/* Incomplete header, wait for continuation */
rc = BLE_HS_EAGAIN;
goto done;
}
os_mbuf_adj(conn->rx_frags, BLE_L2CAP_HDR_SZ);
conn->rx_len = hdr.len;
conn->rx_cid = hdr.cid;
}
return 0;
/* Process fragments */
rc = ble_l2cap_rx_frags_process(conn);
if (rc) {
goto done;
}
rx_frags = conn->rx_frags;
rx_len = conn->rx_len;
rx_cid = conn->rx_cid;
conn->rx_frags = NULL;
ble_l2cap_rx_free(conn);
chan = ble_hs_conn_chan_find_by_scid(conn, rx_cid);
ble_hs_unlock();
if (!chan) {
ble_l2cap_sig_reject_invalid_cid_tx(conn_handle, 0, 0, rx_cid);
return BLE_HS_ENOENT;
}
if (chan->dcid >= BLE_L2CAP_COC_CID_START &&
chan->dcid <= BLE_L2CAP_COC_CID_END && rx_len > chan->my_coc_mps) {
ble_l2cap_disconnect(chan);
return BLE_HS_EBADDATA;
}
if (rx_len > ble_l2cap_get_mtu(chan)) {
ble_l2cap_disconnect(chan);
return BLE_HS_EBADDATA;
}
rc = chan->rx_fn(chan, &rx_frags);
os_mbuf_free_chain(rx_frags);
return rc;
done:
ble_hs_unlock();
err:
os_mbuf_free_chain(om);
return rc;
}
+1 -6
View File
@@ -202,18 +202,13 @@ ble_l2cap_event_coc_received_data(struct ble_l2cap_chan *chan,
}
static int
ble_l2cap_coc_rx_fn(struct ble_l2cap_chan *chan)
ble_l2cap_coc_rx_fn(struct ble_l2cap_chan *chan, struct os_mbuf **om)
{
int rc;
struct os_mbuf **om;
struct os_mbuf *rx_sdu;
struct ble_l2cap_coc_endpoint *rx;
uint16_t om_total;
/* Create a shortcut to rx_buf */
om = &chan->rx_buf;
BLE_HS_DBG_ASSERT(*om != NULL);
/* Create a shortcut to rx endpoint */
rx = &chan->coc_rx;
BLE_HS_DBG_ASSERT(rx != NULL);
+3 -18
View File
@@ -59,17 +59,12 @@ extern ble_l2cap_ctx_t *ble_l2cap_ctx;
extern struct os_mempool ble_l2cap_chan_pool;
#endif
/* This is nimble specific; packets sent to the black hole CID do not elicit
* an "invalid CID" response.
*/
#define BLE_L2CAP_CID_BLACK_HOLE 0xffff
#define BLE_L2CAP_HDR_SZ 4
#define BLE_L2CAP_SDU_SZ 2
typedef uint8_t ble_l2cap_chan_flags;
typedef int ble_l2cap_rx_fn(struct ble_l2cap_chan *chan);
typedef int ble_l2cap_rx_fn(struct ble_l2cap_chan *chan, struct os_mbuf **om);
struct ble_l2cap_chan {
SLIST_ENTRY(ble_l2cap_chan) next;
@@ -92,9 +87,6 @@ struct ble_l2cap_chan {
ble_l2cap_chan_flags flags;
struct os_mbuf *rx_buf;
uint16_t rx_len; /* Length of current reassembled rx packet. */
ble_l2cap_rx_fn *rx_fn;
#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) != 0
@@ -133,8 +125,7 @@ typedef int ble_l2cap_tx_fn(struct ble_hs_conn *conn,
SLIST_HEAD(ble_l2cap_chan_list, ble_l2cap_chan);
int ble_l2cap_parse_hdr(struct os_mbuf *om, int off,
struct ble_l2cap_hdr *l2cap_hdr);
int ble_l2cap_parse_hdr(struct os_mbuf *om, struct ble_l2cap_hdr *hdr);
struct os_mbuf *ble_l2cap_prepend_hdr(struct os_mbuf *om, uint16_t cid,
uint16_t len);
@@ -143,16 +134,10 @@ void ble_l2cap_chan_free(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan);
bool ble_l2cap_is_mtu_req_sent(const struct ble_l2cap_chan *chan);
int ble_l2cap_rx(struct ble_hs_conn *conn,
struct hci_data_hdr *hci_hdr,
struct os_mbuf *om,
ble_l2cap_rx_fn **out_rx_cb,
int *out_reject_cid);
int ble_l2cap_rx(uint16_t conn_handle, uint8_t pb, struct os_mbuf *om);
int ble_l2cap_tx(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan,
struct os_mbuf *txom);
void ble_l2cap_remove_rx(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan);
int ble_l2cap_init(void);
#if MYNEWT_VAL(BLE_STATIC_TO_DYNAMIC)
void ble_l2cap_deinit(void);
+1 -3
View File
@@ -1880,16 +1880,14 @@ ble_l2cap_sig_rx_reject(uint16_t conn_handle,
*****************************************************************************/
static int
ble_l2cap_sig_rx(struct ble_l2cap_chan *chan)
ble_l2cap_sig_rx(struct ble_l2cap_chan *chan, struct os_mbuf **om)
{
struct ble_l2cap_sig_hdr hdr;
ble_l2cap_sig_rx_fn *rx_cb;
uint16_t conn_handle;
struct os_mbuf **om;
int rc;
conn_handle = chan->conn_handle;
om = &chan->rx_buf;
STATS_INC(ble_l2cap_stats, sig_rx);
+2 -6
View File
@@ -3029,13 +3029,12 @@ ble_sm_enc_initiate(uint16_t conn_handle, uint8_t key_size,
}
static int
ble_sm_rx(struct ble_l2cap_chan *chan)
ble_sm_rx(struct ble_l2cap_chan *chan, struct os_mbuf **om)
{
struct ble_sm_result res;
ble_sm_rx_fn *rx_cb;
uint8_t op;
uint16_t conn_handle;
struct os_mbuf **om;
int rc;
STATS_INC(ble_l2cap_stats, sm_rx);
@@ -3045,9 +3044,6 @@ ble_sm_rx(struct ble_l2cap_chan *chan)
return BLE_HS_ENOTCONN;
}
om = &chan->rx_buf;
BLE_HS_DBG_ASSERT(*om != NULL);
rc = os_mbuf_copydata(*om, 0, 1, &op);
if (rc != 0) {
return BLE_HS_EBADDATA;
@@ -3335,7 +3331,7 @@ ble_sm_deinit(void)
* simple
*/
static int
ble_sm_rx(struct ble_l2cap_chan *chan)
ble_sm_rx(struct ble_l2cap_chan *chan, struct os_mbuf **om)
{
struct ble_sm_pair_fail *cmd;
struct os_mbuf *txom;