From 092f808ea43bb65de48ebf55dff79aba093ddc5f Mon Sep 17 00:00:00 2001 From: Omkar Kulkarni Date: Tue, 25 Jun 2024 16:45:50 +0200 Subject: [PATCH] Bluetooth: Mesh: Adds subnet bridge states The `brg_cfg` module implements the states needed for subnet bridge feature. It provides two states - enable state, and bridging table state. APIs are provided to access and modify the states. The module handles responsibility of persistence of the states. Signed-off-by: Omkar Kulkarni --- subsys/bluetooth/Kconfig.logging | 4 + subsys/bluetooth/mesh/CMakeLists.txt | 2 +- subsys/bluetooth/mesh/Kconfig | 12 + subsys/bluetooth/mesh/brg_cfg.c | 307 +++++++++++++++ subsys/bluetooth/mesh/brg_cfg.h | 74 ++++ subsys/bluetooth/mesh/brg_cfg_srv.c | 7 + subsys/bluetooth/mesh/settings.c | 10 +- subsys/bluetooth/mesh/settings.h | 1 + tests/bluetooth/mesh/brg/CMakeLists.txt | 22 ++ tests/bluetooth/mesh/brg/prj.conf | 3 + tests/bluetooth/mesh/brg/src/main.c | 501 ++++++++++++++++++++++++ tests/bluetooth/mesh/brg/testcase.yaml | 10 + 12 files changed, 951 insertions(+), 2 deletions(-) create mode 100644 subsys/bluetooth/mesh/brg_cfg.c create mode 100644 subsys/bluetooth/mesh/brg_cfg.h create mode 100644 tests/bluetooth/mesh/brg/CMakeLists.txt create mode 100644 tests/bluetooth/mesh/brg/prj.conf create mode 100644 tests/bluetooth/mesh/brg/src/main.c create mode 100644 tests/bluetooth/mesh/brg/testcase.yaml diff --git a/subsys/bluetooth/Kconfig.logging b/subsys/bluetooth/Kconfig.logging index 9c2884214e26..d1a20362f7f6 100644 --- a/subsys/bluetooth/Kconfig.logging +++ b/subsys/bluetooth/Kconfig.logging @@ -407,6 +407,10 @@ module = BT_MESH_NET module-str = "Network layer" source "subsys/logging/Kconfig.template.log_config_inherit" +module = BT_MESH_BRG +module-str = "Subnet Bridging layer" +source "subsys/logging/Kconfig.template.log_config_inherit" + module = BT_MESH_RPL module-str = "Replay protection list" source "subsys/logging/Kconfig.template.log_config_inherit" diff --git a/subsys/bluetooth/mesh/CMakeLists.txt b/subsys/bluetooth/mesh/CMakeLists.txt index 4d7ce916be51..3d5deadc6d92 100644 --- a/subsys/bluetooth/mesh/CMakeLists.txt +++ b/subsys/bluetooth/mesh/CMakeLists.txt @@ -115,7 +115,7 @@ zephyr_library_sources_ifdef(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV sol_pdu_rpl_srv.c) zephyr_library_sources_ifdef(CONFIG_BT_MESH_BRG_CFG_CLI brg_cfg_cli.c) -zephyr_library_sources_ifdef(CONFIG_BT_MESH_BRG_CFG_SRV brg_cfg_srv.c) +zephyr_library_sources_ifdef(CONFIG_BT_MESH_BRG_CFG_SRV brg_cfg_srv.c brg_cfg.c) zephyr_library_sources_ifdef(CONFIG_BT_MESH_SOLICITATION solicitation.c) diff --git a/subsys/bluetooth/mesh/Kconfig b/subsys/bluetooth/mesh/Kconfig index 9102cb36d0e6..56f460ff312a 100644 --- a/subsys/bluetooth/mesh/Kconfig +++ b/subsys/bluetooth/mesh/Kconfig @@ -1274,6 +1274,18 @@ config BT_MESH_BRG_CFG_SRV The Bridge Configuration Server model is used to support the configuration of the subnet bridge functionality of a node. +menu "Subnet Bridge configuration" + visible if BT_MESH_BRG_CFG_SRV + +config BT_MESH_BRG_TABLE_ITEMS_MAX + int "Maximum number of entries in the bridging table" + default 16 + range 16 255 + help + The maximum number of entries in the bridging table. + +endmenu + config BT_MESH_BRG_CFG_CLI bool "Support for Bridge Configuration Client model" help diff --git a/subsys/bluetooth/mesh/brg_cfg.c b/subsys/bluetooth/mesh/brg_cfg.c new file mode 100644 index 000000000000..8cfa0139512f --- /dev/null +++ b/subsys/bluetooth/mesh/brg_cfg.c @@ -0,0 +1,307 @@ +/* + * 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 +#include + +#include "mesh.h" +#include "net.h" +#include "settings.h" +#include "brg_cfg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_BRG_LOG_LEVEL +#include +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; + +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--; +} + +#if IS_ENABLED(CONFIG_BT_SETTINGS) +/* 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) +{ + 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; + } + + int err = bt_mesh_settings_set(read_cb, cb_arg, brg_tbl, sizeof(brg_tbl)); + + if (err) { + LOG_ERR("Failed to set bridging table entries"); + return err; + } + + LOG_DBG("Restored bridging table"); + + return 0; +} + +/* Define a setting for storing briging table rows */ +BT_MESH_SETTINGS_DEFINE(brg_tbl, "brg_tbl", brg_tbl_set); +#endif + +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) + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING); +#endif + return 0; +} + +void bt_mesh_brg_cfg_pending_store(void) +{ +#if CONFIG_BT_SETTINGS + char *path_en = "bt/mesh/brg_en"; + char *path_tbl = "bt/mesh/brg_tbl"; + int err; + + 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 (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); + } +#endif +} + +/* 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) + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING); +#endif +} + +/* 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)); + +#if CONFIG_BT_SETTINGS + err = settings_delete("bt/mesh/brg_en"); + + if (err) { + return err; + } + + err = settings_delete("bt/mesh/brg_tbl"); +#endif + 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 > 0x03FF || net_idx2 > 0x03FF) { + 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) + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING); +#endif + + 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); + } + } +} + +void bt_mesh_brg_cfg_tbl_remove(uint16_t net_idx1, uint16_t net_idx2, uint16_t addr1, + uint16_t addr2) +{ +#if IS_ENABLED(CONFIG_BT_SETTINGS) + bool store = false; +#endif + + /* 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; + } + + 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])); + brg_tbl_compact(); +#if IS_ENABLED(CONFIG_BT_SETTINGS) + store = true; +#endif + } + } + } +#if IS_ENABLED(CONFIG_BT_SETTINGS) + if (store) { + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING); + } +#endif +} diff --git a/subsys/bluetooth/mesh/brg_cfg.h b/subsys/bluetooth/mesh/brg_cfg.h new file mode 100644 index 000000000000..28364398de38 --- /dev/null +++ b/subsys/bluetooth/mesh/brg_cfg.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_MESH_BRG_CFG_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_MESH_BRG_CFG_H_ + +/** These are internal APIs. They do not sanitize input params. */ +enum bt_mesh_brg_cfg_dir { + /* Value is prohibited. */ + BT_MESH_BRG_CFG_DIR_PROHIBITED = 0, + /* Briging from Addr1 to Addr2. */ + BT_MESH_BRG_CFG_DIR_ONEWAY = 1, + /* Briging to/from Addr1 from/to Addr2. */ + BT_MESH_BRG_CFG_DIR_TWOWAY = 2, + /* Values above these are prohibited. */ + BT_MESH_BRG_CFG_DIR_MAX = 3, +}; + +#define BT_MESH_BRG_CFG_NETIDX_NOMATCH 0xFFFF + +/* One row of the bridging table */ +struct bt_mesh_brg_cfg_row { + /* Direction of the entry in the bridging table + * 0 - no entry, + * 1 - bridge messages with src as addr1 and dst as addr2 + * 2 - bridge messages with src as addr1 and dst as addr2 and vice-versa + */ + uint32_t direction:8; + uint32_t net_idx1:12; + uint32_t net_idx2:12; + uint16_t addr1; + uint16_t addr2; +}; + +bool bt_mesh_brg_cfg_enable_get(void); + +int bt_mesh_brg_cfg_enable_set(bool enable); + +void bt_mesh_brg_cfg_pending_store(void); + +int bt_mesh_brg_cfg_tbl_reset(void); + +int bt_mesh_brg_cfg_tbl_get(const struct bt_mesh_brg_cfg_row **rows); + +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); + +void bt_mesh_brg_cfg_tbl_remove(uint16_t net_idx1, uint16_t net_idx2, uint16_t addr1, + uint16_t addr2); + +typedef void (*bt_mesh_brg_cfg_cb_t)(uint16_t new_netidx, void *user_data); + +/** + * @brief Iterate over the bridging table to find a matching entry for the given SRC, DST, and + * NetKey Index. + * + * This function iterates over the bridging table and checks if there is a match for the provided + * parameters. If a match is found, the callback function specified by the 'cb' parameter is + * invoked with the NetKey Index of each matching entry (there can be several). Relaying operation + * can then happen inside this callback. + * + * @param src The source address to match. + * @param dst The destination address to match. + * @param net_idx The NetKey Index to match. + * @param cb The callback function to be invoked for each matching entry. + * @param user_data User data to be passed to the callback function. + */ +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); + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_MESH_BRG_CFG_H_ */ diff --git a/subsys/bluetooth/mesh/brg_cfg_srv.c b/subsys/bluetooth/mesh/brg_cfg_srv.c index 60aef4bf02c0..bd5ced50abdf 100644 --- a/subsys/bluetooth/mesh/brg_cfg_srv.c +++ b/subsys/bluetooth/mesh/brg_cfg_srv.c @@ -5,6 +5,7 @@ */ #include +#include "brg_cfg.h" #define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL #include @@ -30,6 +31,12 @@ static int brg_cfg_srv_init(const struct bt_mesh_model *model) 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, }; diff --git a/subsys/bluetooth/mesh/settings.c b/subsys/bluetooth/mesh/settings.c index 8ec9c66481ac..7997b5e9985a 100644 --- a/subsys/bluetooth/mesh/settings.c +++ b/subsys/bluetooth/mesh/settings.c @@ -29,6 +29,7 @@ #include "pb_gatt_srv.h" #include "settings.h" #include "cfg.h" +#include "brg_cfg.h" #include "solicitation.h" #include "va.h" @@ -134,7 +135,8 @@ SETTINGS_STATIC_HANDLER_DEFINE(bt_mesh, "bt/mesh", NULL, NULL, mesh_commit, BIT(BT_MESH_SETTINGS_VA_PENDING) | \ BIT(BT_MESH_SETTINGS_SSEQ_PENDING) | \ BIT(BT_MESH_SETTINGS_COMP_PENDING) | \ - BIT(BT_MESH_SETTINGS_DEV_KEY_CAND_PENDING)) + BIT(BT_MESH_SETTINGS_DEV_KEY_CAND_PENDING) | \ + BIT(BT_MESH_SETTINGS_BRG_PENDING)) void bt_mesh_settings_store_schedule(enum bt_mesh_settings_flag flag) { @@ -262,6 +264,12 @@ static void store_pending(struct k_work *work) BT_MESH_SETTINGS_SSEQ_PENDING)) { bt_mesh_sseq_pending_store(); } + + if (IS_ENABLED(CONFIG_BT_MESH_BRG_CFG_SRV) && + atomic_test_and_clear_bit(pending_flags, + BT_MESH_SETTINGS_BRG_PENDING)) { + bt_mesh_brg_cfg_pending_store(); + } } void bt_mesh_settings_init(void) diff --git a/subsys/bluetooth/mesh/settings.h b/subsys/bluetooth/mesh/settings.h index bd6db9c3cdc2..e50820ae61ce 100644 --- a/subsys/bluetooth/mesh/settings.h +++ b/subsys/bluetooth/mesh/settings.h @@ -21,6 +21,7 @@ enum bt_mesh_settings_flag { BT_MESH_SETTINGS_SSEQ_PENDING, BT_MESH_SETTINGS_COMP_PENDING, BT_MESH_SETTINGS_DEV_KEY_CAND_PENDING, + BT_MESH_SETTINGS_BRG_PENDING, BT_MESH_SETTINGS_FLAG_COUNT, }; diff --git a/tests/bluetooth/mesh/brg/CMakeLists.txt b/tests/bluetooth/mesh/brg/CMakeLists.txt new file mode 100644 index 000000000000..d878ad04d50f --- /dev/null +++ b/tests/bluetooth/mesh/brg/CMakeLists.txt @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(bluetooth_mesh_brg) + +FILE(GLOB app_sources src/*.c) +target_sources(app + PRIVATE + ${app_sources} + ${ZEPHYR_BASE}/subsys/bluetooth/mesh/brg_cfg.c) + +target_include_directories(app + PRIVATE + ${ZEPHYR_BASE}/subsys/bluetooth/mesh) + +target_compile_options(app + PRIVATE + -DCONFIG_BT_SETTINGS + -DCONFIG_BT_MESH_BRG_CFG_SRV + -DCONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX=16 + -DCONFIG_BT_MESH_USES_TINYCRYPT) diff --git a/tests/bluetooth/mesh/brg/prj.conf b/tests/bluetooth/mesh/brg/prj.conf new file mode 100644 index 000000000000..1eea5516d41a --- /dev/null +++ b/tests/bluetooth/mesh/brg/prj.conf @@ -0,0 +1,3 @@ +CONFIG_ZTEST=y +CONFIG_ZTEST_MOCKING=y +CONFIG_BT_MESH_BRG_CFG_SRV=y diff --git a/tests/bluetooth/mesh/brg/src/main.c b/tests/bluetooth/mesh/brg/src/main.c new file mode 100644 index 000000000000..8f6fe6d276a4 --- /dev/null +++ b/tests/bluetooth/mesh/brg/src/main.c @@ -0,0 +1,501 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "settings.h" +#include "brg_cfg.h" + +#define TEST_VECT_SZ (CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX + 1) + +static struct test_brg_cfg_row { + uint8_t direction; + uint16_t net_idx1; + uint16_t net_idx2; + uint16_t addr1; + uint16_t addr2; +} test_vector[TEST_VECT_SZ]; + +#define ADDR1_BASE (1) +#define ADDR2_BASE (100) + +/**** Helper functions ****/ +static void setup(void *f) +{ + /* create test vector */ + for (int i = 0; i < TEST_VECT_SZ; i++) { + test_vector[i].direction = i < (TEST_VECT_SZ / 2) ? 1 : 2; + test_vector[i].net_idx1 = (i/8); + test_vector[i].addr1 = ADDR1_BASE + i; + test_vector[i].net_idx2 = (i/8) + 16; + test_vector[i].addr2 = ADDR2_BASE + i; + } + +} + +/**** Mocked functions ****/ + +void bt_mesh_settings_store_schedule(enum bt_mesh_settings_flag flag) +{ + ztest_check_expected_value(flag); +} + +int settings_save_one(const char *name, const void *value, size_t val_len) +{ + ztest_check_expected_data(name, strlen(name)); + ztest_check_expected_value(val_len); + ztest_check_expected_data(value, val_len); + return 0; +} + +int settings_delete(const char *name) +{ + ztest_check_expected_data(name, strlen(name)); + return 0; +} + +/**** Mocked functions - end ****/ + +static void check_fill_all_bt_entries(void) +{ + int err; + + for (int i = 0; i < TEST_VECT_SZ; i++) { + + if (i < CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX) { + ztest_expect_value(bt_mesh_settings_store_schedule, flag, + BT_MESH_SETTINGS_BRG_PENDING); + } + + err = bt_mesh_brg_cfg_tbl_add(test_vector[i].direction, test_vector[i].net_idx1, + test_vector[i].net_idx2, test_vector[i].addr1, test_vector[i].addr2); + + if (i != CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX) { + zassert_equal(err, 0); + } else { + zassert_equal(err, -ENOMEM); + } + } +} + +static void check_delete_all_bt_entries(void) +{ + for (int i = 0; i < TEST_VECT_SZ; i++) { + + if (i < CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX) { + ztest_expect_value(bt_mesh_settings_store_schedule, flag, + BT_MESH_SETTINGS_BRG_PENDING); + } + + bt_mesh_brg_cfg_tbl_remove(test_vector[i].net_idx1, test_vector[i].net_idx2, + test_vector[i].addr1, test_vector[i].addr2); + } +} + + +static void check_bt_mesh_brg_cfg_tbl_reset(void) +{ + int err; + + ztest_expect_data(settings_delete, name, "bt/mesh/brg_en"); + ztest_expect_data(settings_delete, name, "bt/mesh/brg_tbl"); + err = bt_mesh_brg_cfg_tbl_reset(); + zassert_equal(err, 0); +} + +/**** Tests ****/ + +ZTEST_SUITE(bt_mesh_brg_cfg, NULL, NULL, setup, NULL, NULL); + +/* Test if basic functionality (add and remove entries) works correctly. */ +ZTEST(bt_mesh_brg_cfg, test_basic_functionality_storage) +{ + check_bt_mesh_brg_cfg_tbl_reset(); + + /* Test add entries to bridging table. */ + check_fill_all_bt_entries(); + + /* Test remove entries from bridging table, and then fill it again. */ + check_delete_all_bt_entries(); + check_fill_all_bt_entries(); + + /* Test resetting of the table, and then fill it again. */ + check_bt_mesh_brg_cfg_tbl_reset(); + check_fill_all_bt_entries(); + + /* Test remove entries matching netkey1, and netkey2 */ + uint16_t net_idx1 = test_vector[TEST_VECT_SZ - 1].net_idx1; + uint16_t net_idx2 = test_vector[TEST_VECT_SZ - 1].net_idx2; + uint16_t addr1 = BT_MESH_ADDR_UNASSIGNED; + uint16_t addr2 = BT_MESH_ADDR_UNASSIGNED; + + bt_mesh_brg_cfg_tbl_remove(net_idx1, net_idx2, addr1, addr2); + + const struct bt_mesh_brg_cfg_row *brg_tbl; + int n = bt_mesh_brg_cfg_tbl_get(&brg_tbl); + + zassert_true(n > 0); + + for (int i = 0; i < n; i++) { + zassert_true(brg_tbl[i].net_idx1 != net_idx1); + zassert_true(brg_tbl[i].net_idx2 != net_idx2); + } + + check_bt_mesh_brg_cfg_tbl_reset(); + check_fill_all_bt_entries(); + + /* Test remove entries matching netkey1, and netkey2, and addr1 */ + addr1 = test_vector[TEST_VECT_SZ - 1].addr1; + n = bt_mesh_brg_cfg_tbl_get(&brg_tbl); + + zassert_true(n > 0); + + for (int i = 0; i < n; i++) { + zassert_true(brg_tbl[i].net_idx1 != net_idx1); + zassert_true(brg_tbl[i].net_idx2 != net_idx2); + zassert_true(brg_tbl[i].addr1 != addr1); + } + + check_bt_mesh_brg_cfg_tbl_reset(); + check_fill_all_bt_entries(); + + /* Test remove entries matching netkey1, and netkey2, and addr2 */ + addr1 = BT_MESH_ADDR_UNASSIGNED; + addr2 = test_vector[TEST_VECT_SZ - 1].addr2; + n = bt_mesh_brg_cfg_tbl_get(&brg_tbl); + + zassert_true(n > 0); + + for (int i = 0; i < n; i++) { + zassert_true(brg_tbl[i].net_idx1 != net_idx1); + zassert_true(brg_tbl[i].net_idx2 != net_idx2); + zassert_true(brg_tbl[i].addr2 != addr2); + } +} + +static void pending_store_enable_create_expectations(bool *enable_val, + int n, const struct bt_mesh_brg_cfg_row *tbl_val) +{ + if (*enable_val) { + ztest_expect_data(settings_save_one, name, "bt/mesh/brg_en"); + ztest_expect_value(settings_save_one, val_len, 1); + ztest_expect_data(settings_save_one, value, enable_val); + } else { + ztest_expect_data(settings_delete, name, "bt/mesh/brg_en"); + } + + if (n > 0) { + ztest_expect_data(settings_save_one, name, "bt/mesh/brg_tbl"); + ztest_expect_value(settings_save_one, val_len, + n * sizeof(struct bt_mesh_brg_cfg_row)); + ztest_expect_data(settings_save_one, value, tbl_val); + } else { + ztest_expect_data(settings_delete, name, "bt/mesh/brg_tbl"); + } +} + +/* Test if enable flag is stored correctly. */ +ZTEST(bt_mesh_brg_cfg, test_brg_cfg_en) +{ + int err; + int n; + bool val; + const struct bt_mesh_brg_cfg_row *tbl; + + check_bt_mesh_brg_cfg_tbl_reset(); + val = bt_mesh_brg_cfg_enable_get(); + n = bt_mesh_brg_cfg_tbl_get(&tbl); + zassert_equal(val, false, NULL); + pending_store_enable_create_expectations(&val, n, tbl); + bt_mesh_brg_cfg_pending_store(); + + + ztest_expect_value(bt_mesh_settings_store_schedule, flag, + BT_MESH_SETTINGS_BRG_PENDING); + err = bt_mesh_brg_cfg_enable_set(true); + zassert_equal(err, 0, NULL); + val = bt_mesh_brg_cfg_enable_get(); + n = bt_mesh_brg_cfg_tbl_get(&tbl); + pending_store_enable_create_expectations(&val, n, tbl); + bt_mesh_brg_cfg_pending_store(); + + zassert_equal(bt_mesh_brg_cfg_enable_get(), true, NULL); +} + +/* Test if pending store works correctly by adding one entry to the table. */ +ZTEST(bt_mesh_brg_cfg, test_brg_tbl_pending_store) +{ + int n, err; + bool b_en; + struct bt_mesh_brg_cfg_row test_vec = { + .direction = BT_MESH_BRG_CFG_DIR_ONEWAY, + .net_idx1 = 1, + .net_idx2 = 2, + .addr1 = 3, + .addr2 = 4, + }; + + check_bt_mesh_brg_cfg_tbl_reset(); + ztest_expect_value(bt_mesh_settings_store_schedule, flag, + BT_MESH_SETTINGS_BRG_PENDING); + err = bt_mesh_brg_cfg_tbl_add(test_vec.direction, test_vec.net_idx1, + test_vec.net_idx2, test_vec.addr1, test_vec.addr2); + zassert_equal(err, 0); + + const struct bt_mesh_brg_cfg_row *tbl; + + n = bt_mesh_brg_cfg_tbl_get(&tbl); + b_en = bt_mesh_brg_cfg_enable_get(); + + zassert_equal(n, 1); + zassert_true(tbl); + + pending_store_enable_create_expectations(&b_en, 1, &test_vec); + bt_mesh_brg_cfg_pending_store(); +} + +/* Test if invalid entries are not added to the table. */ +ZTEST(bt_mesh_brg_cfg, test_tbl_add_invalid_ip) +{ + int err; + /* Create test vector array of test_brg_cfg_row iteams with invalid values. + * Each vector has only one invalid field value, rest all are valid values. + */ + const struct test_brg_cfg_row inv_test_vector[] = { + /* Direction has invalid values */ + {.direction = BT_MESH_BRG_CFG_DIR_PROHIBITED, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 2}, + {.direction = BT_MESH_BRG_CFG_DIR_MAX, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 2}, + /* Out of range netidx values */ + {.direction = BT_MESH_BRG_CFG_DIR_ONEWAY, + .net_idx1 = 4096, .net_idx2 = 1, .addr1 = 1, .addr2 = 2}, + {.direction = BT_MESH_BRG_CFG_DIR_ONEWAY, + .net_idx1 = 0, .net_idx2 = 4096, .addr1 = 1, .addr2 = 2}, + /* Same netidx values */ + {.direction = BT_MESH_BRG_CFG_DIR_ONEWAY, + .net_idx1 = 0, .net_idx2 = 0, .addr1 = 1, .addr2 = 2}, + /* Same addr values */ + {.direction = BT_MESH_BRG_CFG_DIR_ONEWAY, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 1}, + /* Invalid address1 value */ + {.direction = BT_MESH_BRG_CFG_DIR_ONEWAY, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 0, .addr2 = 1}, + {.direction = BT_MESH_BRG_CFG_DIR_ONEWAY, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 0x8000, .addr2 = 1}, + {.direction = BT_MESH_BRG_CFG_DIR_ONEWAY, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 0xC000, .addr2 = 1}, + {.direction = BT_MESH_BRG_CFG_DIR_ONEWAY, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 0xFFFE, .addr2 = 1}, + {.direction = BT_MESH_BRG_CFG_DIR_ONEWAY, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 0xFFFF, .addr2 = 1}, + /* Invalid address2 values */ + {.direction = BT_MESH_BRG_CFG_DIR_ONEWAY, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 0}, + {.direction = BT_MESH_BRG_CFG_DIR_ONEWAY, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 0xFFFF}, + {.direction = BT_MESH_BRG_CFG_DIR_TWOWAY, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 0x8000}, + {.direction = BT_MESH_BRG_CFG_DIR_TWOWAY, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 0xC000}, + {.direction = BT_MESH_BRG_CFG_DIR_TWOWAY, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 0xFFFE}, + {.direction = BT_MESH_BRG_CFG_DIR_TWOWAY, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 0xFFFF}, + }; + + check_bt_mesh_brg_cfg_tbl_reset(); + + for (int i = 0; i < ARRAY_SIZE(inv_test_vector); i++) { + err = bt_mesh_brg_cfg_tbl_add(inv_test_vector[i].direction, + inv_test_vector[i].net_idx1, inv_test_vector[i].net_idx2, + inv_test_vector[i].addr1, inv_test_vector[i].addr2); + zassert_equal(err, -EINVAL, "Test vector index: %zu", i); + } +} + + +/* Following are helper functions for the test that checks the iteration logic */ +#define NUM_MSGS (10000) + +static void print_brg_tbl(void) +{ + const struct bt_mesh_brg_cfg_row *tbl; + int n = bt_mesh_brg_cfg_tbl_get(&tbl); + + zassert_true(n <= CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX); + + for (int i = 0; i < n; i++) { + printk("entry: %3d # dir: %d, net_idx1: %3d, addr1: %3d, net_idx2: %3d, addr2: %3d\n", + i, tbl[i].direction, tbl[i].net_idx1, tbl[i].addr1, tbl[i].net_idx2, + tbl[i].addr2); + } +} + +static void check_fill_all_bt_entries_reversed(void) +{ + int err; + + for (int i = TEST_VECT_SZ - 2; i >= 0 ; i--) { + ztest_expect_value(bt_mesh_settings_store_schedule, flag, + BT_MESH_SETTINGS_BRG_PENDING); + err = bt_mesh_brg_cfg_tbl_add(test_vector[i].direction, test_vector[i].net_idx1, + test_vector[i].net_idx2, test_vector[i].addr1, test_vector[i].addr2); + zassert_equal(err, 0); + } + + int last = TEST_VECT_SZ - 1; + + err = bt_mesh_brg_cfg_tbl_add(test_vector[last].direction, test_vector[last].net_idx1, + test_vector[last].net_idx2, test_vector[last].addr1, test_vector[last].addr2); + zassert_equal(err, -ENOMEM); +} + +static struct test_brg_cfg_row test_vector_copy[TEST_VECT_SZ - 1]; + +static void check_fill_all_bt_entries_randomly(void) +{ + int err; + int copy_cnt = ARRAY_SIZE(test_vector_copy); + + memcpy(test_vector_copy, test_vector, sizeof(test_vector_copy)); + + for (int i = 0; i < copy_cnt; i++) { + int idx = rand() % copy_cnt; + struct test_brg_cfg_row tmp = test_vector_copy[i]; + + test_vector_copy[i] = test_vector_copy[idx]; + test_vector_copy[idx] = tmp; + } + + for (int i = 0; i < copy_cnt; i++) { + ztest_expect_value(bt_mesh_settings_store_schedule, flag, + BT_MESH_SETTINGS_BRG_PENDING); + err = bt_mesh_brg_cfg_tbl_add(test_vector_copy[i].direction, + test_vector_copy[i].net_idx1, test_vector_copy[i].net_idx2, + test_vector_copy[i].addr1, test_vector_copy[i].addr2); + zassert_equal(err, 0); + } + + int last = TEST_VECT_SZ - 1; + + err = bt_mesh_brg_cfg_tbl_add(test_vector[last].direction, test_vector[last].net_idx1, + test_vector[last].net_idx2, test_vector[last].addr1, test_vector[last].addr2); + zassert_equal(err, -ENOMEM); +} + +static void subnet_relay_cb_check(uint16_t new_net_idx, void *user_data) +{ + int idx = *(int *)user_data; + + zassert_equal(new_net_idx, test_vector[idx].net_idx2); +} + +static void subnet_relay_cb_check_rev(uint16_t new_net_idx, void *user_data) +{ + int idx = *(int *)user_data; + + if (test_vector[idx].direction == 2) { + zassert_equal(new_net_idx, test_vector[idx].net_idx1); + } else { + /* Should never assert. Test vector created in setup(). */ + zassert_true(false); + } +} + +static void test_bridging_performance(bool test_one_way) +{ + int idx; + uint32_t tick1; + uint32_t ticks = 0; + + for (int i = 0; i < NUM_MSGS; i++) { + /* randomly pick an entry from the test vector */ + idx = rand() % TEST_VECT_SZ; + + /* check src to dst bridging*/ + const struct bt_mesh_brg_cfg_row *tbl_row = NULL; + + tick1 = k_uptime_ticks(); + bt_mesh_brg_cfg_tbl_foreach_subnet(test_vector[idx].addr1, test_vector[idx].addr2, + test_vector[idx].net_idx1, subnet_relay_cb_check, &idx); + ticks += k_uptime_ticks() - tick1; + + if (test_one_way) { + continue; + } + + /* check dst to src bridging - for the same test vector src-dst pairs + * but now, reverse them and consider packets are arriving on net_idx2 + */ + tbl_row = NULL; + tick1 = k_uptime_ticks(); + bt_mesh_brg_cfg_tbl_foreach_subnet(test_vector[idx].addr2, test_vector[idx].addr1, + test_vector[idx].net_idx2, subnet_relay_cb_check_rev, &idx); + ticks += k_uptime_ticks() - tick1; + } + printk("ticks: %8u us: %u\n", ticks, k_ticks_to_us_floor32(ticks)); +} + +/* Test checks iteration logic and performance when run on real devices. */ +ZTEST(bt_mesh_brg_cfg, test_zcheck_entry_randomly_sorting) +{ + printk("num msgs: %d\n\n", NUM_MSGS); + + /* Test performance when packets are flowing in one directions */ + /* Fill bridging table in sorted order */ + printk("\n\nPackets going only in one direction (from outside towards the subnet)\n"); + printk("\nBridging table is pre-filled in sorted order\n"); + + check_bt_mesh_brg_cfg_tbl_reset(); + check_fill_all_bt_entries(); + print_brg_tbl(); + test_bridging_performance(true); + + /* Fill bridging table in reversed order */ + printk("\nBridging table is pre-filled in reversed order\n"); + + check_bt_mesh_brg_cfg_tbl_reset(); + check_fill_all_bt_entries_reversed(); + print_brg_tbl(); + test_bridging_performance(true); + + /* Fill bridging table in random order */ + printk("\nBridging table is pre-filled in random order\n"); + + check_bt_mesh_brg_cfg_tbl_reset(); + check_fill_all_bt_entries_randomly(); + print_brg_tbl(); + test_bridging_performance(true); + + /* Test performance when packets are flowing in both directions - use same dataset. */ + printk("\n\nPackets going in both directions (same data set, flip src and dst pairs)\n"); + printk("\nBridging table is pre-filled in sorted order\n"); + + check_bt_mesh_brg_cfg_tbl_reset(); + check_fill_all_bt_entries(); + print_brg_tbl(); + test_bridging_performance(false); + + /* Fill bridging table in reversed order */ + printk("\nBridging table is pre-filled in reversed order\n"); + + check_bt_mesh_brg_cfg_tbl_reset(); + check_fill_all_bt_entries_reversed(); + print_brg_tbl(); + test_bridging_performance(false); + + /* Fill bridging table in random order */ + printk("\nBridging table is pre-filled in random order\n"); + + check_bt_mesh_brg_cfg_tbl_reset(); + check_fill_all_bt_entries_randomly(); + print_brg_tbl(); + test_bridging_performance(false); +} diff --git a/tests/bluetooth/mesh/brg/testcase.yaml b/tests/bluetooth/mesh/brg/testcase.yaml new file mode 100644 index 000000000000..b94a3d445e22 --- /dev/null +++ b/tests/bluetooth/mesh/brg/testcase.yaml @@ -0,0 +1,10 @@ +tests: + bluetooth.mesh.brg: + platform_allow: + - native_posix + - native_sim + tags: + - bluetooth + - mesh + integration_platforms: + - native_sim