This prevents a Configuration Client from binding an app key to a model that only supports DevKey-based security. Signed-off-by: Michał Narajowski <michal.narajowski@codecoup.pl>
1272 lines
27 KiB
C
1272 lines
27 KiB
C
/*
|
|
* 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 "mesh.h"
|
|
#include "adv.h"
|
|
#include "net.h"
|
|
#include "lpn.h"
|
|
#include "transport.h"
|
|
#include "access.h"
|
|
#include "foundation.h"
|
|
#include "settings.h"
|
|
|
|
#define LOG_LEVEL CONFIG_BT_MESH_ACCESS_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(bt_mesh_access);
|
|
|
|
/* Model publication information for persistent storage. */
|
|
struct mod_pub_val {
|
|
uint16_t addr;
|
|
uint16_t key;
|
|
uint8_t ttl;
|
|
uint8_t retransmit;
|
|
uint8_t period;
|
|
uint8_t period_div:4,
|
|
cred:1;
|
|
};
|
|
|
|
static const struct bt_mesh_comp *dev_comp;
|
|
static uint16_t dev_primary_addr;
|
|
static void (*msg_cb)(uint32_t opcode, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf);
|
|
|
|
void bt_mesh_model_foreach(void (*func)(struct bt_mesh_model *mod,
|
|
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++) {
|
|
struct bt_mesh_elem *elem = &dev_comp->elem[i];
|
|
|
|
for (j = 0; j < elem->model_count; j++) {
|
|
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++) {
|
|
struct bt_mesh_model *model = &elem->vnd_models[j];
|
|
|
|
func(model, elem, true, i == 0, user_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
int32_t bt_mesh_model_pub_period_get(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(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");
|
|
/* Return smallest positive number since 0 means disabled */
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
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");
|
|
/* Return smallest positive number since 0 means disabled */
|
|
return 1;
|
|
}
|
|
|
|
return period - elapsed;
|
|
}
|
|
|
|
static void publish_sent(int err, void *user_data)
|
|
{
|
|
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(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)->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, 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, pub->mod);
|
|
return err;
|
|
}
|
|
|
|
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("%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, pub->mod);
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
/* First publication in this period */
|
|
err = pub_period_start(pub);
|
|
if (err) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
err = publish_transmit(pub->mod);
|
|
if (err) {
|
|
LOG_ERR("Failed to publish (err %d)", err);
|
|
publish_sent(err, pub->mod);
|
|
}
|
|
}
|
|
|
|
struct bt_mesh_elem *bt_mesh_model_elem(struct bt_mesh_model *mod)
|
|
{
|
|
return &dev_comp->elem[mod->elem_idx];
|
|
}
|
|
|
|
struct bt_mesh_model *bt_mesh_model_get(bool vnd, uint8_t elem_idx, uint8_t mod_idx)
|
|
{
|
|
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(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(struct bt_mesh_model *mod, 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->elem_idx = elem - dev_comp->elem;
|
|
if (vnd) {
|
|
mod->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->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;
|
|
bt_mesh_model_foreach(mod_init, &err);
|
|
|
|
return err;
|
|
}
|
|
|
|
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++) {
|
|
struct bt_mesh_elem *elem = &dev_comp->elem[i];
|
|
|
|
elem->addr = addr++;
|
|
|
|
LOG_DBG("addr 0x%04x mod_count %u vnd_mod_count %u", elem->addr, elem->model_count,
|
|
elem->vnd_model_count);
|
|
}
|
|
}
|
|
|
|
void bt_mesh_comp_unprovision(void)
|
|
{
|
|
LOG_DBG("");
|
|
|
|
dev_primary_addr = BT_MESH_ADDR_UNASSIGNED;
|
|
}
|
|
|
|
uint16_t bt_mesh_primary_addr(void)
|
|
{
|
|
return dev_primary_addr;
|
|
}
|
|
|
|
static uint16_t *model_group_get(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;
|
|
struct bt_mesh_model *mod;
|
|
uint16_t addr;
|
|
};
|
|
|
|
static enum bt_mesh_walk find_group_mod_visitor(struct bt_mesh_model *mod, void *user_data)
|
|
{
|
|
struct find_group_visitor_ctx *ctx = user_data;
|
|
|
|
if (mod->elem_idx != ctx->mod->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(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;
|
|
}
|
|
|
|
static struct bt_mesh_model *bt_mesh_elem_find_group(struct bt_mesh_elem *elem,
|
|
uint16_t group_addr)
|
|
{
|
|
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;
|
|
}
|
|
|
|
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].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++) {
|
|
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(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(struct bt_mesh_model *mod, uint16_t dst)
|
|
{
|
|
if (BT_MESH_ADDR_IS_UNICAST(dst)) {
|
|
return (dev_comp->elem[mod->elem_idx].addr == dst);
|
|
} else if (BT_MESH_ADDR_IS_GROUP(dst) || BT_MESH_ADDR_IS_VIRTUAL(dst) ||
|
|
(BT_MESH_ADDR_IS_FIXED_GROUP(dst) && mod->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->elem_idx == 0;
|
|
}
|
|
|
|
static const struct bt_mesh_model_op *find_op(struct bt_mesh_elem *elem,
|
|
uint32_t opcode, 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;
|
|
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;
|
|
}
|
|
|
|
void bt_mesh_model_recv(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_model *model;
|
|
const struct bt_mesh_model_op *op;
|
|
uint32_t opcode;
|
|
int i;
|
|
|
|
LOG_DBG("app_idx 0x%04x src 0x%04x dst 0x%04x", rx->ctx.app_idx, rx->ctx.addr,
|
|
rx->ctx.recv_dst);
|
|
LOG_DBG("len %u: %s", buf->len, bt_hex(buf->data, buf->len));
|
|
|
|
if (get_opcode(buf, &opcode) < 0) {
|
|
LOG_WRN("Unable to decode OpCode");
|
|
return;
|
|
}
|
|
|
|
LOG_DBG("OpCode 0x%08x", opcode);
|
|
|
|
for (i = 0; i < dev_comp->elem_count; i++) {
|
|
struct net_buf_simple_state state;
|
|
|
|
op = find_op(&dev_comp->elem[i], opcode, &model);
|
|
if (!op) {
|
|
LOG_DBG("No OpCode 0x%08x for elem %d", opcode, i);
|
|
continue;
|
|
}
|
|
|
|
if (!bt_mesh_model_has_key(model, rx->ctx.app_idx)) {
|
|
continue;
|
|
}
|
|
|
|
if (!model_has_dst(model, rx->ctx.recv_dst)) {
|
|
continue;
|
|
}
|
|
|
|
if ((op->len >= 0) && (buf->len < (size_t)op->len)) {
|
|
LOG_ERR("Too short message for OpCode 0x%08x", opcode);
|
|
continue;
|
|
} else if ((op->len < 0) && (buf->len != (size_t)(-op->len))) {
|
|
LOG_ERR("Invalid message size for OpCode 0x%08x", opcode);
|
|
continue;
|
|
}
|
|
|
|
/* The callback will likely parse the buffer, so
|
|
* store the parsing state in case multiple models
|
|
* receive the message.
|
|
*/
|
|
net_buf_simple_save(buf, &state);
|
|
(void)op->func(model, &rx->ctx, buf);
|
|
net_buf_simple_restore(buf, &state);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_MESH_ACCESS_LAYER_MSG) && msg_cb) {
|
|
msg_cb(opcode, &rx->ctx, buf);
|
|
}
|
|
}
|
|
|
|
int bt_mesh_model_send(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 (!bt_mesh_model_has_key(model, ctx->app_idx)) {
|
|
LOG_ERR("Model not bound to AppKey 0x%04x", ctx->app_idx);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return bt_mesh_access_send(ctx, msg, bt_mesh_model_elem(model)->addr, cb, cb_data);
|
|
}
|
|
|
|
int bt_mesh_model_publish(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));
|
|
|
|
k_work_reschedule(&pub->timer, K_NO_WAIT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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(struct bt_mesh_model *model,
|
|
enum bt_mesh_walk (*cb)(struct bt_mesh_model *mod,
|
|
void *user_data),
|
|
void *user_data)
|
|
{
|
|
#ifndef CONFIG_BT_MESH_MODEL_EXTENSIONS
|
|
(void)cb(model, user_data);
|
|
return;
|
|
#else
|
|
struct bt_mesh_model *it;
|
|
|
|
if (cb(model, user_data) == BT_MESH_WALK_STOP || !model->next) {
|
|
return;
|
|
}
|
|
|
|
/* List is circular. Step through all models until we reach the start: */
|
|
for (it = model->next; it != model; it = it->next) {
|
|
if (cb(it, user_data) == BT_MESH_WALK_STOP) {
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_BT_MESH_MODEL_EXTENSIONS
|
|
int bt_mesh_model_extend(struct bt_mesh_model *extending_mod, struct bt_mesh_model *base_mod)
|
|
{
|
|
struct bt_mesh_model *a = extending_mod;
|
|
struct bt_mesh_model *b = base_mod;
|
|
struct bt_mesh_model *a_next = a->next;
|
|
struct bt_mesh_model *b_next = b->next;
|
|
struct bt_mesh_model *it;
|
|
|
|
base_mod->flags |= BT_MESH_MOD_EXTENDED;
|
|
|
|
if (a == b) {
|
|
return 0;
|
|
}
|
|
|
|
/* Check if a's list contains b */
|
|
for (it = a; (it != NULL) && (it->next != a); it = it->next) {
|
|
if (it == b) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Merge lists */
|
|
if (a_next) {
|
|
b->next = a_next;
|
|
} else {
|
|
b->next = a;
|
|
}
|
|
|
|
if (b_next) {
|
|
a->next = b_next;
|
|
} else {
|
|
a->next = b;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
bool bt_mesh_model_is_extended(struct bt_mesh_model *model)
|
|
{
|
|
return model->flags & BT_MESH_MOD_EXTENDED;
|
|
}
|
|
|
|
static int mod_set_bind(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(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_pub(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;
|
|
|
|
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;
|
|
}
|
|
|
|
mod->pub->addr = pub.addr;
|
|
mod->pub->key = pub.key;
|
|
mod->pub->cred = pub.cred;
|
|
mod->pub->ttl = pub.ttl;
|
|
mod->pub->period = pub.period;
|
|
mod->pub->retransmit = pub.retransmit;
|
|
mod->pub->period_div = pub.period_div;
|
|
mod->pub->count = 0U;
|
|
|
|
LOG_DBG("Restored model publication, dst 0x%04x app_idx 0x%03x", pub.addr, pub.key);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mod_data_set(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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (!strncmp(next, "bind", len)) {
|
|
return mod_set_bind(mod, len_rd, read_cb, cb_arg);
|
|
}
|
|
|
|
if (!strncmp(next, "sub", len)) {
|
|
return mod_set_sub(mod, len_rd, read_cb, cb_arg);
|
|
}
|
|
|
|
if (!strncmp(next, "pub", len)) {
|
|
return mod_set_pub(mod, len_rd, read_cb, cb_arg);
|
|
}
|
|
|
|
if (!strncmp(next, "data", len)) {
|
|
return mod_data_set(mod, next, len_rd, read_cb, cb_arg);
|
|
}
|
|
|
|
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 void encode_mod_path(struct bt_mesh_model *mod, bool vnd,
|
|
const char *key, char *path, size_t path_len)
|
|
{
|
|
uint16_t mod_key = (((uint16_t)mod->elem_idx << 8) | mod->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(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(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_pub(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.addr = mod->pub->addr;
|
|
pub.key = mod->pub->key;
|
|
pub.ttl = mod->pub->ttl;
|
|
pub.retransmit = mod->pub->retransmit;
|
|
pub.period = mod->pub->period;
|
|
pub.period_div = mod->pub->period_div;
|
|
pub.cred = mod->pub->cred;
|
|
|
|
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(struct bt_mesh_model *mod,
|
|
struct bt_mesh_elem *elem, bool vnd,
|
|
bool primary, void *user_data)
|
|
{
|
|
if (!mod->flags) {
|
|
return;
|
|
}
|
|
|
|
if (mod->flags & BT_MESH_MOD_BIND_PENDING) {
|
|
mod->flags &= ~BT_MESH_MOD_BIND_PENDING;
|
|
store_pending_mod_bind(mod, vnd);
|
|
}
|
|
|
|
if (mod->flags & BT_MESH_MOD_SUB_PENDING) {
|
|
mod->flags &= ~BT_MESH_MOD_SUB_PENDING;
|
|
store_pending_mod_sub(mod, vnd);
|
|
}
|
|
|
|
if (mod->flags & BT_MESH_MOD_PUB_PENDING) {
|
|
mod->flags &= ~BT_MESH_MOD_PUB_PENDING;
|
|
store_pending_mod_pub(mod, vnd);
|
|
}
|
|
}
|
|
|
|
void bt_mesh_model_pending_store(void)
|
|
{
|
|
bt_mesh_model_foreach(store_pending_mod, NULL);
|
|
}
|
|
|
|
void bt_mesh_model_bind_store(struct bt_mesh_model *mod)
|
|
{
|
|
mod->flags |= BT_MESH_MOD_BIND_PENDING;
|
|
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING);
|
|
}
|
|
|
|
void bt_mesh_model_sub_store(struct bt_mesh_model *mod)
|
|
{
|
|
mod->flags |= BT_MESH_MOD_SUB_PENDING;
|
|
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING);
|
|
}
|
|
|
|
void bt_mesh_model_pub_store(struct bt_mesh_model *mod)
|
|
{
|
|
mod->flags |= BT_MESH_MOD_PUB_PENDING;
|
|
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING);
|
|
}
|
|
|
|
int bt_mesh_model_data_store(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;
|
|
}
|
|
|
|
static void commit_mod(struct bt_mesh_model *mod, 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) {
|
|
LOG_DBG("Starting publish timer (period %u ms)", ms);
|
|
k_work_schedule(&mod->pub->timer, K_MSEC(ms));
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|