blob: 0ad4e7164adc5dd9ceb04a44e146eb35f4ba566a [file] [log] [blame]
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <errno.h>
#include <stdlib.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/net/buf.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/mesh.h>
#include "common/bt_str.h"
#include "host/testing.h"
#include "mesh.h"
#include "net.h"
#include "lpn.h"
#include "transport.h"
#include "access.h"
#include "foundation.h"
#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>
LOG_MODULE_REGISTER(bt_mesh_access);
/* 20 - 50ms */
#define RANDOM_DELAY_SHORT 30
/* 20 - 500ms */
#define RANDOM_DELAY_LONG 480
/* Model publication information for persistent storage. */
struct mod_pub_val {
struct {
uint16_t addr;
uint16_t key;
uint8_t ttl;
uint8_t retransmit;
uint8_t period;
uint8_t period_div:4,
cred:1;
} base;
uint16_t uuidx;
};
struct comp_foreach_model_arg {
struct net_buf_simple *buf;
size_t *offset;
};
static const struct bt_mesh_comp *dev_comp;
static const struct bt_mesh_comp2 *dev_comp2;
static uint16_t dev_primary_addr;
static void (*msg_cb)(uint32_t opcode, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf);
/* Structure containing information about model extension */
struct mod_relation {
/** Element that composition data base model belongs to. */
uint8_t elem_base;
/** Index of composition data base model in its element. */
uint8_t idx_base;
/** Element that composition data extension model belongs to. */
uint8_t elem_ext;
/** Index of composition data extension model in its element. */
uint8_t idx_ext;
/** Type of relation; value in range 0x00-0xFE marks correspondence
* and equals to Correspondence ID; value 0xFF marks extension
*/
uint8_t type;
};
#ifdef CONFIG_BT_MESH_MODEL_EXTENSION_LIST_SIZE
#define MOD_REL_LIST_SIZE CONFIG_BT_MESH_MODEL_EXTENSION_LIST_SIZE
#else
#define MOD_REL_LIST_SIZE 0
#endif
/* List of all existing extension relations between models */
static struct mod_relation mod_rel_list[MOD_REL_LIST_SIZE];
#define MOD_REL_LIST_FOR_EACH(idx) \
for ((idx) = 0; \
(idx) < ARRAY_SIZE(mod_rel_list) && \
!(mod_rel_list[(idx)].elem_base == 0 && \
mod_rel_list[(idx)].idx_base == 0 && \
mod_rel_list[(idx)].elem_ext == 0 && \
mod_rel_list[(idx)].idx_ext == 0); \
(idx)++)
#define IS_MOD_BASE(mod, idx, offset) \
(mod_rel_list[(idx)].elem_base == mod->rt->elem_idx && \
mod_rel_list[(idx)].idx_base == mod->rt->mod_idx + (offset))
#define IS_MOD_EXTENSION(mod, idx, offset) \
(mod_rel_list[(idx)].elem_ext == mod->rt->elem_idx && \
mod_rel_list[(idx)].idx_ext == mod->rt->mod_idx + (offset))
#define RELATION_TYPE_EXT 0xFF
static const struct {
uint8_t *path;
uint8_t page;
} comp_data_pages[] = {
{ "bt/mesh/cmp/0", 0, },
#if IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1)
{ "bt/mesh/cmp/1", 1, },
#endif
#if IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_2)
{ "bt/mesh/cmp/2", 2, },
#endif
};
void bt_mesh_model_foreach(void (*func)(const struct bt_mesh_model *mod,
const struct bt_mesh_elem *elem,
bool vnd, bool primary,
void *user_data),
void *user_data)
{
int i, j;
for (i = 0; i < dev_comp->elem_count; i++) {
const struct bt_mesh_elem *elem = &dev_comp->elem[i];
for (j = 0; j < elem->model_count; j++) {
const struct bt_mesh_model *model = &elem->models[j];
func(model, elem, false, i == 0, user_data);
}
for (j = 0; j < elem->vnd_model_count; j++) {
const struct bt_mesh_model *model = &elem->vnd_models[j];
func(model, elem, true, i == 0, user_data);
}
}
}
static size_t bt_mesh_comp_elem_size(const struct bt_mesh_elem *elem)
{
return (4 + (elem->model_count * 2U) + (elem->vnd_model_count * 4U));
}
static uint8_t *data_buf_add_u8_offset(struct net_buf_simple *buf,
uint8_t val, size_t *offset)
{
if (*offset >= 1) {
*offset -= 1;
return NULL;
}
return net_buf_simple_add_u8(buf, val);
}
static void data_buf_add_le16_offset(struct net_buf_simple *buf,
uint16_t val, size_t *offset)
{
if (*offset >= 2) {
*offset -= 2;
return;
} else if (*offset == 1) {
*offset -= 1;
net_buf_simple_add_u8(buf, (val >> 8));
} else {
net_buf_simple_add_le16(buf, val);
}
}
static void data_buf_add_mem_offset(struct net_buf_simple *buf, const uint8_t *data, size_t len,
size_t *offset)
{
if (*offset >= len) {
*offset -= len;
return;
}
net_buf_simple_add_mem(buf, data + *offset, len - *offset);
*offset = 0;
}
static void comp_add_model(const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem,
bool vnd, void *user_data)
{
struct comp_foreach_model_arg *arg = user_data;
if (vnd) {
data_buf_add_le16_offset(arg->buf, mod->vnd.company, arg->offset);
data_buf_add_le16_offset(arg->buf, mod->vnd.id, arg->offset);
} else {
data_buf_add_le16_offset(arg->buf, mod->id, arg->offset);
}
}
#if defined(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV)
static size_t metadata_model_size(const struct bt_mesh_model *mod,
const struct bt_mesh_elem *elem, bool vnd)
{
const struct bt_mesh_models_metadata_entry *entry;
size_t size = 0;
if (!mod->metadata) {
return size;
}
if (vnd) {
size += sizeof(mod->vnd.company);
size += sizeof(mod->vnd.id);
} else {
size += sizeof(mod->id);
}
size += sizeof(uint8_t);
for (entry = *mod->metadata; entry && entry->len; ++entry) {
size += sizeof(entry->len) + sizeof(entry->id) + entry->len;
}
return size;
}
size_t bt_mesh_metadata_page_0_size(void)
{
const struct bt_mesh_comp *comp;
size_t size = 0;
int i, j;
comp = bt_mesh_comp_get();
for (i = 0; i < dev_comp->elem_count; i++) {
const struct bt_mesh_elem *elem = &dev_comp->elem[i];
size += sizeof(elem->model_count) +
sizeof(elem->vnd_model_count);
for (j = 0; j < elem->model_count; j++) {
const struct bt_mesh_model *model = &elem->models[j];
size += metadata_model_size(model, elem, false);
}
for (j = 0; j < elem->vnd_model_count; j++) {
const struct bt_mesh_model *model = &elem->vnd_models[j];
size += metadata_model_size(model, elem, true);
}
}
return size;
}
static int metadata_add_model(const struct bt_mesh_model *mod,
const struct bt_mesh_elem *elem, bool vnd,
void *user_data)
{
const struct bt_mesh_models_metadata_entry *entry;
struct comp_foreach_model_arg *arg = user_data;
struct net_buf_simple *buf = arg->buf;
size_t *offset = arg->offset;
size_t model_size;
uint8_t count = 0;
uint8_t *count_ptr;
model_size = metadata_model_size(mod, elem, vnd);
if (*offset >= model_size) {
*offset -= model_size;
return 0;
}
if (net_buf_simple_tailroom(buf) < (model_size + BT_MESH_MIC_SHORT)) {
LOG_DBG("Model metadata didn't fit in the buffer");
return -E2BIG;
}
comp_add_model(mod, elem, vnd, user_data);
count_ptr = data_buf_add_u8_offset(buf, 0, offset);
if (mod->metadata) {
for (entry = *mod->metadata; entry && entry->data != NULL; ++entry) {
data_buf_add_le16_offset(buf, entry->len, offset);
data_buf_add_le16_offset(buf, entry->id, offset);
data_buf_add_mem_offset(buf, entry->data, entry->len, offset);
count++;
}
}
if (count_ptr) {
*count_ptr = count;
}
return 0;
}
int bt_mesh_metadata_get_page_0(struct net_buf_simple *buf, size_t offset)
{
const struct bt_mesh_comp *comp;
struct comp_foreach_model_arg arg = {
.buf = buf,
.offset = &offset,
};
uint8_t *mod_count_ptr;
uint8_t *vnd_count_ptr;
int i, j, err;
comp = bt_mesh_comp_get();
for (i = 0; i < comp->elem_count; i++) {
const struct bt_mesh_elem *elem = &dev_comp->elem[i];
/* Check that the buffer has available tailroom for metadata item counts */
if (net_buf_simple_tailroom(buf) < (((offset == 0) ? 2 : (offset == 1) ? 1 : 0)
+ BT_MESH_MIC_SHORT)) {
LOG_DBG("Model metadata didn't fit in the buffer");
return -E2BIG;
}
mod_count_ptr = data_buf_add_u8_offset(buf, 0, &offset);
vnd_count_ptr = data_buf_add_u8_offset(buf, 0, &offset);
for (j = 0; j < elem->model_count; j++) {
const struct bt_mesh_model *model = &elem->models[j];
if (!model->metadata) {
continue;
}
err = metadata_add_model(model, elem, false, &arg);
if (err) {
return err;
}
if (mod_count_ptr) {
(*mod_count_ptr) += 1;
}
}
for (j = 0; j < elem->vnd_model_count; j++) {
const struct bt_mesh_model *model = &elem->vnd_models[j];
if (!model->metadata) {
continue;
}
err = metadata_add_model(model, elem, true, &arg);
if (err) {
return err;
}
if (vnd_count_ptr) {
(*vnd_count_ptr) += 1;
}
}
}
return 0;
}
#endif
static int comp_add_elem(struct net_buf_simple *buf, const struct bt_mesh_elem *elem,
size_t *offset)
{
struct comp_foreach_model_arg arg = {
.buf = buf,
.offset = offset,
};
const size_t elem_size = bt_mesh_comp_elem_size(elem);
int i;
if (*offset >= elem_size) {
*offset -= elem_size;
return 0;
}
if (net_buf_simple_tailroom(buf) < ((elem_size - *offset) + BT_MESH_MIC_SHORT)) {
if (IS_ENABLED(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV)) {
/* MshPRTv1.1: 4.4.1.2.2:
* If the complete list of models does not fit in the Data field,
* the element shall not be reported.
*/
LOG_DBG("Element 0x%04x didn't fit in the Data field",
elem->rt->addr);
return 0;
}
LOG_ERR("Too large device composition");
return -E2BIG;
}
data_buf_add_le16_offset(buf, elem->loc, offset);
data_buf_add_u8_offset(buf, elem->model_count, offset);
data_buf_add_u8_offset(buf, elem->vnd_model_count, offset);
for (i = 0; i < elem->model_count; i++) {
const struct bt_mesh_model *model = &elem->models[i];
comp_add_model(model, elem, false, &arg);
}
for (i = 0; i < elem->vnd_model_count; i++) {
const struct bt_mesh_model *model = &elem->vnd_models[i];
comp_add_model(model, elem, true, &arg);
}
return 0;
}
int bt_mesh_comp_data_get_page_0(struct net_buf_simple *buf, size_t offset)
{
uint16_t feat = 0U;
const struct bt_mesh_comp *comp;
int i;
comp = bt_mesh_comp_get();
if (IS_ENABLED(CONFIG_BT_MESH_RELAY)) {
feat |= BT_MESH_FEAT_RELAY;
}
if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) {
feat |= BT_MESH_FEAT_PROXY;
}
if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) {
feat |= BT_MESH_FEAT_FRIEND;
}
if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) {
feat |= BT_MESH_FEAT_LOW_POWER;
}
data_buf_add_le16_offset(buf, comp->cid, &offset);
data_buf_add_le16_offset(buf, comp->pid, &offset);
data_buf_add_le16_offset(buf, comp->vid, &offset);
data_buf_add_le16_offset(buf, CONFIG_BT_MESH_CRPL, &offset);
data_buf_add_le16_offset(buf, feat, &offset);
for (i = 0; i < comp->elem_count; i++) {
int err;
err = comp_add_elem(buf, &comp->elem[i], &offset);
if (err) {
return err;
}
}
return 0;
}
static uint8_t count_mod_ext(const struct bt_mesh_model *mod,
uint8_t *max_offset, uint8_t sig_offset)
{
int i;
uint8_t extensions = 0;
int8_t offset, offset_record = 0;
MOD_REL_LIST_FOR_EACH(i) {
if (IS_MOD_EXTENSION(mod, i, sig_offset) &&
mod_rel_list[i].type == RELATION_TYPE_EXT) {
extensions++;
offset = mod_rel_list[i].elem_ext -
mod_rel_list[i].elem_base;
if (abs(offset) > abs(offset_record)) {
offset_record = offset;
}
}
}
if (max_offset) {
memcpy(max_offset, &offset_record, sizeof(uint8_t));
}
return extensions;
}
static bool is_cor_present(const struct bt_mesh_model *mod, uint8_t *cor_id, uint8_t sig_offset)
{
int i;
MOD_REL_LIST_FOR_EACH(i)
{
if ((IS_MOD_BASE(mod, i, sig_offset) ||
IS_MOD_EXTENSION(mod, i, sig_offset)) &&
mod_rel_list[i].type < RELATION_TYPE_EXT) {
if (cor_id) {
memcpy(cor_id, &mod_rel_list[i].type, sizeof(uint8_t));
}
return true;
}
}
return false;
}
static void prep_model_item_header(const struct bt_mesh_model *mod, uint8_t *cor_id,
uint8_t *mod_cnt, struct net_buf_simple *buf,
size_t *offset, uint8_t sig_offset)
{
uint8_t ext_mod_cnt;
bool cor_present;
uint8_t mod_elem_info = 0;
int8_t max_offset;
ext_mod_cnt = count_mod_ext(mod, &max_offset, sig_offset);
cor_present = is_cor_present(mod, cor_id, sig_offset);
mod_elem_info = ext_mod_cnt << 2;
if (ext_mod_cnt > 31 ||
max_offset > 3 ||
max_offset < -4) {
mod_elem_info |= BIT(1);
}
if (cor_present) {
mod_elem_info |= BIT(0);
}
data_buf_add_u8_offset(buf, mod_elem_info, offset);
if (cor_present) {
data_buf_add_u8_offset(buf, *cor_id, offset);
}
memset(mod_cnt, ext_mod_cnt, sizeof(uint8_t));
}
static void add_items_to_page(struct net_buf_simple *buf, const struct bt_mesh_model *mod,
uint8_t ext_mod_cnt, size_t *offset, uint8_t sig_offset)
{
int i, elem_offset;
uint8_t mod_idx;
MOD_REL_LIST_FOR_EACH(i) {
if (IS_MOD_EXTENSION(mod, i, sig_offset) &&
mod_rel_list[i].type == RELATION_TYPE_EXT) {
elem_offset = mod->rt->elem_idx - mod_rel_list[i].elem_base;
mod_idx = mod_rel_list[i].idx_base;
if (ext_mod_cnt < 32 &&
elem_offset < 4 &&
elem_offset > -5) {
/* short format */
if (elem_offset < 0) {
elem_offset += 8;
}
elem_offset |= mod_idx << 3;
data_buf_add_u8_offset(buf, elem_offset, offset);
} else {
/* long format */
if (elem_offset < 0) {
elem_offset += 256;
}
data_buf_add_u8_offset(buf, elem_offset, offset);
data_buf_add_u8_offset(buf, mod_idx, offset);
}
}
}
}
static size_t mod_items_size(const struct bt_mesh_model *mod, uint8_t sig_offset)
{
int i, offset;
size_t temp_size = 0;
int ext_mod_cnt = count_mod_ext(mod, NULL, sig_offset);
if (!ext_mod_cnt) {
return 0;
}
MOD_REL_LIST_FOR_EACH(i) {
if (IS_MOD_EXTENSION(mod, i, sig_offset)) {
offset = mod->rt->elem_idx - mod_rel_list[i].elem_base;
temp_size += (ext_mod_cnt < 32 && offset < 4 && offset > -5) ? 1 : 2;
}
}
return temp_size;
}
static size_t page1_elem_size(const struct bt_mesh_elem *elem)
{
size_t temp_size = 2;
for (int i = 0; i < elem->model_count; i++) {
temp_size += is_cor_present(&elem->models[i], NULL, 0) ? 2 : 1;
temp_size += mod_items_size(&elem->models[i], 0);
}
for (int i = 0; i < elem->vnd_model_count; i++) {
temp_size += is_cor_present(&elem->vnd_models[i], NULL, elem->model_count) ? 2 : 1;
temp_size += mod_items_size(&elem->vnd_models[i], elem->model_count);
}
return temp_size;
}
static int bt_mesh_comp_data_get_page_1(struct net_buf_simple *buf, size_t offset)
{
const struct bt_mesh_comp *comp;
uint8_t cor_id = 0;
uint8_t ext_mod_cnt = 0;
int i, j;
comp = bt_mesh_comp_get();
for (i = 0; i < comp->elem_count; i++) {
size_t elem_size = page1_elem_size(&comp->elem[i]);
if (offset >= elem_size) {
offset -= elem_size;
continue;
}
if (net_buf_simple_tailroom(buf) < ((elem_size - offset) + BT_MESH_MIC_SHORT)) {
if (IS_ENABLED(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV)) {
/* MshPRTv1.1: 4.4.1.2.2:
* If the complete list of models does not fit in the Data field,
* the element shall not be reported.
*/
LOG_DBG("Element 0x%04x didn't fit in the Data field",
comp->elem[i].rt->addr);
return 0;
}
LOG_ERR("Too large device composition");
return -E2BIG;
}
data_buf_add_u8_offset(buf, comp->elem[i].model_count, &offset);
data_buf_add_u8_offset(buf, comp->elem[i].vnd_model_count, &offset);
for (j = 0; j < comp->elem[i].model_count; j++) {
prep_model_item_header(&comp->elem[i].models[j], &cor_id, &ext_mod_cnt, buf,
&offset, 0);
if (ext_mod_cnt != 0) {
add_items_to_page(buf, &comp->elem[i].models[j], ext_mod_cnt,
&offset,
0);
}
}
for (j = 0; j < comp->elem[i].vnd_model_count; j++) {
prep_model_item_header(&comp->elem[i].vnd_models[j], &cor_id, &ext_mod_cnt,
buf, &offset,
comp->elem[i].model_count);
if (ext_mod_cnt != 0) {
add_items_to_page(buf, &comp->elem[i].vnd_models[j], ext_mod_cnt,
&offset,
comp->elem[i].model_count);
}
}
}
return 0;
}
static int bt_mesh_comp_data_get_page_2(struct net_buf_simple *buf, size_t offset)
{
if (!dev_comp2) {
LOG_ERR("Composition data P2 not registered");
return -ENODEV;
}
size_t elem_size;
for (int i = 0; i < dev_comp2->record_cnt; i++) {
elem_size =
8 + dev_comp2->record[i].elem_offset_cnt + dev_comp2->record[i].data_len;
if (offset >= elem_size) {
offset -= elem_size;
continue;
}
if (net_buf_simple_tailroom(buf) < ((elem_size - offset) + BT_MESH_MIC_SHORT)) {
if (IS_ENABLED(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV)) {
/* MshPRTv1.1: 4.4.1.2.2:
* If the complete list of models does not fit in the Data field,
* the element shall not be reported.
*/
LOG_DBG("Record 0x%04x didn't fit in the Data field", i);
return 0;
}
LOG_ERR("Too large device composition");
return -E2BIG;
}
data_buf_add_le16_offset(buf, dev_comp2->record[i].id, &offset);
data_buf_add_u8_offset(buf, dev_comp2->record[i].version.x, &offset);
data_buf_add_u8_offset(buf, dev_comp2->record[i].version.y, &offset);
data_buf_add_u8_offset(buf, dev_comp2->record[i].version.z, &offset);
data_buf_add_u8_offset(buf, dev_comp2->record[i].elem_offset_cnt, &offset);
if (dev_comp2->record[i].elem_offset_cnt) {
data_buf_add_mem_offset(buf, dev_comp2->record[i].elem_offset,
dev_comp2->record[i].elem_offset_cnt, &offset);
}
data_buf_add_le16_offset(buf, dev_comp2->record[i].data_len, &offset);
if (dev_comp2->record[i].data_len) {
data_buf_add_mem_offset(buf, dev_comp2->record[i].data,
dev_comp2->record[i].data_len, &offset);
}
}
return 0;
}
int32_t bt_mesh_model_pub_period_get(const struct bt_mesh_model *mod)
{
int32_t period;
if (!mod->pub) {
return 0;
}
switch (mod->pub->period >> 6) {
case 0x00:
/* 1 step is 100 ms */
period = (mod->pub->period & BIT_MASK(6)) * 100U;
break;
case 0x01:
/* 1 step is 1 second */
period = (mod->pub->period & BIT_MASK(6)) * MSEC_PER_SEC;
break;
case 0x02:
/* 1 step is 10 seconds */
period = (mod->pub->period & BIT_MASK(6)) * 10U * MSEC_PER_SEC;
break;
case 0x03:
/* 1 step is 10 minutes */
period = (mod->pub->period & BIT_MASK(6)) * 600U * MSEC_PER_SEC;
break;
default:
CODE_UNREACHABLE;
}
if (mod->pub->fast_period) {
if (!period) {
return 0;
}
return MAX(period >> mod->pub->period_div, 100);
} else {
return period;
}
}
static int32_t next_period(const struct bt_mesh_model *mod)
{
struct bt_mesh_model_pub *pub = mod->pub;
uint32_t period = 0;
uint32_t elapsed;
elapsed = k_uptime_get_32() - pub->period_start;
LOG_DBG("Publishing took %ums", elapsed);
if (mod->pub->count) {
/* If a message is to be retransmitted, period should include time since the first
* publication until the last publication.
*/
period = BT_MESH_PUB_TRANSMIT_INT(mod->pub->retransmit);
period *= BT_MESH_PUB_MSG_NUM(mod->pub);
if (period && elapsed >= period) {
LOG_WRN("Retransmission interval is too short");
if (!!pub->delayable) {
LOG_WRN("Publication period is too short for"
" retransmissions");
}
/* Keep retransmitting the message with the interval sacrificing the
* next publication period start.
*/
return BT_MESH_PUB_TRANSMIT_INT(mod->pub->retransmit);
}
}
if (!period) {
period = bt_mesh_model_pub_period_get(mod);
if (!period) {
return 0;
}
}
if (elapsed >= period) {
LOG_WRN("Publication sending took longer than the period");
if (!!pub->delayable) {
LOG_WRN("Publication period is too short to be delayable");
}
/* Return smallest positive number since 0 means disabled */
return 1;
}
return period - elapsed;
}
static void publish_sent(int err, void *user_data)
{
const struct bt_mesh_model *mod = user_data;
int32_t delay;
LOG_DBG("err %d, time %u", err, k_uptime_get_32());
delay = next_period(mod);
if (delay) {
LOG_DBG("Publishing next time in %dms", delay);
/* Using schedule() in case the application has already called
* bt_mesh_publish, and a publication is pending.
*/
k_work_schedule(&mod->pub->timer, K_MSEC(delay));
}
}
static void publish_start(uint16_t duration, int err, void *user_data)
{
if (err) {
LOG_ERR("Failed to publish: err %d", err);
publish_sent(err, user_data);
return;
}
}
static const struct bt_mesh_send_cb pub_sent_cb = {
.start = publish_start,
.end = publish_sent,
};
static int publish_transmit(const struct bt_mesh_model *mod)
{
NET_BUF_SIMPLE_DEFINE(sdu, BT_MESH_TX_SDU_MAX);
struct bt_mesh_model_pub *pub = mod->pub;
struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_PUB(pub);
struct bt_mesh_net_tx tx = {
.ctx = &ctx,
.src = bt_mesh_model_elem(mod)->rt->addr,
.friend_cred = pub->cred,
};
net_buf_simple_add_mem(&sdu, pub->msg->data, pub->msg->len);
return bt_mesh_trans_send(&tx, &sdu, &pub_sent_cb, (void *)mod);
}
static int pub_period_start(struct bt_mesh_model_pub *pub)
{
int err;
pub->count = BT_MESH_PUB_TRANSMIT_COUNT(pub->retransmit);
if (!pub->update) {
return 0;
}
err = pub->update(pub->mod);
pub->period_start = k_uptime_get_32();
if (err) {
/* Skip this publish attempt. */
LOG_DBG("Update failed, skipping publish (err: %d)", err);
pub->count = 0;
publish_sent(err, (void *)pub->mod);
return err;
}
return 0;
}
static uint16_t pub_delay_get(int random_delay_window)
{
if (!IS_ENABLED(CONFIG_BT_MESH_DELAYABLE_PUBLICATION)) {
return 0;
}
uint16_t num = 0;
(void)bt_rand(&num, sizeof(num));
return 20 + (num % random_delay_window);
}
static int pub_delay_schedule(struct bt_mesh_model_pub *pub, int delay)
{
uint16_t random;
int err;
if (!IS_ENABLED(CONFIG_BT_MESH_DELAYABLE_PUBLICATION)) {
return -ENOTSUP;
}
random = pub_delay_get(delay);
err = k_work_reschedule(&pub->timer, K_MSEC(random));
if (err < 0) {
LOG_ERR("Unable to delay publication (err %d)", err);
return err;
}
LOG_DBG("Publication delayed by %dms", random);
return 0;
}
static void mod_publish(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct bt_mesh_model_pub *pub = CONTAINER_OF(dwork,
struct bt_mesh_model_pub,
timer);
int err;
if (pub->addr == BT_MESH_ADDR_UNASSIGNED ||
atomic_test_bit(bt_mesh.flags, BT_MESH_SUSPENDED)) {
/* Publication is no longer active, but the cancellation of the
* delayed work failed. Abandon recurring timer.
*/
return;
}
LOG_DBG("timestamp: %u", k_uptime_get_32());
if (pub->count) {
pub->count--;
if (pub->retr_update && pub->update &&
bt_mesh_model_pub_is_retransmission(pub->mod)) {
err = pub->update(pub->mod);
if (err) {
publish_sent(err, (void *)pub->mod);
return;
}
}
} else {
/* First publication in this period */
err = pub_period_start(pub);
if (err) {
return;
}
/* Delay the first publication in a period. */
if (!!pub->delayable && !pub_delay_schedule(pub, RANDOM_DELAY_SHORT)) {
/* Increment count as it would do BT_MESH_PUB_MSG_TOTAL */
pub->count++;
return;
}
}
err = publish_transmit(pub->mod);
if (err) {
LOG_ERR("Failed to publish (err %d)", err);
publish_sent(err, (void *)pub->mod);
}
}
const struct bt_mesh_elem *bt_mesh_model_elem(const struct bt_mesh_model *mod)
{
return &dev_comp->elem[mod->rt->elem_idx];
}
const struct bt_mesh_model *bt_mesh_model_get(bool vnd, uint8_t elem_idx, uint8_t mod_idx)
{
const struct bt_mesh_elem *elem;
if (elem_idx >= dev_comp->elem_count) {
LOG_ERR("Invalid element index %u", elem_idx);
return NULL;
}
elem = &dev_comp->elem[elem_idx];
if (vnd) {
if (mod_idx >= elem->vnd_model_count) {
LOG_ERR("Invalid vendor model index %u", mod_idx);
return NULL;
}
return &elem->vnd_models[mod_idx];
} else {
if (mod_idx >= elem->model_count) {
LOG_ERR("Invalid SIG model index %u", mod_idx);
return NULL;
}
return &elem->models[mod_idx];
}
}
#if defined(CONFIG_BT_MESH_MODEL_VND_MSG_CID_FORCE)
static int bt_mesh_vnd_mod_msg_cid_check(const struct bt_mesh_model *mod)
{
uint16_t cid;
const struct bt_mesh_model_op *op;
for (op = mod->op; op->func; op++) {
cid = (uint16_t)(op->opcode & 0xffff);
if (cid == mod->vnd.company) {
continue;
}
LOG_ERR("Invalid vendor model(company:0x%04x"
" id:0x%04x) message opcode 0x%08x",
mod->vnd.company, mod->vnd.id, op->opcode);
return -EINVAL;
}
return 0;
}
#endif
static void mod_init(const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem,
bool vnd, bool primary, void *user_data)
{
int i;
int *err = user_data;
if (*err) {
return;
}
if (mod->pub) {
mod->pub->mod = mod;
k_work_init_delayable(&mod->pub->timer, mod_publish);
}
for (i = 0; i < mod->keys_cnt; i++) {
mod->keys[i] = BT_MESH_KEY_UNUSED;
}
mod->rt->elem_idx = elem - dev_comp->elem;
if (vnd) {
mod->rt->mod_idx = mod - elem->vnd_models;
if (IS_ENABLED(CONFIG_BT_MESH_MODEL_VND_MSG_CID_FORCE)) {
*err = bt_mesh_vnd_mod_msg_cid_check(mod);
if (*err) {
return;
}
}
} else {
mod->rt->mod_idx = mod - elem->models;
}
if (mod->cb && mod->cb->init) {
*err = mod->cb->init(mod);
}
}
int bt_mesh_comp_register(const struct bt_mesh_comp *comp)
{
int err;
/* There must be at least one element */
if (!comp || !comp->elem_count) {
return -EINVAL;
}
dev_comp = comp;
err = 0;
if (MOD_REL_LIST_SIZE > 0) {
memset(mod_rel_list, 0, sizeof(mod_rel_list));
}
bt_mesh_model_foreach(mod_init, &err);
if (MOD_REL_LIST_SIZE > 0) {
int i;
MOD_REL_LIST_FOR_EACH(i) {
LOG_DBG("registered %s",
mod_rel_list[i].type < RELATION_TYPE_EXT ?
"correspondence" : "extension");
LOG_DBG("\tbase: elem %u idx %u",
mod_rel_list[i].elem_base,
mod_rel_list[i].idx_base);
LOG_DBG("\text: elem %u idx %u",
mod_rel_list[i].elem_ext,
mod_rel_list[i].idx_ext);
}
if (i < MOD_REL_LIST_SIZE) {
LOG_WRN("Unused space in relation list: %d",
MOD_REL_LIST_SIZE - i);
}
}
return err;
}
int bt_mesh_comp2_register(const struct bt_mesh_comp2 *comp2)
{
if (!IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_2)) {
return -EINVAL;
}
dev_comp2 = comp2;
return 0;
}
void bt_mesh_comp_provision(uint16_t addr)
{
int i;
dev_primary_addr = addr;
LOG_DBG("addr 0x%04x elem_count %zu", addr, dev_comp->elem_count);
for (i = 0; i < dev_comp->elem_count; i++) {
const struct bt_mesh_elem *elem = &dev_comp->elem[i];
elem->rt->addr = addr++;
LOG_DBG("addr 0x%04x mod_count %u vnd_mod_count %u", elem->rt->addr,
elem->model_count, elem->vnd_model_count);
}
}
void bt_mesh_comp_unprovision(void)
{
LOG_DBG("");
dev_primary_addr = BT_MESH_ADDR_UNASSIGNED;
for (int i = 0; i < dev_comp->elem_count; i++) {
const struct bt_mesh_elem *elem = &dev_comp->elem[i];
elem->rt->addr = BT_MESH_ADDR_UNASSIGNED;
}
}
uint16_t bt_mesh_primary_addr(void)
{
return dev_primary_addr;
}
static uint16_t *model_group_get(const struct bt_mesh_model *mod, uint16_t addr)
{
int i;
for (i = 0; i < mod->groups_cnt; i++) {
if (mod->groups[i] == addr) {
return &mod->groups[i];
}
}
return NULL;
}
struct find_group_visitor_ctx {
uint16_t *entry;
const struct bt_mesh_model *mod;
uint16_t addr;
};
static enum bt_mesh_walk find_group_mod_visitor(const struct bt_mesh_model *mod, void *user_data)
{
struct find_group_visitor_ctx *ctx = user_data;
if (mod->rt->elem_idx != ctx->mod->rt->elem_idx) {
return BT_MESH_WALK_CONTINUE;
}
ctx->entry = model_group_get(mod, ctx->addr);
if (ctx->entry) {
ctx->mod = mod;
return BT_MESH_WALK_STOP;
}
return BT_MESH_WALK_CONTINUE;
}
uint16_t *bt_mesh_model_find_group(const struct bt_mesh_model **mod, uint16_t addr)
{
struct find_group_visitor_ctx ctx = {
.mod = *mod,
.entry = NULL,
.addr = addr,
};
bt_mesh_model_extensions_walk(*mod, find_group_mod_visitor, &ctx);
*mod = ctx.mod;
return ctx.entry;
}
#if CONFIG_BT_MESH_LABEL_COUNT > 0
static const uint8_t **model_uuid_get(const struct bt_mesh_model *mod, const uint8_t *uuid)
{
int i;
for (i = 0; i < CONFIG_BT_MESH_LABEL_COUNT; i++) {
if (mod->uuids[i] == uuid) {
/* If we are looking for a new entry, ensure that we find a model where
* there is empty entry in both, uuids and groups list.
*/
if (uuid == NULL && !model_group_get(mod, BT_MESH_ADDR_UNASSIGNED)) {
continue;
}
return &mod->uuids[i];
}
}
return NULL;
}
struct find_uuid_visitor_ctx {
const uint8_t **entry;
const struct bt_mesh_model *mod;
const uint8_t *uuid;
};
static enum bt_mesh_walk find_uuid_mod_visitor(const struct bt_mesh_model *mod, void *user_data)
{
struct find_uuid_visitor_ctx *ctx = user_data;
if (mod->rt->elem_idx != ctx->mod->rt->elem_idx) {
return BT_MESH_WALK_CONTINUE;
}
ctx->entry = model_uuid_get(mod, ctx->uuid);
if (ctx->entry) {
ctx->mod = mod;
return BT_MESH_WALK_STOP;
}
return BT_MESH_WALK_CONTINUE;
}
#endif /* CONFIG_BT_MESH_LABEL_COUNT > 0 */
const uint8_t **bt_mesh_model_find_uuid(const struct bt_mesh_model **mod, const uint8_t *uuid)
{
#if CONFIG_BT_MESH_LABEL_COUNT > 0
struct find_uuid_visitor_ctx ctx = {
.mod = *mod,
.entry = NULL,
.uuid = uuid,
};
bt_mesh_model_extensions_walk(*mod, find_uuid_mod_visitor, &ctx);
*mod = ctx.mod;
return ctx.entry;
#else
return NULL;
#endif
}
static const struct bt_mesh_model *bt_mesh_elem_find_group(const struct bt_mesh_elem *elem,
uint16_t group_addr)
{
const struct bt_mesh_model *model;
uint16_t *match;
int i;
for (i = 0; i < elem->model_count; i++) {
model = &elem->models[i];
match = model_group_get(model, group_addr);
if (match) {
return model;
}
}
for (i = 0; i < elem->vnd_model_count; i++) {
model = &elem->vnd_models[i];
match = model_group_get(model, group_addr);
if (match) {
return model;
}
}
return NULL;
}
const struct bt_mesh_elem *bt_mesh_elem_find(uint16_t addr)
{
uint16_t index;
if (!BT_MESH_ADDR_IS_UNICAST(addr)) {
return NULL;
}
index = addr - dev_comp->elem[0].rt->addr;
if (index >= dev_comp->elem_count) {
return NULL;
}
return &dev_comp->elem[index];
}
bool bt_mesh_has_addr(uint16_t addr)
{
uint16_t index;
if (BT_MESH_ADDR_IS_UNICAST(addr)) {
return bt_mesh_elem_find(addr) != NULL;
}
if (IS_ENABLED(CONFIG_BT_MESH_ACCESS_LAYER_MSG) && msg_cb) {
return true;
}
for (index = 0; index < dev_comp->elem_count; index++) {
const struct bt_mesh_elem *elem = &dev_comp->elem[index];
if (bt_mesh_elem_find_group(elem, addr)) {
return true;
}
}
return false;
}
#if defined(CONFIG_BT_MESH_ACCESS_LAYER_MSG)
void bt_mesh_msg_cb_set(void (*cb)(uint32_t opcode, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf))
{
msg_cb = cb;
}
#endif
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)
{
struct bt_mesh_net_tx tx = {
.ctx = ctx,
.src = src_addr,
};
LOG_DBG("net_idx 0x%04x app_idx 0x%04x dst 0x%04x", tx.ctx->net_idx, tx.ctx->app_idx,
tx.ctx->addr);
LOG_DBG("len %u: %s", buf->len, bt_hex(buf->data, buf->len));
if (!bt_mesh_is_provisioned()) {
LOG_ERR("Local node is not yet provisioned");
return -EAGAIN;
}
return bt_mesh_trans_send(&tx, buf, cb, cb_data);
}
uint8_t bt_mesh_elem_count(void)
{
return dev_comp->elem_count;
}
bool bt_mesh_model_has_key(const struct bt_mesh_model *mod, uint16_t key)
{
int i;
for (i = 0; i < mod->keys_cnt; i++) {
if (mod->keys[i] == key ||
(mod->keys[i] == BT_MESH_KEY_DEV_ANY &&
BT_MESH_IS_DEV_KEY(key))) {
return true;
}
}
return false;
}
static bool model_has_dst(const struct bt_mesh_model *mod, uint16_t dst, const uint8_t *uuid)
{
if (BT_MESH_ADDR_IS_UNICAST(dst)) {
return (dev_comp->elem[mod->rt->elem_idx].rt->addr == dst);
} else if (BT_MESH_ADDR_IS_VIRTUAL(dst)) {
return !!bt_mesh_model_find_uuid(&mod, uuid);
} else if (BT_MESH_ADDR_IS_GROUP(dst) ||
(BT_MESH_ADDR_IS_FIXED_GROUP(dst) && mod->rt->elem_idx != 0)) {
return !!bt_mesh_model_find_group(&mod, dst);
}
/* If a message with a fixed group address is sent to the access layer,
* the lower layers have already confirmed that we are subscribing to
* it. All models on the primary element should receive the message.
*/
return mod->rt->elem_idx == 0;
}
static const struct bt_mesh_model_op *find_op(const struct bt_mesh_elem *elem,
uint32_t opcode, const struct bt_mesh_model **model)
{
uint8_t i;
uint8_t count;
/* This value shall not be used in shipping end products. */
uint32_t cid = UINT32_MAX;
const struct bt_mesh_model *models;
/* SIG models cannot contain 3-byte (vendor) OpCodes, and
* vendor models cannot contain SIG (1- or 2-byte) OpCodes, so
* we only need to do the lookup in one of the model lists.
*/
if (BT_MESH_MODEL_OP_LEN(opcode) < 3) {
models = elem->models;
count = elem->model_count;
} else {
models = elem->vnd_models;
count = elem->vnd_model_count;
cid = (uint16_t)(opcode & 0xffff);
}
for (i = 0U; i < count; i++) {
const struct bt_mesh_model_op *op;
if (IS_ENABLED(CONFIG_BT_MESH_MODEL_VND_MSG_CID_FORCE) &&
cid != UINT32_MAX &&
cid != models[i].vnd.company) {
continue;
}
*model = &models[i];
for (op = (*model)->op; op->func; op++) {
if (op->opcode == opcode) {
return op;
}
}
}
*model = NULL;
return NULL;
}
static int get_opcode(struct net_buf_simple *buf, uint32_t *opcode)
{
switch (buf->data[0] >> 6) {
case 0x00:
case 0x01:
if (buf->data[0] == 0x7f) {
LOG_ERR("Ignoring RFU OpCode");
return -EINVAL;
}
*opcode = net_buf_simple_pull_u8(buf);
return 0;
case 0x02:
if (buf->len < 2) {
LOG_ERR("Too short payload for 2-octet OpCode");
return -EINVAL;
}
*opcode = net_buf_simple_pull_be16(buf);
return 0;
case 0x03:
if (buf->len < 3) {
LOG_ERR("Too short payload for 3-octet OpCode");
return -EINVAL;
}
*opcode = net_buf_simple_pull_u8(buf) << 16;
/* Using LE for the CID since the model layer is defined as
* little-endian in the mesh spec and using BT_MESH_MODEL_OP_3
* will declare the opcode in this way.
*/
*opcode |= net_buf_simple_pull_le16(buf);
return 0;
}
CODE_UNREACHABLE;
}
static int element_model_recv(struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf,
const struct bt_mesh_elem *elem, uint32_t opcode)
{
const struct bt_mesh_model_op *op;
const struct bt_mesh_model *model;
struct net_buf_simple_state state;
int err;
op = find_op(elem, opcode, &model);
if (!op) {
LOG_DBG("No OpCode 0x%08x for elem 0x%02x", opcode, elem->rt->addr);
return ACCESS_STATUS_WRONG_OPCODE;
}
if (!bt_mesh_model_has_key(model, ctx->app_idx)) {
LOG_ERR("Wrong key");
return ACCESS_STATUS_WRONG_KEY;
}
if (!model_has_dst(model, ctx->recv_dst, ctx->uuid)) {
LOG_ERR("Invalid address 0x%02x", ctx->recv_dst);
return ACCESS_STATUS_INVALID_ADDRESS;
}
if ((op->len >= 0) && (buf->len < (size_t)op->len)) {
LOG_ERR("Too short message for OpCode 0x%08x", opcode);
return ACCESS_STATUS_MESSAGE_NOT_UNDERSTOOD;
} else if ((op->len < 0) && (buf->len != (size_t)(-op->len))) {
LOG_ERR("Invalid message size for OpCode 0x%08x", opcode);
return ACCESS_STATUS_MESSAGE_NOT_UNDERSTOOD;
}
if (IS_ENABLED(CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CTX_ENABLED)) {
ctx->rnd_delay = true;
}
net_buf_simple_save(buf, &state);
err = op->func(model, ctx, buf);
net_buf_simple_restore(buf, &state);
if (err) {
return ACCESS_STATUS_MESSAGE_NOT_UNDERSTOOD;
}
return ACCESS_STATUS_SUCCESS;
}
int bt_mesh_model_recv(struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
int err = ACCESS_STATUS_SUCCESS;
uint32_t opcode;
uint16_t index;
LOG_DBG("app_idx 0x%04x src 0x%04x dst 0x%04x", ctx->app_idx, ctx->addr,
ctx->recv_dst);
LOG_DBG("len %u: %s", buf->len, bt_hex(buf->data, buf->len));
if (IS_ENABLED(CONFIG_BT_TESTING)) {
bt_test_mesh_model_recv(ctx->addr, ctx->recv_dst, buf->data,
buf->len);
}
if (get_opcode(buf, &opcode) < 0) {
LOG_WRN("Unable to decode OpCode");
return ACCESS_STATUS_WRONG_OPCODE;
}
LOG_DBG("OpCode 0x%08x", opcode);
if (BT_MESH_ADDR_IS_UNICAST(ctx->recv_dst)) {
index = ctx->recv_dst - dev_comp->elem[0].rt->addr;
if (index >= dev_comp->elem_count) {
LOG_ERR("Invalid address 0x%02x", ctx->recv_dst);
return ACCESS_STATUS_INVALID_ADDRESS;
} else {
const struct bt_mesh_elem *elem = &dev_comp->elem[index];
err = element_model_recv(ctx, buf, elem, opcode);
}
} else {
err = ACCESS_STATUS_MESSAGE_NOT_UNDERSTOOD;
for (index = 0; index < dev_comp->elem_count; index++) {
const struct bt_mesh_elem *elem = &dev_comp->elem[index];
int err_elem;
err_elem = element_model_recv(ctx, buf, elem, opcode);
err = err_elem == ACCESS_STATUS_SUCCESS ? err_elem : err;
}
}
if (IS_ENABLED(CONFIG_BT_MESH_ACCESS_LAYER_MSG) && msg_cb) {
msg_cb(opcode, ctx, buf);
}
return err;
}
int bt_mesh_access_recv(struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
int err;
err = bt_mesh_model_recv(ctx, buf);
if (IS_ENABLED(CONFIG_BT_MESH_ACCESS_LAYER_MSG) && msg_cb) {
/* Mesh assumes that the application has processed the message.
* Access layer returns success to trigger RPL update and prevent
* replay attack over application.
*/
err = 0;
}
return err;
}
int bt_mesh_model_send(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *msg,
const struct bt_mesh_send_cb *cb, void *cb_data)
{
if (IS_ENABLED(CONFIG_BT_MESH_OP_AGG_SRV) && bt_mesh_op_agg_srv_accept(ctx, msg)) {
return bt_mesh_op_agg_srv_send(model, msg);
} else if (IS_ENABLED(CONFIG_BT_MESH_OP_AGG_CLI) && bt_mesh_op_agg_cli_accept(ctx, msg)) {
return bt_mesh_op_agg_cli_send(model, msg);
}
if (!bt_mesh_model_has_key(model, ctx->app_idx)) {
LOG_ERR("Model not bound to AppKey 0x%04x", ctx->app_idx);
return -EINVAL;
}
#if defined CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG
/* No sense to use delayable message for unicast loopback. */
if (ctx->rnd_delay &&
!(bt_mesh_has_addr(ctx->addr) && BT_MESH_ADDR_IS_UNICAST(ctx->addr))) {
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);
}
int bt_mesh_model_publish(const struct bt_mesh_model *model)
{
struct bt_mesh_model_pub *pub = model->pub;
if (!pub) {
return -ENOTSUP;
}
LOG_DBG("");
if (pub->addr == BT_MESH_ADDR_UNASSIGNED) {
return -EADDRNOTAVAIL;
}
if (!pub->msg || !pub->msg->len) {
LOG_ERR("No publication message");
return -EINVAL;
}
if (pub->msg->len + BT_MESH_MIC_SHORT > BT_MESH_TX_SDU_MAX) {
LOG_ERR("Message does not fit maximum SDU size");
return -EMSGSIZE;
}
if (pub->count) {
LOG_WRN("Clearing publish retransmit timer");
}
/* Account for initial transmission */
pub->count = BT_MESH_PUB_MSG_TOTAL(pub);
pub->period_start = k_uptime_get_32();
LOG_DBG("Publish Retransmit Count %u Interval %ums", pub->count,
BT_MESH_PUB_TRANSMIT_INT(pub->retransmit));
/* Delay the publication for longer time when the publication is triggered manually (section
* 3.7.3.1):
*
* When the publication of a message is the result of a power-up, a state transition
* progress update, or completion of a state transition, multiple nodes may be reporting the
* state change at the same time. To reduce the probability of a message collision, these
* messages should be sent with a random delay between 20 and 500 milliseconds.
*/
if (!!pub->delayable && !pub_delay_schedule(pub, RANDOM_DELAY_LONG)) {
return 0;
}
k_work_reschedule(&pub->timer, K_NO_WAIT);
return 0;
}
const struct bt_mesh_model *bt_mesh_model_find_vnd(const struct bt_mesh_elem *elem,
uint16_t company, uint16_t id)
{
uint8_t i;
for (i = 0U; i < elem->vnd_model_count; i++) {
if (elem->vnd_models[i].vnd.company == company &&
elem->vnd_models[i].vnd.id == id) {
return &elem->vnd_models[i];
}
}
return NULL;
}
const struct bt_mesh_model *bt_mesh_model_find(const struct bt_mesh_elem *elem,
uint16_t id)
{
uint8_t i;
for (i = 0U; i < elem->model_count; i++) {
if (elem->models[i].id == id) {
return &elem->models[i];
}
}
return NULL;
}
const struct bt_mesh_comp *bt_mesh_comp_get(void)
{
return dev_comp;
}
void bt_mesh_model_extensions_walk(const struct bt_mesh_model *model,
enum bt_mesh_walk (*cb)(const struct bt_mesh_model *mod,
void *user_data),
void *user_data)
{
#ifndef CONFIG_BT_MESH_MODEL_EXTENSIONS
(void)cb(model, user_data);
return;
#else
const struct bt_mesh_model *it;
if (cb(model, user_data) == BT_MESH_WALK_STOP || !model->rt->next) {
return;
}
/* List is circular. Step through all models until we reach the start: */
for (it = model->rt->next; it != model; it = it->rt->next) {
if (cb(it, user_data) == BT_MESH_WALK_STOP) {
return;
}
}
#endif
}
#ifdef CONFIG_BT_MESH_MODEL_EXTENSIONS
/* For vendor models, determine the offset within the model relation list
* by counting the number of standard SIG models in the associated element.
*/
static uint8_t get_sig_offset(const struct bt_mesh_model *mod)
{
const struct bt_mesh_elem *elem = bt_mesh_model_elem(mod);
uint8_t i;
for (i = 0U; i < elem->vnd_model_count; i++) {
if (&elem->vnd_models[i] == mod) {
return elem->model_count;
}
}
return 0;
}
static int mod_rel_register(const struct bt_mesh_model *base,
const struct bt_mesh_model *ext,
uint8_t type)
{
LOG_DBG("");
struct mod_relation extension = {
base->rt->elem_idx,
base->rt->mod_idx + get_sig_offset(base),
ext->rt->elem_idx,
ext->rt->mod_idx + get_sig_offset(ext),
type,
};
int i;
for (i = 0; i < ARRAY_SIZE(mod_rel_list); i++) {
if (mod_rel_list[i].elem_base == 0 &&
mod_rel_list[i].idx_base == 0 &&
mod_rel_list[i].elem_ext == 0 &&
mod_rel_list[i].idx_ext == 0) {
memcpy(&mod_rel_list[i], &extension,
sizeof(extension));
return 0;
}
}
LOG_ERR("CONFIG_BT_MESH_MODEL_EXTENSION_LIST_SIZE is too small");
return -ENOMEM;
}
int bt_mesh_model_extend(const struct bt_mesh_model *extending_mod,
const struct bt_mesh_model *base_mod)
{
const struct bt_mesh_model *a = extending_mod;
const struct bt_mesh_model *b = base_mod;
const struct bt_mesh_model *a_next = a->rt->next;
const struct bt_mesh_model *b_next = b->rt->next;
const struct bt_mesh_model *it;
base_mod->rt->flags |= BT_MESH_MOD_EXTENDED;
if (a == b) {
return 0;
}
/* Check if a's list contains b */
for (it = a; (it != NULL) && (it->rt->next != a); it = it->rt->next) {
if (it == b) {
goto register_extension;
}
}
/* Merge lists */
if (a_next) {
b->rt->next = a_next;
} else {
b->rt->next = a;
}
if (b_next) {
a->rt->next = b_next;
} else {
a->rt->next = b;
}
register_extension:
if (MOD_REL_LIST_SIZE > 0) {
return mod_rel_register(base_mod, extending_mod, RELATION_TYPE_EXT);
} else if (IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1)) {
LOG_ERR("CONFIG_BT_MESH_MODEL_EXTENSION_LIST_SIZE is too small");
return -ENOMEM;
}
return 0;
}
int bt_mesh_model_correspond(const struct bt_mesh_model *corresponding_mod,
const struct bt_mesh_model *base_mod)
{
int i, err;
uint8_t cor_id = 0;
if (MOD_REL_LIST_SIZE == 0) {
return -ENOTSUP;
}
uint8_t base_offset = get_sig_offset(base_mod);
uint8_t corresponding_offset = get_sig_offset(corresponding_mod);
MOD_REL_LIST_FOR_EACH(i) {
if (mod_rel_list[i].type < RELATION_TYPE_EXT &&
mod_rel_list[i].type > cor_id) {
cor_id = mod_rel_list[i].type;
}
if ((IS_MOD_BASE(base_mod, i, base_offset) ||
IS_MOD_EXTENSION(base_mod, i, base_offset) ||
IS_MOD_BASE(corresponding_mod, i, corresponding_offset) ||
IS_MOD_EXTENSION(corresponding_mod, i, corresponding_offset)) &&
mod_rel_list[i].type < RELATION_TYPE_EXT) {
return mod_rel_register(base_mod, corresponding_mod, mod_rel_list[i].type);
}
}
err = mod_rel_register(base_mod, corresponding_mod, cor_id);
if (err) {
return err;
}
return 0;
}
#endif /* CONFIG_BT_MESH_MODEL_EXTENSIONS */
bool bt_mesh_model_is_extended(const struct bt_mesh_model *model)
{
return model->rt->flags & BT_MESH_MOD_EXTENDED;
}
static int mod_set_bind(const struct bt_mesh_model *mod, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
ssize_t len;
int i;
/* Start with empty array regardless of cleared or set value */
for (i = 0; i < mod->keys_cnt; i++) {
mod->keys[i] = BT_MESH_KEY_UNUSED;
}
if (len_rd == 0) {
LOG_DBG("Cleared bindings for model");
return 0;
}
len = read_cb(cb_arg, mod->keys, mod->keys_cnt * sizeof(mod->keys[0]));
if (len < 0) {
LOG_ERR("Failed to read value (err %zd)", len);
return len;
}
LOG_HEXDUMP_DBG(mod->keys, len, "val");
LOG_DBG("Decoded %zu bound keys for model", len / sizeof(mod->keys[0]));
return 0;
}
static int mod_set_sub(const struct bt_mesh_model *mod, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
size_t size = mod->groups_cnt * sizeof(mod->groups[0]);
ssize_t len;
/* Start with empty array regardless of cleared or set value */
(void)memset(mod->groups, 0, size);
if (len_rd == 0) {
LOG_DBG("Cleared subscriptions for model");
return 0;
}
len = read_cb(cb_arg, mod->groups, size);
if (len < 0) {
LOG_ERR("Failed to read value (err %zd)", len);
return len;
}
LOG_HEXDUMP_DBG(mod->groups, len, "val");
LOG_DBG("Decoded %zu subscribed group addresses for model", len / sizeof(mod->groups[0]));
#if !IS_ENABLED(CONFIG_BT_MESH_LABEL_NO_RECOVER) && (CONFIG_BT_MESH_LABEL_COUNT > 0)
/* If uuids[0] is NULL, then either the model is not subscribed to virtual addresses or
* uuids are not yet recovered.
*/
if (mod->uuids[0] == NULL) {
int i, j = 0;
for (i = 0; i < mod->groups_cnt && j < CONFIG_BT_MESH_LABEL_COUNT; i++) {
if (BT_MESH_ADDR_IS_VIRTUAL(mod->groups[i])) {
/* Recover from implementation where uuid was not stored for
* virtual address. It is safe to pick first matched label because
* previously the stack wasn't able to store virtual addresses with
* collisions.
*/
mod->uuids[j] = bt_mesh_va_uuid_get(mod->groups[i], NULL, NULL);
j++;
}
}
}
#endif
return 0;
}
static int mod_set_sub_va(const struct bt_mesh_model *mod, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
#if CONFIG_BT_MESH_LABEL_COUNT > 0
uint16_t uuidxs[CONFIG_BT_MESH_LABEL_COUNT];
ssize_t len;
int i;
int count;
/* Start with empty array regardless of cleared or set value */
(void)memset(mod->uuids, 0, CONFIG_BT_MESH_LABEL_COUNT * sizeof(mod->uuids[0]));
if (len_rd == 0) {
LOG_DBG("Cleared subscriptions for model");
return 0;
}
len = read_cb(cb_arg, uuidxs, sizeof(uuidxs));
if (len < 0) {
LOG_ERR("Failed to read value (err %zd)", len);
return len;
}
LOG_HEXDUMP_DBG(uuidxs, len, "val");
for (i = 0, count = 0; i < len / sizeof(uint16_t); i++) {
mod->uuids[count] = bt_mesh_va_get_uuid_by_idx(uuidxs[i]);
if (mod->uuids[count] != NULL) {
count++;
}
}
LOG_DBG("Decoded %zu subscribed virtual addresses for model", count);
#endif /* CONFIG_BT_MESH_LABEL_COUNT > 0 */
return 0;
}
static int mod_set_pub(const struct bt_mesh_model *mod, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
struct mod_pub_val pub;
int err;
if (!mod->pub) {
LOG_WRN("Model has no publication context!");
return -EINVAL;
}
if (len_rd == 0) {
mod->pub->addr = BT_MESH_ADDR_UNASSIGNED;
mod->pub->key = 0U;
mod->pub->cred = 0U;
mod->pub->ttl = 0U;
mod->pub->period = 0U;
mod->pub->retransmit = 0U;
mod->pub->count = 0U;
mod->pub->uuid = NULL;
LOG_DBG("Cleared publication for model");
return 0;
}
if (!IS_ENABLED(CONFIG_BT_SETTINGS)) {
return 0;
}
if (!IS_ENABLED(CONFIG_BT_MESH_LABEL_NO_RECOVER)) {
err = bt_mesh_settings_set(read_cb, cb_arg, &pub, sizeof(pub.base));
if (!err) {
/* Recover from implementation where uuid was not stored for virtual
* address. It is safe to pick first matched label because previously the
* stack wasn't able to store virtual addresses with collisions.
*/
if (BT_MESH_ADDR_IS_VIRTUAL(pub.base.addr)) {
mod->pub->uuid = bt_mesh_va_uuid_get(pub.base.addr, NULL, NULL);
}
goto pub_base_set;
}
}
err = bt_mesh_settings_set(read_cb, cb_arg, &pub, sizeof(pub));
if (err) {
LOG_ERR("Failed to set \'model-pub\'");
return err;
}
if (BT_MESH_ADDR_IS_VIRTUAL(pub.base.addr)) {
mod->pub->uuid = bt_mesh_va_get_uuid_by_idx(pub.uuidx);
}
pub_base_set:
mod->pub->addr = pub.base.addr;
mod->pub->key = pub.base.key;
mod->pub->cred = pub.base.cred;
mod->pub->ttl = pub.base.ttl;
mod->pub->period = pub.base.period;
mod->pub->retransmit = pub.base.retransmit;
mod->pub->period_div = pub.base.period_div;
mod->pub->count = 0U;
LOG_DBG("Restored model publication, dst 0x%04x app_idx 0x%03x", pub.base.addr,
pub.base.key);
return 0;
}
static int mod_data_set(const struct bt_mesh_model *mod,
const char *name, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
const char *next;
settings_name_next(name, &next);
if (mod->cb && mod->cb->settings_set) {
return mod->cb->settings_set(mod, next, len_rd,
read_cb, cb_arg);
}
return 0;
}
static int mod_set(bool vnd, const char *name, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
const struct bt_mesh_model *mod;
uint8_t elem_idx, mod_idx;
uint16_t mod_key;
int len;
const char *next;
if (!name) {
LOG_ERR("Insufficient number of arguments");
return -ENOENT;
}
mod_key = strtol(name, NULL, 16);
elem_idx = mod_key >> 8;
mod_idx = mod_key;
LOG_DBG("Decoded mod_key 0x%04x as elem_idx %u mod_idx %u", mod_key, elem_idx, mod_idx);
mod = bt_mesh_model_get(vnd, elem_idx, mod_idx);
if (!mod) {
LOG_ERR("Failed to get model for elem_idx %u mod_idx %u", elem_idx, mod_idx);
return -ENOENT;
}
len = settings_name_next(name, &next);
if (!next) {
LOG_ERR("Insufficient number of arguments");
return -ENOENT;
}
/* `len` contains length of model id string representation. Call settings_name_next() again
* to get length of `next`.
*/
switch (settings_name_next(next, NULL)) {
case 4:
if (!strncmp(next, "bind", 4)) {
return mod_set_bind(mod, len_rd, read_cb, cb_arg);
} else if (!strncmp(next, "subv", 4)) {
return mod_set_sub_va(mod, len_rd, read_cb, cb_arg);
} else if (!strncmp(next, "data", 4)) {
return mod_data_set(mod, next, len_rd, read_cb, cb_arg);
}
break;
case 3:
if (!strncmp(next, "sub", 3)) {
return mod_set_sub(mod, len_rd, read_cb, cb_arg);
} else if (!strncmp(next, "pub", 3)) {
return mod_set_pub(mod, len_rd, read_cb, cb_arg);
}
break;
default:
break;
}
LOG_WRN("Unknown module key %s", next);
return -ENOENT;
}
static int sig_mod_set(const char *name, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
return mod_set(false, name, len_rd, read_cb, cb_arg);
}
BT_MESH_SETTINGS_DEFINE(sig_mod, "s", sig_mod_set);
static int vnd_mod_set(const char *name, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
return mod_set(true, name, len_rd, read_cb, cb_arg);
}
BT_MESH_SETTINGS_DEFINE(vnd_mod, "v", vnd_mod_set);
static int comp_set(const char *name, size_t len_rd, settings_read_cb read_cb,
void *cb_arg)
{
/* Only need to know that the entry exists. Will load the contents on
* demand.
*/
if (len_rd > 0) {
atomic_set_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY);
}
return 0;
}
BT_MESH_SETTINGS_DEFINE(comp, "cmp", comp_set);
static void encode_mod_path(const struct bt_mesh_model *mod, bool vnd,
const char *key, char *path, size_t path_len)
{
uint16_t mod_key = (((uint16_t)mod->rt->elem_idx << 8) | mod->rt->mod_idx);
if (vnd) {
snprintk(path, path_len, "bt/mesh/v/%x/%s", mod_key, key);
} else {
snprintk(path, path_len, "bt/mesh/s/%x/%s", mod_key, key);
}
}
static void store_pending_mod_bind(const struct bt_mesh_model *mod, bool vnd)
{
uint16_t keys[CONFIG_BT_MESH_MODEL_KEY_COUNT];
char path[20];
int i, count, err;
for (i = 0, count = 0; i < mod->keys_cnt; i++) {
if (mod->keys[i] != BT_MESH_KEY_UNUSED) {
keys[count++] = mod->keys[i];
LOG_DBG("model key 0x%04x", mod->keys[i]);
}
}
encode_mod_path(mod, vnd, "bind", path, sizeof(path));
if (count) {
err = settings_save_one(path, keys, count * sizeof(keys[0]));
} else {
err = settings_delete(path);
}
if (err) {
LOG_ERR("Failed to store %s value", path);
} else {
LOG_DBG("Stored %s value", path);
}
}
static void store_pending_mod_sub(const struct bt_mesh_model *mod, bool vnd)
{
uint16_t groups[CONFIG_BT_MESH_MODEL_GROUP_COUNT];
char path[20];
int i, count, err;
for (i = 0, count = 0; i < mod->groups_cnt; i++) {
if (mod->groups[i] != BT_MESH_ADDR_UNASSIGNED) {
groups[count++] = mod->groups[i];
}
}
encode_mod_path(mod, vnd, "sub", path, sizeof(path));
if (count) {
err = settings_save_one(path, groups, count * sizeof(groups[0]));
} else {
err = settings_delete(path);
}
if (err) {
LOG_ERR("Failed to store %s value", path);
} else {
LOG_DBG("Stored %s value", path);
}
}
static void store_pending_mod_sub_va(const struct bt_mesh_model *mod, bool vnd)
{
#if CONFIG_BT_MESH_LABEL_COUNT > 0
uint16_t uuidxs[CONFIG_BT_MESH_LABEL_COUNT];
char path[20];
int i, count, err;
for (i = 0, count = 0; i < CONFIG_BT_MESH_LABEL_COUNT; i++) {
if (mod->uuids[i] != NULL) {
err = bt_mesh_va_get_idx_by_uuid(mod->uuids[i], &uuidxs[count]);
if (!err) {
count++;
}
}
}
encode_mod_path(mod, vnd, "subv", path, sizeof(path));
if (count) {
err = settings_save_one(path, uuidxs, count * sizeof(uuidxs[0]));
} else {
err = settings_delete(path);
}
if (err) {
LOG_ERR("Failed to store %s value", path);
} else {
LOG_DBG("Stored %s value", path);
}
#endif /* CONFIG_BT_MESH_LABEL_COUNT > 0 */
}
static void store_pending_mod_pub(const struct bt_mesh_model *mod, bool vnd)
{
struct mod_pub_val pub = {0};
char path[20];
int err;
encode_mod_path(mod, vnd, "pub", path, sizeof(path));
if (!mod->pub || mod->pub->addr == BT_MESH_ADDR_UNASSIGNED) {
err = settings_delete(path);
} else {
pub.base.addr = mod->pub->addr;
pub.base.key = mod->pub->key;
pub.base.ttl = mod->pub->ttl;
pub.base.retransmit = mod->pub->retransmit;
pub.base.period = mod->pub->period;
pub.base.period_div = mod->pub->period_div;
pub.base.cred = mod->pub->cred;
if (BT_MESH_ADDR_IS_VIRTUAL(mod->pub->addr)) {
(void)bt_mesh_va_get_idx_by_uuid(mod->pub->uuid, &pub.uuidx);
}
err = settings_save_one(path, &pub, sizeof(pub));
}
if (err) {
LOG_ERR("Failed to store %s value", path);
} else {
LOG_DBG("Stored %s value", path);
}
}
static void store_pending_mod(const struct bt_mesh_model *mod,
const struct bt_mesh_elem *elem, bool vnd,
bool primary, void *user_data)
{
if (!mod->rt->flags) {
return;
}
if (mod->rt->flags & BT_MESH_MOD_BIND_PENDING) {
mod->rt->flags &= ~BT_MESH_MOD_BIND_PENDING;
store_pending_mod_bind(mod, vnd);
}
if (mod->rt->flags & BT_MESH_MOD_SUB_PENDING) {
mod->rt->flags &= ~BT_MESH_MOD_SUB_PENDING;
store_pending_mod_sub(mod, vnd);
store_pending_mod_sub_va(mod, vnd);
}
if (mod->rt->flags & BT_MESH_MOD_PUB_PENDING) {
mod->rt->flags &= ~BT_MESH_MOD_PUB_PENDING;
store_pending_mod_pub(mod, vnd);
}
if (mod->rt->flags & BT_MESH_MOD_DATA_PENDING) {
mod->rt->flags &= ~BT_MESH_MOD_DATA_PENDING;
mod->cb->pending_store(mod);
}
}
void bt_mesh_model_pending_store(void)
{
bt_mesh_model_foreach(store_pending_mod, NULL);
}
void bt_mesh_model_bind_store(const struct bt_mesh_model *mod)
{
mod->rt->flags |= BT_MESH_MOD_BIND_PENDING;
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING);
}
void bt_mesh_model_sub_store(const struct bt_mesh_model *mod)
{
mod->rt->flags |= BT_MESH_MOD_SUB_PENDING;
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING);
}
void bt_mesh_model_pub_store(const struct bt_mesh_model *mod)
{
mod->rt->flags |= BT_MESH_MOD_PUB_PENDING;
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING);
}
int bt_mesh_comp_data_get_page(struct net_buf_simple *buf, size_t page, size_t offset)
{
if (page == 0 || page == 128) {
return bt_mesh_comp_data_get_page_0(buf, offset);
} else if (IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1) && (page == 1 || page == 129)) {
return bt_mesh_comp_data_get_page_1(buf, offset);
} else if (IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_2) && (page == 2 || page == 130)) {
return bt_mesh_comp_data_get_page_2(buf, offset);
}
return -EINVAL;
}
size_t comp_page_0_size(void)
{
const struct bt_mesh_comp *comp;
const struct bt_mesh_elem *elem;
size_t size = 10; /* Non-variable length params of comp page 0. */
comp = bt_mesh_comp_get();
for (int i = 0; i < comp->elem_count; i++) {
elem = &comp->elem[i];
size += bt_mesh_comp_elem_size(elem);
}
return size;
}
size_t comp_page_1_size(void)
{
const struct bt_mesh_comp *comp;
size_t size = 0;
comp = bt_mesh_comp_get();
for (int i = 0; i < comp->elem_count; i++) {
size += page1_elem_size(&comp->elem[i]);
}
return size;
}
size_t comp_page_2_size(void)
{
size_t size = 0;
if (!dev_comp2) {
LOG_ERR("Composition data P2 not registered");
return size;
}
for (int i = 0; i < dev_comp2->record_cnt; i++) {
size += 8 + dev_comp2->record[i].elem_offset_cnt + dev_comp2->record[i].data_len;
}
return size;
}
size_t bt_mesh_comp_page_size(uint8_t page)
{
if (page == 0 || page == 128) {
return comp_page_0_size();
} else if (IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1) && (page == 1 || page == 129)) {
return comp_page_1_size();
} else if (IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_2) && (page == 2 || page == 130)) {
return comp_page_2_size();
}
return 0;
}
int bt_mesh_comp_store(void)
{
NET_BUF_SIMPLE_DEFINE(buf, CONFIG_BT_MESH_COMP_PST_BUF_SIZE);
int err;
for (int i = 0; i < ARRAY_SIZE(comp_data_pages); i++) {
size_t page_size = bt_mesh_comp_page_size(i);
if (page_size > CONFIG_BT_MESH_COMP_PST_BUF_SIZE) {
LOG_WRN("CDP%d is larger than the CDP persistence buffer. "
"Please increase the CDP persistence buffer size "
"to the required size (%d bytes)",
i, page_size);
}
net_buf_simple_reset(&buf);
err = bt_mesh_comp_data_get_page(&buf, comp_data_pages[i].page, 0);
if (err) {
LOG_ERR("Failed to read CDP%d: %d", comp_data_pages[i].page, err);
return err;
}
err = settings_save_one(comp_data_pages[i].path, buf.data, buf.len);
if (err) {
LOG_ERR("Failed to store CDP%d: %d", comp_data_pages[i].page, err);
return err;
}
LOG_DBG("Stored CDP%d", comp_data_pages[i].page);
}
return 0;
}
int bt_mesh_comp_change_prepare(void)
{
if (!IS_ENABLED(CONFIG_BT_SETTINGS)) {
return -ENOTSUP;
}
return bt_mesh_comp_store();
}
static void comp_data_clear(void)
{
int err;
for (int i = 0; i < ARRAY_SIZE(comp_data_pages); i++) {
err = settings_delete(comp_data_pages[i].path);
if (err) {
LOG_ERR("Failed to clear CDP%d: %d", comp_data_pages[i].page,
err);
}
}
atomic_clear_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY);
}
static int read_comp_cb(const char *key, size_t len, settings_read_cb read_cb,
void *cb_arg, void *param)
{
struct net_buf_simple *buf = param;
if (len > net_buf_simple_tailroom(buf)) {
return -ENOBUFS;
}
len = read_cb(cb_arg, net_buf_simple_tail(buf), len);
if (len > 0) {
net_buf_simple_add(buf, len);
}
return -EALREADY;
}
int bt_mesh_comp_read(struct net_buf_simple *buf, uint8_t page)
{
size_t original_len = buf->len;
int i;
int err;
if (!IS_ENABLED(CONFIG_BT_SETTINGS)) {
return -ENOTSUP;
}
for (i = 0; i < ARRAY_SIZE(comp_data_pages); i++) {
if (comp_data_pages[i].page == page) {
break;
}
}
if (i == ARRAY_SIZE(comp_data_pages)) {
return -ENOENT;
}
err = settings_load_subtree_direct(comp_data_pages[i].path, read_comp_cb, buf);
if (err) {
LOG_ERR("Failed reading composition data: %d", err);
return err;
}
if (buf->len == original_len) {
return -ENOENT;
}
return 0;
}
int bt_mesh_model_data_store(const struct bt_mesh_model *mod, bool vnd,
const char *name, const void *data,
size_t data_len)
{
char path[30];
int err;
encode_mod_path(mod, vnd, "data", path, sizeof(path));
if (name) {
strcat(path, "/");
strncat(path, name, SETTINGS_MAX_DIR_DEPTH);
}
if (data_len) {
err = settings_save_one(path, data, data_len);
} else {
err = settings_delete(path);
}
if (err) {
LOG_ERR("Failed to store %s value", path);
} else {
LOG_DBG("Stored %s value", path);
}
return err;
}
#if defined(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV)
static int metadata_set(const char *name, size_t len_rd, settings_read_cb read_cb, void *cb_arg)
{
/* Only need to know that the entry exists. Will load the contents on
* demand.
*/
if (len_rd > 0) {
atomic_set_bit(bt_mesh.flags, BT_MESH_METADATA_DIRTY);
}
return 0;
}
BT_MESH_SETTINGS_DEFINE(metadata, "metadata", metadata_set);
int bt_mesh_models_metadata_store(void)
{
NET_BUF_SIMPLE_DEFINE(buf, CONFIG_BT_MESH_MODELS_METADATA_PAGE_LEN);
size_t total_size;
int err;
total_size = bt_mesh_metadata_page_0_size();
LOG_DBG("bt/mesh/metadata total %d", total_size);
net_buf_simple_init(&buf, 0);
net_buf_simple_add_le16(&buf, total_size);
err = bt_mesh_metadata_get_page_0(&buf, 0);
if (err == -E2BIG) {
LOG_ERR("Metadata too large");
return err;
}
if (err) {
LOG_ERR("Failed to read models metadata: %d", err);
return err;
}
LOG_DBG("bt/mesh/metadata len %d", buf.len);
err = settings_save_one("bt/mesh/metadata", buf.data, buf.len);
if (err) {
LOG_ERR("Failed to store models metadata: %d", err);
} else {
LOG_DBG("Stored models metadata");
}
return err;
}
int bt_mesh_models_metadata_read(struct net_buf_simple *buf, size_t offset)
{
NET_BUF_SIMPLE_DEFINE(stored_buf, CONFIG_BT_MESH_MODELS_METADATA_PAGE_LEN);
size_t original_len = buf->len;
int err;
if (!IS_ENABLED(CONFIG_BT_SETTINGS)) {
return -ENOTSUP;
}
net_buf_simple_init(&stored_buf, 0);
err = settings_load_subtree_direct("bt/mesh/metadata", read_comp_cb, &stored_buf);
if (err) {
LOG_ERR("Failed reading models metadata: %d", err);
return err;
}
/* First two bytes are total length */
offset += 2;
net_buf_simple_add_mem(buf, &stored_buf.data, MIN(net_buf_simple_tailroom(buf), 2));
if (offset >= stored_buf.len) {
return 0;
}
net_buf_simple_add_mem(buf, &stored_buf.data[offset],
MIN(net_buf_simple_tailroom(buf), stored_buf.len - offset));
LOG_DBG("metadata read %d", buf->len);
if (buf->len == original_len) {
return -ENOENT;
}
return 0;
}
#endif
static void models_metadata_clear(void)
{
int err;
err = settings_delete("bt/mesh/metadata");
if (err) {
LOG_ERR("Failed to clear models metadata: %d", err);
} else {
LOG_DBG("Cleared models metadata");
}
atomic_clear_bit(bt_mesh.flags, BT_MESH_METADATA_DIRTY);
}
void bt_mesh_comp_data_pending_clear(void)
{
comp_data_clear();
models_metadata_clear();
}
void bt_mesh_comp_data_clear(void)
{
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_COMP_PENDING);
}
int bt_mesh_models_metadata_change_prepare(void)
{
#if IS_ENABLED(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV)
return bt_mesh_models_metadata_store();
#else
return -ENOTSUP;
#endif
}
static void commit_mod(const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem,
bool vnd, bool primary, void *user_data)
{
if (mod->pub && mod->pub->update &&
mod->pub->addr != BT_MESH_ADDR_UNASSIGNED) {
int32_t ms = bt_mesh_model_pub_period_get(mod);
if (ms > 0) {
/* Delay the first publication after power-up for longer time (section
* 3.7.3.1):
*
* When the publication of a message is the result of a power-up, a state
* transition progress update, or completion of a state transition, multiple
* nodes may be reporting the state change at the same time. To reduce the
* probability of a message collision, these messages should be sent with a
* random delay between 20 and 500 milliseconds.
*/
uint16_t random;
random = !!mod->pub->delayable ? pub_delay_get(RANDOM_DELAY_LONG) : 0;
LOG_DBG("Starting publish timer (period %u ms, delay %u ms)", ms, random);
k_work_schedule(&mod->pub->timer, K_MSEC(ms + random));
}
}
if (!IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) {
return;
}
for (int i = 0; i < mod->groups_cnt; i++) {
if (mod->groups[i] != BT_MESH_ADDR_UNASSIGNED) {
bt_mesh_lpn_group_add(mod->groups[i]);
}
}
}
void bt_mesh_model_settings_commit(void)
{
bt_mesh_model_foreach(commit_mod, NULL);
}
void bt_mesh_model_data_store_schedule(const struct bt_mesh_model *mod)
{
mod->rt->flags |= BT_MESH_MOD_DATA_PENDING;
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING);
}
uint8_t bt_mesh_comp_parse_page(struct net_buf_simple *buf)
{
uint8_t page = net_buf_simple_pull_u8(buf);
if (page >= 130U && IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_2) &&
(atomic_test_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY) ||
IS_ENABLED(CONFIG_BT_MESH_RPR_SRV))) {
page = 130U;
} else if (page >= 129U && IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1) &&
(atomic_test_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY) ||
IS_ENABLED(CONFIG_BT_MESH_RPR_SRV))) {
page = 129U;
} else if (page >= 128U && (atomic_test_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY) ||
IS_ENABLED(CONFIG_BT_MESH_RPR_SRV))) {
page = 128U;
} else if (page >= 2U && IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_2)) {
page = 2U;
} else if (page >= 1U && IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1)) {
page = 1U;
} else if (page != 0U) {
LOG_DBG("Composition page %u not available", page);
page = 0U;
}
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
}