| /* |
| * Copyright (c) 2024 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /* Implementation for states of Subnet Bridge feature in Bluetooth Mesh Protocol v1.1 |
| * specification |
| */ |
| #include <errno.h> |
| #include <zephyr/bluetooth/mesh.h> |
| |
| #include "mesh.h" |
| #include "net.h" |
| #include "settings.h" |
| #include "brg_cfg.h" |
| |
| #define LOG_LEVEL CONFIG_BT_MESH_BRG_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(bt_mesh_brg_cfg); |
| |
| /* Bridging table state and counter */ |
| static struct bt_mesh_brg_cfg_row brg_tbl[CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX]; |
| static uint32_t bt_mesh_brg_cfg_row_cnt; |
| /* Bridging enabled state */ |
| static bool brg_enabled; |
| |
| enum { |
| STATE_UPDATED, |
| TABLE_UPDATED, |
| BRG_CFG_FLAGS_COUNT, |
| }; |
| static ATOMIC_DEFINE(brg_cfg_flags, BRG_CFG_FLAGS_COUNT); |
| |
| static void brg_tbl_compact(void) |
| { |
| int j = 0; |
| |
| for (int k = 0; k < bt_mesh_brg_cfg_row_cnt; k++) { |
| if (brg_tbl[k].direction != 0) { |
| brg_tbl[j] = brg_tbl[k]; |
| j++; |
| } |
| } |
| memset(&brg_tbl[j], 0, sizeof(brg_tbl[j])); |
| bt_mesh_brg_cfg_row_cnt--; |
| } |
| |
| /* Set function for initializing bridging enable state from value stored in settings. */ |
| static int brg_en_set(const char *name, size_t len_rd, settings_read_cb read_cb, void *cb_arg) |
| { |
| int err; |
| |
| if (len_rd == 0) { |
| brg_enabled = 0; |
| LOG_DBG("Cleared bridge enable state"); |
| return 0; |
| } |
| |
| err = bt_mesh_settings_set(read_cb, cb_arg, &brg_enabled, sizeof(brg_enabled)); |
| if (err) { |
| LOG_ERR("Failed to set bridge enable state"); |
| return err; |
| } |
| |
| LOG_DBG("Restored bridge enable state"); |
| |
| return 0; |
| } |
| |
| /* Define a setting for storing enable state */ |
| BT_MESH_SETTINGS_DEFINE(brg_en, "brg_en", brg_en_set); |
| |
| /* Set function for initializing bridging table rows from values stored in settings. */ |
| static int brg_tbl_set(const char *name, size_t len_rd, settings_read_cb read_cb, void *cb_arg) |
| { |
| ssize_t len; |
| |
| if (len_rd == 0) { |
| memset(brg_tbl, 0, sizeof(brg_tbl)); |
| bt_mesh_brg_cfg_row_cnt = 0; |
| LOG_DBG("Cleared bridging table entries"); |
| return 0; |
| } |
| |
| if (len_rd % sizeof(brg_tbl[0])) { |
| LOG_ERR("Invalid data size"); |
| return -EINVAL; |
| } |
| |
| if (len_rd > sizeof(brg_tbl)) { |
| LOG_ERR("Too many entries to fit in bridging table"); |
| return -ENOMEM; |
| } |
| |
| len = read_cb(cb_arg, brg_tbl, sizeof(brg_tbl)); |
| if (len < 0 || len % sizeof(brg_tbl[0])) { |
| LOG_ERR("Failed to read bridging table entries (err %zd)", len); |
| return len < 0 ? len : -EINVAL; |
| } |
| |
| bt_mesh_brg_cfg_row_cnt = len / sizeof(brg_tbl[0]); |
| LOG_DBG("Restored %d entries in bridging table", bt_mesh_brg_cfg_row_cnt); |
| |
| return 0; |
| } |
| |
| /* Define a setting for storing briging table rows */ |
| BT_MESH_SETTINGS_DEFINE(brg_tbl, "brg_tbl", brg_tbl_set); |
| |
| bool bt_mesh_brg_cfg_enable_get(void) |
| { |
| return brg_enabled; |
| } |
| |
| int bt_mesh_brg_cfg_enable_set(bool enable) |
| { |
| if (brg_enabled == enable) { |
| return 0; |
| } |
| |
| brg_enabled = enable; |
| |
| if (IS_ENABLED(CONFIG_BT_SETTINGS)) { |
| atomic_set_bit(brg_cfg_flags, STATE_UPDATED); |
| bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING); |
| } |
| |
| return 0; |
| } |
| |
| void bt_mesh_brg_cfg_pending_store(void) |
| { |
| char *path_en = "bt/mesh/brg_en"; |
| char *path_tbl = "bt/mesh/brg_tbl"; |
| int err; |
| |
| if (atomic_test_and_clear_bit(brg_cfg_flags, STATE_UPDATED)) { |
| if (brg_enabled) { |
| err = settings_save_one(path_en, &brg_enabled, sizeof(brg_enabled)); |
| } else { |
| err = settings_delete(path_en); |
| } |
| |
| if (err) { |
| LOG_ERR("Failed to store %s value", path_en); |
| } |
| } |
| |
| if (atomic_test_and_clear_bit(brg_cfg_flags, TABLE_UPDATED)) { |
| if (bt_mesh_brg_cfg_row_cnt) { |
| err = settings_save_one(path_tbl, &brg_tbl, |
| bt_mesh_brg_cfg_row_cnt * sizeof(brg_tbl[0])); |
| } else { |
| err = settings_delete(path_tbl); |
| } |
| |
| if (err) { |
| LOG_ERR("Failed to store %s value", path_tbl); |
| } |
| } |
| } |
| |
| /* Remove the entry from the bridging table that corresponds with the NetKey Index of the removed |
| * subnet. |
| */ |
| static void brg_tbl_netkey_removed_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt) |
| { |
| if (evt != BT_MESH_KEY_DELETED) { |
| return; |
| } |
| |
| for (int i = 0; i < CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX; i++) { |
| if (brg_tbl[i].direction && ( |
| brg_tbl[i].net_idx1 == sub->net_idx || |
| brg_tbl[i].net_idx2 == sub->net_idx)) { |
| memset(&brg_tbl[i], 0, sizeof(brg_tbl[i])); |
| brg_tbl_compact(); |
| } |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_SETTINGS)) { |
| atomic_set_bit(brg_cfg_flags, TABLE_UPDATED); |
| bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING); |
| } |
| } |
| |
| /* Add event hook for key deletion event */ |
| BT_MESH_SUBNET_CB_DEFINE(sbr) = { |
| .evt_handler = brg_tbl_netkey_removed_evt, |
| }; |
| |
| int bt_mesh_brg_cfg_tbl_reset(void) |
| { |
| int err = 0; |
| |
| brg_enabled = false; |
| bt_mesh_brg_cfg_row_cnt = 0; |
| memset(brg_tbl, 0, sizeof(brg_tbl)); |
| atomic_clear(brg_cfg_flags); |
| |
| if (!IS_ENABLED(CONFIG_BT_SETTINGS)) { |
| return 0; |
| } |
| |
| err = settings_delete("bt/mesh/brg_en"); |
| |
| if (err) { |
| return err; |
| } |
| |
| err = settings_delete("bt/mesh/brg_tbl"); |
| return err; |
| } |
| |
| int bt_mesh_brg_cfg_tbl_get(const struct bt_mesh_brg_cfg_row **rows) |
| { |
| *rows = brg_tbl; |
| return bt_mesh_brg_cfg_row_cnt; |
| } |
| |
| int bt_mesh_brg_cfg_tbl_add(enum bt_mesh_brg_cfg_dir direction, uint16_t net_idx1, |
| uint16_t net_idx2, uint16_t addr1, uint16_t addr2) |
| { |
| /* Sanity checks */ |
| if (!BT_MESH_ADDR_IS_UNICAST(addr1) || net_idx1 == net_idx2 || addr1 == addr2 || |
| net_idx1 > BT_MESH_BRG_CFG_KEY_INDEX_MAX || net_idx2 > BT_MESH_BRG_CFG_KEY_INDEX_MAX) { |
| return -EINVAL; |
| } |
| |
| if (direction != BT_MESH_BRG_CFG_DIR_ONEWAY && direction != BT_MESH_BRG_CFG_DIR_TWOWAY) { |
| return -EINVAL; |
| } |
| |
| if ((direction == BT_MESH_BRG_CFG_DIR_ONEWAY && |
| (addr2 == BT_MESH_ADDR_UNASSIGNED || addr2 == BT_MESH_ADDR_ALL_NODES)) || |
| (direction == BT_MESH_BRG_CFG_DIR_TWOWAY && |
| !BT_MESH_ADDR_IS_UNICAST(addr2))) { |
| return -EINVAL; |
| } |
| |
| /* Check if entry already exists, if yes, then, update the direction field and it is a |
| * success. |
| * "If a Bridging Table state entry corresponding to the received message exists, the |
| * element shall set the Directions field in the entry to the value of the Directions field |
| * in the received message." |
| */ |
| for (int i = 0; i < bt_mesh_brg_cfg_row_cnt; i++) { |
| if (brg_tbl[i].net_idx1 == net_idx1 && |
| brg_tbl[i].net_idx2 == net_idx2 && brg_tbl[i].addr1 == addr1 && |
| brg_tbl[i].addr2 == addr2) { |
| brg_tbl[i].direction = direction; |
| goto store; |
| } |
| } |
| |
| /* Empty element, is the current table row counter */ |
| if (bt_mesh_brg_cfg_row_cnt == CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX) { |
| return -ENOMEM; |
| } |
| |
| /* Update the row */ |
| brg_tbl[bt_mesh_brg_cfg_row_cnt].direction = direction; |
| brg_tbl[bt_mesh_brg_cfg_row_cnt].net_idx1 = net_idx1; |
| brg_tbl[bt_mesh_brg_cfg_row_cnt].net_idx2 = net_idx2; |
| brg_tbl[bt_mesh_brg_cfg_row_cnt].addr1 = addr1; |
| brg_tbl[bt_mesh_brg_cfg_row_cnt].addr2 = addr2; |
| bt_mesh_brg_cfg_row_cnt++; |
| |
| store: |
| if (IS_ENABLED(CONFIG_BT_SETTINGS)) { |
| atomic_set_bit(brg_cfg_flags, TABLE_UPDATED); |
| bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING); |
| } |
| |
| return 0; |
| } |
| |
| void bt_mesh_brg_cfg_tbl_foreach_subnet(uint16_t src, uint16_t dst, uint16_t net_idx, |
| bt_mesh_brg_cfg_cb_t cb, void *user_data) |
| { |
| for (int i = 0; i < bt_mesh_brg_cfg_row_cnt; i++) { |
| if ((brg_tbl[i].direction == BT_MESH_BRG_CFG_DIR_ONEWAY || |
| brg_tbl[i].direction == BT_MESH_BRG_CFG_DIR_TWOWAY) && |
| brg_tbl[i].net_idx1 == net_idx && brg_tbl[i].addr1 == src && |
| brg_tbl[i].addr2 == dst) { |
| cb(brg_tbl[i].net_idx2, user_data); |
| } else if ((brg_tbl[i].direction == BT_MESH_BRG_CFG_DIR_TWOWAY && |
| brg_tbl[i].net_idx2 == net_idx && brg_tbl[i].addr2 == src && |
| brg_tbl[i].addr1 == dst)) { |
| cb(brg_tbl[i].net_idx1, user_data); |
| } |
| } |
| } |
| |
| int bt_mesh_brg_cfg_tbl_remove(uint16_t net_idx1, uint16_t net_idx2, uint16_t addr1, |
| uint16_t addr2) |
| { |
| bool store = false; |
| |
| /* Sanity checks */ |
| if ((!BT_MESH_ADDR_IS_UNICAST(addr1) && addr1 != BT_MESH_ADDR_UNASSIGNED) || |
| addr2 == BT_MESH_ADDR_ALL_NODES) { |
| return -EINVAL; |
| } |
| |
| if (net_idx1 == net_idx2 || net_idx1 > BT_MESH_BRG_CFG_KEY_INDEX_MAX || |
| net_idx2 > BT_MESH_BRG_CFG_KEY_INDEX_MAX) { |
| return -EINVAL; |
| } |
| |
| |
| /* Iterate over items and set matching row to 0, if nothing exist, or nothing matches, then |
| * it is success (similar to add) |
| */ |
| if (bt_mesh_brg_cfg_row_cnt == 0) { |
| return 0; |
| } |
| |
| for (int i = 0; i < bt_mesh_brg_cfg_row_cnt; i++) { |
| /* Match according to remove behavior in Section 4.4.9.2.2 of MshPRT_v1.1 */ |
| if (brg_tbl[i].direction) { |
| if (!(brg_tbl[i].net_idx1 == net_idx1 && brg_tbl[i].net_idx2 == net_idx2)) { |
| continue; |
| } |
| |
| if ((brg_tbl[i].addr1 == addr1 && brg_tbl[i].addr2 == addr2) || |
| (addr2 == BT_MESH_ADDR_UNASSIGNED && brg_tbl[i].addr1 == addr1) || |
| (addr1 == BT_MESH_ADDR_UNASSIGNED && brg_tbl[i].addr2 == addr2)) { |
| memset(&brg_tbl[i], 0, sizeof(brg_tbl[i])); |
| store = true; |
| } |
| } |
| } |
| |
| /* Compact when all rows have been deleted. */ |
| brg_tbl_compact(); |
| |
| if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) { |
| atomic_set_bit(brg_cfg_flags, TABLE_UPDATED); |
| bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING); |
| } |
| |
| return 0; |
| } |