Bluetooth: Mesh: access tx msg randomizer

Commit adds implementation of the specification
recommendations regarding randomization of
responses on the access layer.
3.7.3.1 Transmitting an Access messages

Signed-off-by: Aleksandr Khromykh <aleksandr.khromykh@nordicsemi.no>
This commit is contained in:
Aleksandr Khromykh 2023-11-20 16:43:25 +01:00 committed by Carles Cufí
commit d175ac0572
12 changed files with 460 additions and 0 deletions

View file

@ -222,6 +222,39 @@ They are used to represent the new content of the mirrored pages when the Compos
change after a firmware update. See :ref:`bluetooth_mesh_dfu_srv_comp_data_and_models_metadata`
for details.
Delayable messages
==================
The delayable message functionality is enabled with Kconfig option
:kconfig:option:`CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG`.
This is an optional functionality that implements specification recommendations for
messages that are transmitted by a model in a response to a received message, also called
response messages.
Response messages should be sent with the following random delays:
* Between 20 and 50 milliseconds if the received message was sent
to a unicast address
* Between 20 and 500 milliseconds if the received message was sent
to a group or virtual address
The delayable message functionality is triggered if the :c:member:`bt_mesh_msg_ctx.rnd_delay`
flag is set.
The delayable message functionality stores messages in the local memory while they are
waiting for the random delay expiration.
If the transport layer doesn't have sufficient memory to send a message at the moment
the random delay expires, the message is postponed for another 10 milliseconds.
If the transport layer cannot send a message for any other reason, the delayable message
functionality raises the :c:member:`bt_mesh_send_cb.start` callback with a transport layer
error code.
If the delayable message functionality cannot find enough free memory to store an incoming
message, it will send messages with delay close to expiration to free memory.
When the mesh stack is suspended or reset, messages not yet sent are removed and
the :c:member:`bt_mesh_send_cb.start` callback is raised with an error code.
API reference
*************

View file

@ -52,6 +52,11 @@ Bluetooth
* Mesh
* Added the delayable messages functionality to apply random delays for
the transmitted responses on the Access layer.
The functionality is enabled by the :kconfig:option:`CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG`
Kconfig option.
* Controller
Boards & SoC Support

View file

@ -98,6 +98,9 @@ struct bt_mesh_msg_ctx {
/** Force sending reliably by using segment acknowledgment */
bool send_rel;
/** Send message with a random delay according to the Access layer transmitting rules. */
bool rnd_delay;
/** TTL, or BT_MESH_TTL_DEFAULT for default TTL. */
uint8_t send_ttl;
};

View file

@ -32,3 +32,4 @@ CONFIG_BT_MESH_SUBNET_COUNT=1
CONFIG_BT_MESH_APP_KEY_COUNT=1
CONFIG_BT_MESH_MODEL_GROUP_COUNT=1
CONFIG_BT_MESH_LABEL_COUNT=0
CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG=n

View file

@ -22,3 +22,4 @@ CONFIG_BT_MESH_BEACON_ENABLED=n
CONFIG_BT_MESH_LABEL_COUNT=1
CONFIG_BT_MESH_SETTINGS_WORKQ=n
CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG=n

View file

@ -122,6 +122,8 @@ zephyr_library_sources_ifdef(CONFIG_BT_MESH_SOLICITATION solicitation.c)
zephyr_library_sources_ifdef(CONFIG_BT_MESH_STATISTIC statistic.c)
zephyr_library_sources_ifdef(CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG delayable_msg.c)
if (CONFIG_BT_MESH_USES_TINYCRYPT)
zephyr_library_sources(crypto_tc.c)
else()

View file

@ -628,6 +628,41 @@ config BT_MESH_LABEL_NO_RECOVER
unprovisioned before upgrading to the version with this option). The option is marked as
deprecated to remove the recovery code eventually.
menuconfig BT_MESH_ACCESS_DELAYABLE_MSG
bool "Access layer tx delayable message"
help
Enable following of the message transmitting recommendations, the Access layer
specification. The recommendations are optional.
However, they are strictly recommended if the device participates in the network with
intensive communication where the device receives a lot of requests that require responses.
if BT_MESH_ACCESS_DELAYABLE_MSG
config BT_MESH_ACCESS_DELAYABLE_MSG_COUNT
int "Number of simultaneously delayed messages"
default 4
help
The maximum number of messages the Access layer can manage to delay
at the same time. The number of messages can be handled only if the Access layer
has a sufficient amount of memory to store the model payload for them.
config BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_SIZE
int "Maximum delayable message storage chunk"
default 20
help
Size of memory that Access layer uses to split model message to. It allocates
a sufficient number of these chunks from the pool to store the full model payload.
config BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_COUNT
int "Maximum number of available chunks"
default 20
help
The maximum number of available chunks the Access layer allocates to store model payload.
It is recommended to keep chunk size equal to the reasonable small value to prevent
unnecessary memory wasting.
endif # BT_MESH_ACCESS_DELAYABLE_MSG
endmenu # Access layer
menu "Models"

