From 54c048989d343ecfd1a8dd00ce9993cbd11685d8 Mon Sep 17 00:00:00 2001 From: Pavel Vasilyev Date: Mon, 22 Jan 2024 14:50:19 +0100 Subject: [PATCH] Bluetooth: Mesh: Send provisioning PDUs with randomized delay This implements the following statement from the section 5.3.3: Each Generic Provisioning PDU shall be sent after a random delay between 20 and 50 milliseconds. Signed-off-by: Pavel Vasilyev --- subsys/bluetooth/mesh/pb_adv.c | 226 +++++++++++++++++++++++++-------- 1 file changed, 175 insertions(+), 51 deletions(-) diff --git a/subsys/bluetooth/mesh/pb_adv.c b/subsys/bluetooth/mesh/pb_adv.c index f723ff48c14..c5c56b1e5b0 100644 --- a/subsys/bluetooth/mesh/pb_adv.c +++ b/subsys/bluetooth/mesh/pb_adv.c @@ -67,6 +67,8 @@ enum { ADV_LINK_INVALID, /* Error occurred during provisioning */ ADV_ACK_PENDING, /* An acknowledgment is being sent */ ADV_PROVISIONER, /* The link was opened as provisioner */ + ADV_LINK_ACK_SENDING, /* Link Ack tx was scheduled but not finished. */ + ADV_SENDING, /* Sending any PDU */ ADV_NUM_FLAGS, }; @@ -100,15 +102,30 @@ struct pb_adv { /* Transaction timeout in seconds */ uint8_t timeout; - /* Pending outgoing adv(s) */ + /* Pending outgoing adv(s) (Link Open, Gen Trans Start and Gen Trans Cont) */ struct bt_mesh_adv *adv[3]; + /* Index of the next adv to be sent */ + int next; + prov_bearer_send_complete_t cb; void *cb_data; /* Retransmit timer */ struct k_work_delayable retransmit; + + /* Unacked adv buffers (Link Ack, Link Close and Gen Trans Ack). Array size is + * hardcoded to 2 allowing to send Gen Trans Ack and Link Close at the same time. + */ + struct unacked_adv_ctx { + struct bt_mesh_adv *adv; + prov_bearer_send_complete_t cb; + void *cb_data; + } unacked[2]; + + /* Last sent unacked[] buffer */ + int last_unacked; } tx; /* Protocol timeout */ @@ -132,28 +149,124 @@ static void link_close(struct prov_rx *rx, struct net_buf_simple *buf); static void prov_link_close(enum prov_bearer_link_status status); static void close_link(enum prov_bearer_link_status status); -static void buf_sent(int err, void *user_data) -{ - enum prov_bearer_link_status reason = (enum prov_bearer_link_status)(int)user_data; +static void tx_work_handler(struct k_work *work); +static K_WORK_DELAYABLE_DEFINE(tx_work, tx_work_handler); - if (atomic_test_and_clear_bit(link.flags, ADV_LINK_CLOSING)) { - close_link(reason); +static void tx_schedule(void) +{ + uint16_t random_delay; + + if (atomic_test_bit(link.flags, ADV_SENDING)) { + LOG_DBG("Another tx is in progress"); return; } + + (void)bt_rand(&random_delay, sizeof(random_delay)); + random_delay = 20 + (random_delay % 30); + + LOG_DBG("Next PDU delayed by %ums", random_delay); + + (void)k_work_schedule(&tx_work, K_MSEC(random_delay)); } -static void buf_start(uint16_t duration, int err, void *user_data) +static int send_unacked(struct bt_mesh_adv *adv, prov_bearer_send_complete_t cb, + void *cb_data) +{ + for (int i = 0; i < ARRAY_SIZE(link.tx.unacked); i++) { + if (link.tx.unacked[i].adv != NULL) { + continue; + } + + link.tx.unacked[i].adv = adv; + link.tx.unacked[i].cb = cb; + link.tx.unacked[i].cb_data = cb_data; + + tx_schedule(); + + return 0; + } + + LOG_WRN("No memory to send unacked PDU: %s", bt_hex(adv->b.data, adv->b.len)); + return -ENOMEM; +} + +static void send_reliable(void) +{ + /* Dropping next tx adv index to start transmission from the first adv buffer. */ + link.tx.next = 0; + + tx_schedule(); +} + +static void delayed_adv_send_end(int err, void *user_data) +{ + bool unacked = (bool)user_data; + struct unacked_adv_ctx *unacked_adv = &link.tx.unacked[link.tx.last_unacked]; + + if (unacked && unacked_adv->adv != NULL) { + if (unacked_adv->cb) { + unacked_adv->cb(err, unacked_adv->cb_data); + } + + bt_mesh_adv_unref(unacked_adv->adv); + unacked_adv->adv = NULL; + } + + atomic_clear_bit(link.flags, ADV_SENDING); + tx_schedule(); +} + +static void delayed_adv_send_start(uint16_t duration, int err, void *user_data) { if (err) { - buf_sent(err, user_data); + delayed_adv_send_end(err, user_data); } } -static struct bt_mesh_send_cb buf_sent_cb = { - .start = buf_start, - .end = buf_sent, +static const struct bt_mesh_send_cb delayed_adv_send_cb = { + .start = delayed_adv_send_start, + .end = delayed_adv_send_end, }; +static void tx_work_handler(struct k_work *work) +{ + int i; + + /* Send Link Ack, Link Close and Gen Trans Ack first. */ + for (i = 0; i < ARRAY_SIZE(link.tx.unacked); i++) { + int idx = (i + link.tx.last_unacked) % ARRAY_SIZE(link.tx.unacked); + struct unacked_adv_ctx *unacked = &link.tx.unacked[idx]; + + if (!unacked->adv) { + continue; + } + + atomic_set_bit(link.flags, ADV_SENDING); + bt_mesh_adv_send(unacked->adv, &delayed_adv_send_cb, (void *)true); + + link.tx.last_unacked = idx; + + return; + } + + /* Send Trans Start, Trans Cont and Link Open */ + if (link.tx.next >= ARRAY_SIZE(link.tx.adv) || link.tx.adv[link.tx.next] == NULL) { + LOG_DBG("All PDUs were sent"); + return; + } + + atomic_set_bit(link.flags, ADV_SENDING); + bt_mesh_adv_send(link.tx.adv[link.tx.next], &delayed_adv_send_cb, (void *)false); + + link.tx.next++; + + if (link.tx.next == ARRAY_SIZE(link.tx.adv) || link.tx.adv[link.tx.next] == NULL) { + /* All ack-able PDUs are sent. Now we can run the retransmit timer. */ + LOG_DBG("Starting retransmit timer"); + k_work_reschedule(&link.tx.retransmit, RETRANSMIT_TIMEOUT); + } +} + static uint8_t last_seg(uint16_t len) { if (len <= START_PAYLOAD_MAX) { @@ -168,9 +281,11 @@ static uint8_t last_seg(uint16_t len) static void free_segments(void) { int i; + bool canceled = false; for (i = 0; i < ARRAY_SIZE(link.tx.adv); i++) { struct bt_mesh_adv *adv = link.tx.adv[i]; + int err; if (!adv) { break; @@ -180,14 +295,23 @@ static void free_segments(void) /* Terminate active adv */ if (adv->ctx.busy == 0U) { - bt_mesh_adv_terminate(adv); + err = bt_mesh_adv_terminate(adv); + if (err == 0) { + canceled = true; + } } else { /* Mark as canceled */ adv->ctx.busy = 0U; + canceled = true; } bt_mesh_adv_unref(adv); } + + if (canceled) { + atomic_clear_bit(link.flags, ADV_SENDING); + tx_schedule(); + } } static uint8_t next_transaction_id(uint8_t id) @@ -268,7 +392,7 @@ static struct bt_mesh_adv *adv_create(uint8_t retransmits) return adv; } -static void ack_complete(uint16_t duration, int err, void *user_data) +static void ack_complete(int err, void *user_data) { LOG_DBG("xact 0x%x complete", (uint8_t)link.tx.pending_ack); atomic_clear_bit(link.flags, ADV_ACK_PENDING); @@ -323,12 +447,9 @@ static void protocol_timeout(struct k_work *work) static void gen_prov_ack_send(uint8_t xact_id) { - static const struct bt_mesh_send_cb cb = { - .start = ack_complete, - }; - const struct bt_mesh_send_cb *complete; struct bt_mesh_adv *adv; bool pending = atomic_test_and_set_bit(link.flags, ADV_ACK_PENDING); + int err; LOG_DBG("xact_id 0x%x", xact_id); @@ -343,19 +464,18 @@ static void gen_prov_ack_send(uint8_t xact_id) return; } - if (pending) { - complete = NULL; - } else { + if (!pending) { link.tx.pending_ack = xact_id; - complete = &cb; } net_buf_simple_add_be32(&adv->b, link.id); net_buf_simple_add_u8(&adv->b, xact_id); net_buf_simple_add_u8(&adv->b, GPC_ACK); - bt_mesh_adv_send(adv, complete, NULL); - bt_mesh_adv_unref(adv); + err = send_unacked(adv, pending ? NULL : ack_complete, NULL); + if (err) { + atomic_clear_bit(link.flags, ADV_ACK_PENDING); + } } static void gen_prov_cont(struct prov_rx *rx, struct net_buf_simple *buf) @@ -591,29 +711,6 @@ static void gen_prov_recv(struct prov_rx *rx, struct net_buf_simple *buf) * TX ******************************************************************************/ -static void send_reliable(void) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(link.tx.adv); i++) { - struct bt_mesh_adv *adv = link.tx.adv[i]; - - if (!adv) { - break; - } - - if (adv->ctx.busy) { - continue; - } - - LOG_DBG("%u bytes: %s", adv->b.len, bt_hex(adv->b.data, adv->b.len)); - - bt_mesh_adv_send(adv, NULL, NULL); - } - - k_work_reschedule(&link.tx.retransmit, RETRANSMIT_TIMEOUT); -} - static void prov_retransmit(struct k_work *work) { LOG_DBG(""); @@ -669,8 +766,22 @@ static int bearer_ctl_send(struct bt_mesh_adv *adv) return 0; } +static void buf_sent(int err, void *user_data) +{ + enum prov_bearer_link_status reason = (enum prov_bearer_link_status)(int)user_data; + + atomic_clear_bit(link.flags, ADV_LINK_ACK_SENDING); + + if (atomic_test_and_clear_bit(link.flags, ADV_LINK_CLOSING)) { + close_link(reason); + return; + } +} + static int bearer_ctl_send_unacked(struct bt_mesh_adv *adv, void *user_data) { + int err; + if (!adv) { return -ENOMEM; } @@ -678,10 +789,12 @@ static int bearer_ctl_send_unacked(struct bt_mesh_adv *adv, void *user_data) prov_clear_tx(); k_work_reschedule(&link.prot_timer, bt_mesh_prov_protocol_timeout_get()); - bt_mesh_adv_send(adv, &buf_sent_cb, user_data); - bt_mesh_adv_unref(adv); + err = send_unacked(adv, &buf_sent, user_data); + if (err) { + bt_mesh_adv_unref(adv); + } - return 0; + return err; } static int prov_send_adv(struct net_buf_simple *msg, @@ -772,8 +885,13 @@ static void link_open(struct prov_rx *rx, struct net_buf_simple *buf) return; } - LOG_DBG("Resending link ack"); + if (atomic_test_bit(link.flags, ADV_LINK_ACK_SENDING)) { + LOG_DBG("Still sending Link Ack"); + return; + } + /* Ignore errors, message will be attempted again if we keep receiving link open: */ + atomic_set_bit(link.flags, ADV_LINK_ACK_SENDING); (void)bearer_ctl_send_unacked( ctl_adv_create(LINK_ACK, NULL, 0, RETRANSMITS_ACK), (void *)PROV_BEARER_LINK_STATUS_SUCCESS); @@ -789,6 +907,7 @@ static void link_open(struct prov_rx *rx, struct net_buf_simple *buf) atomic_set_bit(link.flags, ADV_LINK_ACTIVE); net_buf_simple_reset(link.rx.buf); + atomic_set_bit(link.flags, ADV_LINK_ACK_SENDING); err = bearer_ctl_send_unacked( ctl_adv_create(LINK_ACK, NULL, 0, RETRANSMITS_ACK), (void *)PROV_BEARER_LINK_STATUS_SUCCESS); @@ -923,6 +1042,8 @@ static int prov_link_accept(const struct prov_bearer_cb *cb, void *cb_data) static void prov_link_close(enum prov_bearer_link_status status) { + int err; + if (atomic_test_and_set_bit(link.flags, ADV_LINK_CLOSING)) { return; } @@ -934,9 +1055,12 @@ static void prov_link_close(enum prov_bearer_link_status status) */ link.tx.timeout = CLOSING_TIMEOUT; /* Ignore errors, the link will time out eventually if this doesn't get sent */ - bearer_ctl_send_unacked( + err = bearer_ctl_send_unacked( ctl_adv_create(LINK_CLOSE, &status, 1, RETRANSMITS_LINK_CLOSE), (void *)status); + if (err) { + close_link(status); + } } void bt_mesh_pb_adv_init(void)