| /* |
| * Copyright (c) 2024 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/bluetooth/mesh.h> |
| #include <zephyr/bluetooth/mesh/brg_cfg.h> |
| #include "access.h" |
| #include "brg_cfg.h" |
| #include "foundation.h" |
| #include "subnet.h" |
| |
| #define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(bt_mesh_brg_cfg_srv); |
| |
| static void bridge_status_send(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx) |
| { |
| BT_MESH_MODEL_BUF_DEFINE(msg, OP_SUBNET_BRIDGE_STATUS, 1); |
| |
| bt_mesh_model_msg_init(&msg, OP_SUBNET_BRIDGE_STATUS); |
| net_buf_simple_add_u8(&msg, bt_mesh_brg_cfg_enable_get() ? 1 : 0); |
| |
| if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { |
| LOG_ERR("Brg Status send failed"); |
| } |
| } |
| |
| static int subnet_bridge_get(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| bridge_status_send(model, ctx); |
| |
| return 0; |
| } |
| |
| static int subnet_bridge_set(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| uint8_t enable = net_buf_simple_pull_u8(buf); |
| |
| if (enable > BT_MESH_SUBNET_BRIDGE_ENABLED) { |
| return -EINVAL; |
| } |
| |
| bt_mesh_brg_cfg_enable_set(enable); |
| bridge_status_send(model, ctx); |
| |
| return 0; |
| } |
| |
| static void bridging_table_status_send(const struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, uint8_t status, |
| struct bt_mesh_bridging_table_entry *entry) |
| { |
| BT_MESH_MODEL_BUF_DEFINE(msg, OP_BRIDGING_TABLE_STATUS, 9); |
| |
| bt_mesh_model_msg_init(&msg, OP_BRIDGING_TABLE_STATUS); |
| net_buf_simple_add_u8(&msg, status); |
| net_buf_simple_add_u8(&msg, entry->directions); |
| key_idx_pack_pair(&msg, entry->net_idx1, entry->net_idx2); |
| net_buf_simple_add_le16(&msg, entry->addr1); |
| net_buf_simple_add_le16(&msg, entry->addr2); |
| |
| if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { |
| LOG_ERR("Brg Tbl Status send failed"); |
| } |
| } |
| |
| static bool netkey_check(uint16_t net_idx1, uint16_t net_idx2) |
| { |
| return bt_mesh_subnet_get(net_idx1) && bt_mesh_subnet_get(net_idx2); |
| } |
| |
| static int bridging_table_add(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct bt_mesh_bridging_table_entry entry; |
| uint8_t status = STATUS_SUCCESS; |
| |
| entry.directions = net_buf_simple_pull_u8(buf); |
| key_idx_unpack_pair(buf, &entry.net_idx1, &entry.net_idx2); |
| entry.addr1 = net_buf_simple_pull_le16(buf); |
| entry.addr2 = net_buf_simple_pull_le16(buf); |
| |
| if (!netkey_check(entry.net_idx1, entry.net_idx2)) { |
| status = STATUS_INVALID_NETKEY; |
| goto add_respond; |
| } |
| |
| int err = bt_mesh_brg_cfg_tbl_add(entry.directions, entry.net_idx1, entry.net_idx2, |
| entry.addr1, entry.addr2); |
| if (err == -ENOMEM) { |
| status = STATUS_INSUFF_RESOURCES; |
| } else if (err) { |
| /* Per spec, do not respond if parameters values are invalid. */ |
| return err; |
| } |
| |
| add_respond: |
| |
| bridging_table_status_send(model, ctx, status, &entry); |
| |
| return 0; |
| } |
| |
| static int bridging_table_remove(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct bt_mesh_bridging_table_entry entry; |
| uint8_t status = STATUS_SUCCESS; |
| |
| entry.directions = 0; |
| key_idx_unpack_pair(buf, &entry.net_idx1, &entry.net_idx2); |
| entry.addr1 = net_buf_simple_pull_le16(buf); |
| entry.addr2 = net_buf_simple_pull_le16(buf); |
| |
| if (!netkey_check(entry.net_idx1, entry.net_idx2)) { |
| status = STATUS_INVALID_NETKEY; |
| goto rmv_respond; |
| } |
| |
| int err = bt_mesh_brg_cfg_tbl_remove(entry.net_idx1, entry.net_idx2, entry.addr1, |
| entry.addr2); |
| |
| /* Per spec, do not respond if parameters values are invalid. */ |
| if (err) { |
| return err; |
| } |
| |
| rmv_respond: |
| |
| bridging_table_status_send(model, ctx, status, &entry); |
| |
| return 0; |
| } |
| |
| static int bridged_subnets_get(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| BT_MESH_MODEL_BUF_DEFINE(msg, OP_BRIDGED_SUBNETS_LIST, |
| BT_MESH_TX_SDU_MAX - BT_MESH_MODEL_OP_LEN(OP_BRIDGED_SUBNETS_LIST)); |
| bt_mesh_model_msg_init(&msg, OP_BRIDGED_SUBNETS_LIST); |
| |
| const struct bt_mesh_brg_cfg_row *brg_tbl; |
| int rows = bt_mesh_brg_cfg_tbl_get(&brg_tbl); |
| int16_t net_idx_filter = net_buf_simple_pull_le16(buf); |
| |
| if (net_idx_filter & BT_MESH_BRG_CFG_NKEY_PRHB_FLT_MASK) { |
| return -EINVAL; |
| } |
| |
| struct bt_mesh_filter_netkey filter_net_idx; |
| |
| filter_net_idx.filter = net_idx_filter & BIT_MASK(2); |
| filter_net_idx.net_idx = (net_idx_filter >> 4) & BIT_MASK(12); |
| |
| uint8_t start_id = net_buf_simple_pull_u8(buf); |
| |
| net_buf_simple_add_le16(&msg, net_idx_filter); |
| net_buf_simple_add_u8(&msg, start_id); |
| |
| uint8_t cnt = 0; |
| uint16_t net_idx1, net_idx2; |
| |
| for (int i = 0; i < rows; i++) { |
| net_idx1 = brg_tbl[i].net_idx1; |
| net_idx2 = brg_tbl[i].net_idx2; |
| |
| if (net_buf_simple_tailroom(&msg) < 3 + BT_MESH_MIC_SHORT) { |
| break; |
| } |
| |
| switch (filter_net_idx.filter) { |
| /* Report pair of NetKeys from the table, starting from start_id. */ |
| case 0: |
| if (i >= start_id) { |
| key_idx_pack_pair(&msg, net_idx1, net_idx2); |
| } |
| break; |
| |
| /* Report pair of NetKeys in which (NetKeyIndex1) matches the net_idx */ |
| case 1: |
| if (net_idx1 == filter_net_idx.net_idx) { |
| if (cnt >= start_id) { |
| key_idx_pack_pair(&msg, net_idx1, net_idx2); |
| } |
| cnt++; |
| } |
| break; |
| |
| /* Report pair of NetKeys in which (NetKeyIndex2) matches the net_idx */ |
| case 2: |
| if (net_idx2 == filter_net_idx.net_idx) { |
| if (cnt >= start_id) { |
| key_idx_pack_pair(&msg, net_idx1, net_idx2); |
| } |
| cnt++; |
| } |
| break; |
| |
| /* Report pair of NetKeys in which (NetKeyIndex1 or NetKeyIndex2) matches the |
| * net_idx |
| */ |
| case 3: |
| if (net_idx1 == filter_net_idx.net_idx || |
| net_idx2 == filter_net_idx.net_idx) { |
| if (cnt >= start_id) { |
| key_idx_pack_pair(&msg, net_idx1, net_idx2); |
| } |
| cnt++; |
| } |
| break; |
| |
| default: |
| CODE_UNREACHABLE; |
| } |
| } |
| |
| if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { |
| LOG_ERR("Brg Subnet List send failed"); |
| } |
| |
| return 0; |
| } |
| |
| static int bridging_table_get(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| BT_MESH_MODEL_BUF_DEFINE(msg, OP_BRIDGING_TABLE_LIST, |
| BT_MESH_TX_SDU_MAX - BT_MESH_MODEL_OP_LEN(OP_BRIDGING_TABLE_LIST)); |
| uint8_t status = STATUS_SUCCESS; |
| uint16_t net_idx1, net_idx2; |
| |
| bt_mesh_model_msg_init(&msg, OP_BRIDGING_TABLE_LIST); |
| |
| key_idx_unpack_pair(buf, &net_idx1, &net_idx2); |
| |
| uint16_t start_id = net_buf_simple_pull_le16(buf); |
| |
| if (!netkey_check(net_idx1, net_idx2)) { |
| status = STATUS_INVALID_NETKEY; |
| } |
| |
| net_buf_simple_add_u8(&msg, status); |
| key_idx_pack_pair(&msg, net_idx1, net_idx2); |
| net_buf_simple_add_le16(&msg, start_id); |
| |
| if (status != STATUS_SUCCESS) { |
| goto tbl_get_respond; |
| } |
| |
| int cnt = 0; |
| const struct bt_mesh_brg_cfg_row *brg_tbl; |
| int rows = bt_mesh_brg_cfg_tbl_get(&brg_tbl); |
| |
| for (int i = 0; i < rows; i++) { |
| if (brg_tbl[i].net_idx1 == net_idx1 && brg_tbl[i].net_idx2 == net_idx2) { |
| if (cnt >= start_id) { |
| if (net_buf_simple_tailroom(&msg) < 5 + BT_MESH_MIC_SHORT) { |
| LOG_WRN("Bridging Table List message too large"); |
| break; |
| } |
| |
| net_buf_simple_add_le16(&msg, brg_tbl[i].addr1); |
| net_buf_simple_add_le16(&msg, brg_tbl[i].addr2); |
| net_buf_simple_add_u8(&msg, brg_tbl[i].direction); |
| } |
| cnt++; |
| } |
| |
| } |
| |
| tbl_get_respond: |
| if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { |
| LOG_ERR("Brg Tbl List send failed"); |
| } |
| |
| return 0; |
| } |
| |
| static int bridging_table_size_get(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| BT_MESH_MODEL_BUF_DEFINE(msg, OP_BRIDGING_TABLE_SIZE_STATUS, 2); |
| bt_mesh_model_msg_init(&msg, OP_BRIDGING_TABLE_SIZE_STATUS); |
| |
| net_buf_simple_add_le16(&msg, CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX); |
| |
| if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { |
| LOG_ERR("Brg Tbl Size Status send failed"); |
| } |
| |
| return 0; |
| } |
| |
| |
| const struct bt_mesh_model_op _bt_mesh_brg_cfg_srv_op[] = { |
| { OP_SUBNET_BRIDGE_GET, BT_MESH_LEN_EXACT(0), subnet_bridge_get }, |
| { OP_SUBNET_BRIDGE_SET, BT_MESH_LEN_EXACT(1), subnet_bridge_set }, |
| { OP_BRIDGING_TABLE_ADD, BT_MESH_LEN_EXACT(8), bridging_table_add }, |
| { OP_BRIDGING_TABLE_REMOVE, BT_MESH_LEN_EXACT(7), bridging_table_remove }, |
| { OP_BRIDGED_SUBNETS_GET, BT_MESH_LEN_EXACT(3), bridged_subnets_get }, |
| { OP_BRIDGING_TABLE_GET, BT_MESH_LEN_EXACT(5), bridging_table_get }, |
| { OP_BRIDGING_TABLE_SIZE_GET, BT_MESH_LEN_EXACT(0), bridging_table_size_get }, |
| BT_MESH_MODEL_OP_END, |
| }; |
| |
| static int brg_cfg_srv_init(const struct bt_mesh_model *model) |
| { |
| const struct bt_mesh_model *config_srv = |
| bt_mesh_model_find(bt_mesh_model_elem(model), BT_MESH_MODEL_ID_CFG_SRV); |
| |
| if (config_srv == NULL) { |
| LOG_ERR("Not on primary element"); |
| return -EINVAL; |
| } |
| |
| /* |
| * Bridge Configuration Server model security is device key based and only the local |
| * device key is allowed to access this model. |
| */ |
| model->keys[0] = BT_MESH_KEY_DEV_LOCAL; |
| model->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; |
| |
| bt_mesh_model_extend(model, config_srv); |
| |
| return 0; |
| } |
| |
| void brg_cfg_srv_reset(const struct bt_mesh_model *model) |
| { |
| bt_mesh_brg_cfg_tbl_reset(); |
| } |
| |
| const struct bt_mesh_model_cb _bt_mesh_brg_cfg_srv_cb = { |
| .init = brg_cfg_srv_init, |
| .reset = brg_cfg_srv_reset, |
| }; |