View file

@ -27,6 +27,7 @@
#include "op_agg.h"
#include "settings.h"
#include "va.h"
#include "delayable_msg.h"
#define LOG_LEVEL CONFIG_BT_MESH_ACCESS_LOG_LEVEL
#include <zephyr/logging/log.h>
@ -1518,6 +1519,13 @@ int bt_mesh_model_send(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx
return -EINVAL;
}
#if defined CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG
if (ctx->rnd_delay) {
return bt_mesh_delayable_msg_manage(ctx, msg, bt_mesh_model_elem(model)->rt->addr,
cb, cb_data);
}
#endif
return bt_mesh_access_send(ctx, msg, bt_mesh_model_elem(model)->rt->addr, cb, cb_data);
}
@ -2613,3 +2621,24 @@ uint8_t bt_mesh_comp_parse_page(struct net_buf_simple *buf)
return page;
}
void bt_mesh_access_init(void)
{
#if defined CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG
bt_mesh_delayable_msg_init();
#endif
}
void bt_mesh_access_suspend(void)
{
#if defined CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG
bt_mesh_delayable_msg_stop();
#endif
}
void bt_mesh_access_reset(void)
{
#if defined CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG
bt_mesh_delayable_msg_stop();
#endif
}

View file

@ -96,8 +96,25 @@ void bt_mesh_msg_cb_set(void (*cb)(uint32_t opcode, struct bt_mesh_msg_ctx *ctx,
*
* @param ctx The Bluetooth Mesh message context.
* @param buf The message payload.
* @param src_addr The source address of model
* @param cb Callback function.
* @param cb_data Callback data.
*
* @return 0 on success or negative error code on failure.
*/
int bt_mesh_access_send(struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf, uint16_t src_addr,
const struct bt_mesh_send_cb *cb, void *cb_data);
/** @brief Initialize the Access layer.
*
* Initialize the delayable message mechanism if it has been enabled.
*/
void bt_mesh_access_init(void);
/** @brief Suspend the Access layer.
*/
void bt_mesh_access_suspend(void);
/** @brief Reset the Access layer.
*/
void bt_mesh_access_reset(void);

View file

@ -0,0 +1,314 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <errno.h>
#include <stdlib.h>
#include <zephyr/sys/slist.h>
#include <zephyr/net/buf.h>
#include <zephyr/bluetooth/mesh.h>
#include "msg.h"
#include "access.h"
#include "net.h"
#define LOG_LEVEL CONFIG_BT_MESH_ACCESS_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mesh_delayable_msg);
static void delayable_msg_handler(struct k_work *w);
static bool push_msg_from_delayable_msgs(void);
static struct delayable_msg_chunk {
sys_snode_t node;
uint8_t data[CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_SIZE];
} delayable_msg_chunks[CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_COUNT];
static struct delayable_msg_ctx {
sys_snode_t node;
sys_slist_t chunks;
struct bt_mesh_msg_ctx ctx;
uint16_t src_addr;
const struct bt_mesh_send_cb *cb;
void *cb_data;
uint32_t fired_time;
uint16_t len;
} delayable_msgs_ctx[CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_COUNT];
static struct {
sys_slist_t busy_ctx;
sys_slist_t free_ctx;
sys_slist_t free_chunks;
struct k_work_delayable random_delay;
} access_delayable_msg = {.random_delay = Z_WORK_DELAYABLE_INITIALIZER(delayable_msg_handler)};
static void put_ctx_to_busy_list(struct delayable_msg_ctx *ctx)
{
struct delayable_msg_ctx *curr_ctx;
sys_slist_t *list = &access_delayable_msg.busy_ctx;
sys_snode_t *head = sys_slist_peek_head(list);
sys_snode_t *curr = head;
sys_snode_t *prev = curr;
if (!head) {
sys_slist_append(list, &ctx->node);
return;
}
do {
curr_ctx = CONTAINER_OF(curr, struct delayable_msg_ctx, node);
if (ctx->fired_time < curr_ctx->fired_time) {
if (curr == head) {
sys_slist_prepend(list, &ctx->node);
} else {
sys_slist_insert(list, prev, &ctx->node);
}
return;
}
prev = curr;
} while ((curr = sys_slist_peek_next(curr)));
sys_slist_append(list, &ctx->node);
}
static struct delayable_msg_ctx *peek_pending_msg(void)
{
struct delayable_msg_ctx *pending_msg = NULL;
sys_snode_t *node = sys_slist_peek_head(&access_delayable_msg.busy_ctx);
if (node) {
pending_msg = CONTAINER_OF(node, struct delayable_msg_ctx, node);
}
return pending_msg;
}
static void reschedule_delayable_msg(struct delayable_msg_ctx *msg)
{
uint32_t curr_time;
k_timeout_t delay = K_NO_WAIT;
struct delayable_msg_ctx *pending_msg;
if (msg) {
put_ctx_to_busy_list(msg);
}
pending_msg = peek_pending_msg();
if (!pending_msg) {
return;
}
curr_time = k_uptime_get_32();
if (curr_time < pending_msg->fired_time) {
delay = K_MSEC(pending_msg->fired_time - curr_time);
}
k_work_reschedule(&access_delayable_msg.random_delay, delay);
}
static int allocate_delayable_msg_chunks(struct delayable_msg_ctx *msg, int number)
{
sys_snode_t *node;
for (int i = 0; i < number; i++) {
node = sys_slist_get(&access_delayable_msg.free_chunks);
if (!node) {
LOG_WRN("Unable allocate %u chunks, allocated %u", number, i);
return i;
}
sys_slist_append(&msg->chunks, node);
}
return number;
}
static void release_delayable_msg_chunks(struct delayable_msg_ctx *msg)
{
sys_snode_t *node;
while ((node = sys_slist_get(&msg->chunks))) {
sys_slist_append(&access_delayable_msg.free_chunks, node);
}
}
static struct delayable_msg_ctx *allocate_delayable_msg_ctx(void)
{
struct delayable_msg_ctx *msg;
sys_snode_t *node;
if (sys_slist_is_empty(&access_delayable_msg.free_ctx)) {
LOG_WRN("Purge pending delayable message.");
if (!push_msg_from_delayable_msgs()) {
return NULL;
}
}
node = sys_slist_get(&access_delayable_msg.free_ctx);
msg = CONTAINER_OF(node, struct delayable_msg_ctx, node);
sys_slist_init(&msg->chunks);
return msg;
}
static void release_delayable_msg_ctx(struct delayable_msg_ctx *ctx)
{
if (sys_slist_find_and_remove(&access_delayable_msg.busy_ctx, &ctx->node)) {
sys_slist_append(&access_delayable_msg.free_ctx, &ctx->node);
}
}
static bool push_msg_from_delayable_msgs(void)
{
sys_snode_t *node;
struct delayable_msg_chunk *chunk;
struct delayable_msg_ctx *msg = peek_pending_msg();
uint16_t len = msg->len;
int err;
if (!msg) {
return false;
}
NET_BUF_SIMPLE_DEFINE(buf, BT_MESH_TX_SDU_MAX);
SYS_SLIST_FOR_EACH_NODE(&msg->chunks, node) {
uint16_t tmp = MIN(CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_SIZE, len);
chunk = CONTAINER_OF(node, struct delayable_msg_chunk, node);
memcpy(net_buf_simple_add(&buf, tmp), chunk->data, tmp);
len -= tmp;
}
msg->ctx.rnd_delay = false;
err = bt_mesh_access_send(&msg->ctx, &buf, msg->src_addr, msg->cb, msg->cb_data);
msg->ctx.rnd_delay = true;
if (err == -EBUSY || err == -ENOBUFS) {
return false;
}
release_delayable_msg_chunks(msg);
release_delayable_msg_ctx(msg);
if (err && msg->cb && msg->cb->start) {
msg->cb->start(0, err, msg->cb_data);
}
return true;
}
static void delayable_msg_handler(struct k_work *w)
{
if (!push_msg_from_delayable_msgs()) {
sys_snode_t *node = sys_slist_get(&access_delayable_msg.busy_ctx);
struct delayable_msg_ctx *pending_msg =
CONTAINER_OF(node, struct delayable_msg_ctx, node);
pending_msg->fired_time += 10;
reschedule_delayable_msg(pending_msg);
} else {
reschedule_delayable_msg(NULL);
}
}
int bt_mesh_delayable_msg_manage(struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf,
uint16_t src_addr, const struct bt_mesh_send_cb *cb, void *cb_data)
{
sys_snode_t *node;
struct delayable_msg_ctx *msg;
uint16_t random_delay;
int total_number = DIV_ROUND_UP(buf->size, CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_SIZE);
int allocated_number = 0;
uint16_t len = buf->len;
if (atomic_test_bit(bt_mesh.flags, BT_MESH_SUSPENDED)) {
LOG_WRN("Refusing to allocate message context while suspended");
return -ENODEV;
}
if (total_number > CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_COUNT) {
return -EINVAL;
}
msg = allocate_delayable_msg_ctx();
if (!msg) {
LOG_WRN("No available free delayable message context.");
return -ENOMEM;
}
do {
allocated_number +=
allocate_delayable_msg_chunks(msg, total_number - allocated_number);
if (total_number > allocated_number) {
LOG_DBG("Unable allocate %u chunks, allocated %u", total_number,
allocated_number);
if (!push_msg_from_delayable_msgs()) {
LOG_WRN("No available chunk memory.");
release_delayable_msg_chunks(msg);
release_delayable_msg_ctx(msg);
return -ENOMEM;
}
}
} while (total_number > allocated_number);
SYS_SLIST_FOR_EACH_NODE(&msg->chunks, node) {
uint16_t tmp = MIN(CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_SIZE, buf->len);
struct delayable_msg_chunk *chunk =
CONTAINER_OF(node, struct delayable_msg_chunk, node);
memcpy(chunk->data, net_buf_simple_pull_mem(buf, tmp), tmp);
}
bt_rand(&random_delay, sizeof(uint16_t));
random_delay = 20 + random_delay % (BT_MESH_ADDR_IS_UNICAST(ctx->recv_dst) ? 30 : 480);
msg->fired_time = k_uptime_get_32() + random_delay;
msg->ctx = *ctx;
msg->src_addr = src_addr;
msg->cb = cb;
msg->cb_data = cb_data;
msg->len = len;
reschedule_delayable_msg(msg);
return 0;
}
void bt_mesh_delayable_msg_init(void)
{
sys_slist_init(&access_delayable_msg.busy_ctx);
sys_slist_init(&access_delayable_msg.free_ctx);
sys_slist_init(&access_delayable_msg.free_chunks);
for (int i = 0; i < CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_COUNT; i++) {
sys_slist_append(&access_delayable_msg.free_ctx, &delayable_msgs_ctx[i].node);
}
for (int i = 0; i < CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_COUNT; i++) {
sys_slist_append(&access_delayable_msg.free_chunks, &delayable_msg_chunks[i].node);
}
}
void bt_mesh_delayable_msg_stop(void)
{
sys_snode_t *node;
struct delayable_msg_ctx *ctx;
k_work_cancel_delayable(&access_delayable_msg.random_delay);
while ((node = sys_slist_peek_head(&access_delayable_msg.busy_ctx))) {
ctx = CONTAINER_OF(node, struct delayable_msg_ctx, node);
release_delayable_msg_chunks(ctx);
release_delayable_msg_ctx(ctx);
if (ctx->cb && ctx->cb->start) {
ctx->cb->start(0, -ENODEV, ctx->cb_data);
}
}
}

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DELAYABLE_MSG_H__
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DELAYABLE_MSG_H__
int bt_mesh_delayable_msg_manage(struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf,
uint16_t src_addr, const struct bt_mesh_send_cb *cb,
void *cb_data);
void bt_mesh_delayable_msg_init(void);
void bt_mesh_delayable_msg_stop(void);
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DELAYABLE_MSG_H__ */

View file

@ -359,6 +359,7 @@ void bt_mesh_reset(void)
*/
(void)k_work_cancel_delayable(&bt_mesh.ivu_timer);
bt_mesh_access_reset();
bt_mesh_model_reset();
bt_mesh_cfg_default_set();
bt_mesh_trans_reset();
@ -459,6 +460,8 @@ int bt_mesh_suspend(void)
bt_mesh_model_foreach(model_suspend, NULL);
bt_mesh_access_suspend();
err = bt_mesh_adv_disable();
if (err) {
atomic_clear_bit(bt_mesh.flags, BT_MESH_SUSPENDED);
@ -558,6 +561,7 @@ int bt_mesh_init(const struct bt_mesh_prov *prov,
bt_mesh_cfg_default_set();
bt_mesh_net_init();
bt_mesh_trans_init();
bt_mesh_access_init();
bt_mesh_hb_init();
bt_mesh_beacon_init();
bt_mesh_adv_init();