/* * Copyright (c) 2017 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include "common/bt_str.h" #include "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 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 defined(CONFIG_BT_MESH_COMP_PAGE_1) { "bt/mesh/cmp/1", 1, }, #endif #if defined(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_DBG("Model at 0x%04x is not bound to app idx %d", elem->rt->addr, ctx->app_idx); return ACCESS_STATUS_WRONG_KEY; } if (!model_has_dst(model, ctx->recv_dst, ctx->uuid)) { LOG_DBG("Dst addr 0x%02x is invalid for model at 0x%04x", ctx->recv_dst, elem->rt->addr); 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_mesh_test_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])); 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; } 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); } 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 defined(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 }