diff --git a/subsys/bluetooth/mesh/CMakeLists.txt b/subsys/bluetooth/mesh/CMakeLists.txt index cbc80d0f9f96f55..9c9e73049efc7a2 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 f1e4941154f55be..325f7d0ea71f61d 100644 --- a/subsys/bluetooth/mesh/Kconfig +++ b/subsys/bluetooth/mesh/Kconfig @@ -1234,6 +1234,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 000000000000000..e9af2111aa54e5c --- /dev/null +++ b/subsys/bluetooth/mesh/brg_cfg.c @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Implimentation for Bridging Table state of Subnet Bridge feature + * in Bluetooth Mesh Protocol v1.1 specification + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "mesh.h" +#include "net.h" +#include "subnet.h" +#include "settings.h" +#include "brg_cfg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_NET_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_brg_cfg); + +/* Bridging table state. Each item is a slist node. */ +static struct brg_tbl_row brg_tbl[CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX]; +static uint32_t brg_tbl_row_cnt; +static bool brg_enabled; + +static void brg_tbl_compact(void) +{ + int j = 0; + + for (int k = 0; k < brg_tbl_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])); + brg_tbl_row_cnt--; +} + +#if IS_ENABLED(CONFIG_BT_SETTINGS) +static int brgen_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 sbren"); + + return 0; +} + +/* Define a setting for storing enable state */ +BT_MESH_SETTINGS_DEFINE(brgen, "brgen", brgen_set); + +/* Set function for initializing bridging table rows from brg_tbl slist */ +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)); + brg_tbl_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 brg_tbl"); + + return 0; +} + +/* Define a setting for storing briging table rows */ +BT_MESH_SETTINGS_DEFINE(brgbt, "brgbt", brg_tbl_set); +#endif + +bool brg_cfg_enable_get(void) +{ + return brg_enabled; +} + +int 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_en_pending_store(void) +{ +#if CONFIG_BT_SETTINGS + char *path = "bt/mesh/brgen"; + int err; + + if (brg_enabled) { + err = settings_save_one(path, &brg_enabled, sizeof(brg_enabled)); + } else { + err = settings_delete(path); + } + + if (err) { + LOG_ERR("Failed to store %s value", path); + } +#endif +} + +void bt_mesh_brg_tbl_pending_store(void) +{ +#if CONFIG_BT_SETTINGS + char *path = "bt/mesh/brgbt"; + char path_bt[20]; + int err; + + if (!brg_tbl_row_cnt) { + err = settings_save_one(path, &brg_tbl, brg_tbl_row_cnt * sizeof(brg_tbl[0])); + } else { + err = settings_delete(path); + } + + if (err) { + LOG_ERR("Failed to store %s value", path_bt); + } +#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) +{ + 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 brg_cfg_init(void) +{ + memset(brg_tbl, 0, sizeof(brg_tbl)); + brg_tbl_row_cnt = 0; + return 0; +} + +int brg_cfg_tbl_reset(void) +{ + brg_cfg_init(); + + int err = settings_delete("bt/mesh/brgen"); + + if (err) { + return err; + } + err = settings_delete("bt/mesh/brgbt"); + + return err; +} + +int brg_cfg_tbl_get(const struct brg_tbl_row **rows) +{ + *rows = brg_tbl; + return brg_tbl_row_cnt; +} + +int brg_cfg_tbl_add(enum brg_tbl_direction direction, uint16_t net_idx1, uint16_t net_idx2, + uint16_t addr1, uint16_t addr2) +{ + /* Sanity check */ + if (!BT_MESH_ADDR_IS_UNICAST(addr1) || net_idx1 == net_idx2 || addr1 == addr2 || + net_idx1 > 0x03FF || net_idx2 > 0x03FF || + (direction != BRG_ADDR1_TO_ADDR2 && direction != BRG_ADDR1_TF_ADDR2) || + (direction == BRG_ADDR1_TO_ADDR2 && + (addr2 == BT_MESH_ADDR_UNASSIGNED || addr2 == BT_MESH_ADDR_ALL_NODES)) || + (direction == BRG_ADDR1_TF_ADDR2 && !BT_MESH_ADDR_IS_UNICAST(addr2))) { + return -EINVAL; + } + + /* Check if entry already exists, if yes, then it is success. */ + for (int i = 0; i < brg_tbl_row_cnt; i++) { + if (brg_tbl[i].direction == direction && brg_tbl[i].net_idx1 == net_idx1 && + brg_tbl[i].net_idx2 == net_idx2 && brg_tbl[i].addr1 == addr1 && + brg_tbl[i].addr2 == addr2) { + return 0; + } + } + + /* Empty element, is the current table row counter */ + if (brg_tbl_row_cnt == CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX) { + return -ENOMEM; + } + + /* Update the row */ + brg_tbl[brg_tbl_row_cnt].direction = direction; + brg_tbl[brg_tbl_row_cnt].net_idx1 = net_idx1; + brg_tbl[brg_tbl_row_cnt].net_idx2 = net_idx2; + brg_tbl[brg_tbl_row_cnt].addr1 = addr1; + brg_tbl[brg_tbl_row_cnt].addr2 = addr2; + brg_tbl_row_cnt++; + +#if IS_ENABLED(CONFIG_BT_SETTINGS) + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING); +#endif + + return 0; +} + +static int itr; + +int brg_cfg_tbl_check_start(void) +{ + itr = 0; + + if (!brg_tbl_row_cnt) { + return -ENOENT; + } + + return 0; +} + +int brg_cfg_tbl_check_itr(uint16_t src, uint16_t dst, uint16_t netidx, uint16_t *new_netidx) +{ + /* Iterate over items */ + *new_netidx = BRG_NETIDX_NOMATCH; + if (itr >= brg_tbl_row_cnt) { + return -ENOENT; + } + + if ((brg_tbl[itr].direction == 1 || brg_tbl[itr].direction == 2) && + brg_tbl[itr].net_idx1 == netidx && brg_tbl[itr].addr1 == src && + brg_tbl[itr].addr2 == dst) { + *new_netidx = brg_tbl[itr].net_idx2; + goto exititr; + } + + if (brg_tbl[itr].direction == 2 && + brg_tbl[itr].net_idx2 == netidx && brg_tbl[itr].addr2 == src && + brg_tbl[itr].addr1 == dst) { + *new_netidx = brg_tbl[itr].net_idx1; + goto exititr; + } + +exititr: + itr++; + return 0; +} + +int brg_cfg_tbl_remove(uint16_t net_idx1, uint16_t net_idx2, uint16_t addr1, uint16_t addr2) +{ + /* Iterate over items and set matching row to 0, if nothing exist, or + * nopthing matches, then it is success. + */ + if (brg_tbl_row_cnt == 0) { + return 0; + } + + for (int i = 0; i < brg_tbl_row_cnt; i++) { + if (brg_tbl[i].direction && brg_tbl[i].net_idx1 == net_idx1 && + brg_tbl[i].net_idx2 == net_idx2 && brg_tbl[i].addr1 == addr1 && + brg_tbl[i].addr2 == addr2) { + 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 + + return 0; + } + } + + return 0; +} diff --git a/subsys/bluetooth/mesh/brg_cfg.h b/subsys/bluetooth/mesh/brg_cfg.h new file mode 100644 index 000000000000000..eecd44f43bf11d7 --- /dev/null +++ b/subsys/bluetooth/mesh/brg_cfg.h @@ -0,0 +1,64 @@ +/* + * 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_ + +#include +#include +#include + +/** These are internal APIs. They do not sanitize input params. */ + +enum brg_tbl_direction { + BRG_PROHIBITED = 0, + BRG_ADDR1_TO_ADDR2 = 1, + BRG_ADDR1_TF_ADDR2 = 2, + BRG_RFU_MAX = 3, +}; + +#define BRG_NETIDX_NOMATCH 0xFFFF + +/* One row of the bridging table */ +struct brg_tbl_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 brg_cfg_enable_get(void); + +int brg_cfg_enable_set(bool enable); + +void bt_mesh_brg_en_pending_store(void); + +void bt_mesh_brg_tbl_pending_store(void); + +int brg_cfg_init(void); + +int brg_cfg_tbl_reset(void); + +int brg_cfg_tbl_get(const struct brg_tbl_row **rows); + +int brg_cfg_tbl_add(enum brg_tbl_direction direction, uint16_t net_idx1, uint16_t net_idx2, + uint16_t addr1, uint16_t addr2); + +int brg_cfg_tbl_remove(uint16_t net_idx1, uint16_t net_idx2, uint16_t addr1, uint16_t addr2); + +int brg_cfg_tbl_check_start(void); + +int brg_cfg_tbl_check_itr(uint16_t src, uint16_t dst, uint16_t netidx, uint16_t *new_netidx); + +int brg_cfg_tbl_reset(void); + +#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 60aef4bf02c0b78..a8674f9e6245e0a 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) +{ + 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 8ec9c66481ac8c5..24bd7c89e3fce49 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,13 @@ 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_en_pending_store(); + bt_mesh_brg_tbl_pending_store(); + } } void bt_mesh_settings_init(void) diff --git a/subsys/bluetooth/mesh/settings.h b/subsys/bluetooth/mesh/settings.h index bd6db9c3cdc238f..e50820ae61ce2f7 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 000000000000000..d878ad04d50fd68 --- /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 000000000000000..1eea5516d41a969 --- /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 000000000000000..a0f4f1dc2362685 --- /dev/null +++ b/tests/bluetooth/mesh/brg/src/main.c @@ -0,0 +1,430 @@ +/* + * 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; +} + +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 = 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) +{ + 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 = brg_cfg_tbl_remove(test_vector[i].net_idx1, test_vector[i].net_idx2, + test_vector[i].addr1, test_vector[i].addr2); + zassert_equal(err, 0); + } +} + + +static void check_brg_cfg_tbl_reset(void) +{ + int err; + + ztest_expect_data(settings_delete, name, "bt/mesh/brgen"); + ztest_expect_data(settings_delete, name, "bt/mesh/brgbt"); + err = brg_cfg_tbl_reset(); + zassert_equal(err, 0); +} + +/**** Tests ****/ + +ZTEST_SUITE(bt_mesh_brg_cfg, NULL, NULL, setup, NULL, NULL); + +ZTEST(bt_mesh_brg_cfg, test_basic_functionality_storage) +{ + check_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_brg_cfg_tbl_reset(); + check_fill_all_bt_entries(); +} + +ZTEST(bt_mesh_brg_cfg, test_brg_cfg_en) +{ + int err; + + check_brg_cfg_tbl_reset(); + zassert_equal(brg_cfg_enable_get(), false, NULL); + + ztest_expect_value(bt_mesh_settings_store_schedule, flag, + BT_MESH_SETTINGS_BRG_PENDING); + err = brg_cfg_enable_set(true); + zassert_equal(err, 0, NULL); + + zassert_equal(brg_cfg_enable_get(), true, NULL); +} + +/* Test if enable flag is stored correctly */ +ZTEST(bt_mesh_brg_cfg, test_brg_en_pending_store) +{ + static bool val; + + check_brg_cfg_tbl_reset(); + val = brg_cfg_enable_get(); + ztest_expect_data(settings_save_one, name, "bt/mesh/brgen"); + ztest_expect_value(settings_save_one, val_len, 1); + ztest_expect_data(settings_save_one, value, &val); + + bt_mesh_brg_en_pending_store(); +} + +/* Test if pending store works correctly by adding one entry to the table */ +ZTEST(bt_mesh_brg_cfg, test_brg_tbl_pending_store) +{ + check_brg_cfg_tbl_reset(); + + ztest_expect_value(bt_mesh_settings_store_schedule, flag, + BT_MESH_SETTINGS_BRG_PENDING); + + static struct brg_tbl_row test_vec = { + .direction = BRG_ADDR1_TO_ADDR2, + .net_idx1 = 1, + .net_idx2 = 2, + .addr1 = 3, + .addr2 = 4, + }; + + int err = 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); + + ztest_expect_data(settings_save_one, name, "bt/mesh/brgbt"); + ztest_expect_value(settings_save_one, val_len, sizeof(test_vec)); + ztest_expect_data(settings_save_one, value, &test_vec); + + bt_mesh_brg_tbl_pending_store(); +} + + +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. + */ + struct test_brg_cfg_row inv_test_vector[] = { + /* Direction has invalid values */ + {.direction = BRG_PROHIBITED, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 2}, + {.direction = BRG_RFU_MAX, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 2}, + /* Out of range netidx values */ + {.direction = BRG_ADDR1_TO_ADDR2, + .net_idx1 = 4096, .net_idx2 = 1, .addr1 = 1, .addr2 = 2}, + {.direction = BRG_ADDR1_TO_ADDR2, + .net_idx1 = 0, .net_idx2 = 4096, .addr1 = 1, .addr2 = 2}, + /* Same netidx values */ + {.direction = BRG_ADDR1_TO_ADDR2, + .net_idx1 = 0, .net_idx2 = 0, .addr1 = 1, .addr2 = 2}, + /* Same addr values */ + {.direction = BRG_ADDR1_TO_ADDR2, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 1}, + /* Invalid address1 value */ + {.direction = BRG_ADDR1_TO_ADDR2, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 0, .addr2 = 1}, + {.direction = BRG_ADDR1_TO_ADDR2, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 0x8000, .addr2 = 1}, + {.direction = BRG_ADDR1_TO_ADDR2, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 0xC000, .addr2 = 1}, + {.direction = BRG_ADDR1_TO_ADDR2, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 0xFFFE, .addr2 = 1}, + {.direction = BRG_ADDR1_TO_ADDR2, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 0xFFFF, .addr2 = 1}, + /* Invalid address2 values */ + {.direction = BRG_ADDR1_TO_ADDR2, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 0}, + {.direction = BRG_ADDR1_TO_ADDR2, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 0xFFFF}, + {.direction = BRG_ADDR1_TF_ADDR2, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 0x8000}, + {.direction = BRG_ADDR1_TF_ADDR2, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 0xC000}, + {.direction = BRG_ADDR1_TF_ADDR2, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 0xFFFE}, + {.direction = BRG_ADDR1_TF_ADDR2, + .net_idx1 = 0, .net_idx2 = 1, .addr1 = 1, .addr2 = 0xFFFF}, + }; + + check_brg_cfg_tbl_reset(); + + for (int i = 0; i < ARRAY_SIZE(inv_test_vector); i++) { + err = 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 test is written only for the purpose of checking performance on real devices. */ +#if (0) +#define NUM_MSGS (10000) + +static void print_brg_tbl(void) +{ + const struct brg_tbl_row *tbl; + int n = brg_cfg_tbl_get(&tbl); + + for (int i = 0; i < CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX; 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 = 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 = 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 = 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 = 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 test_bridging_performance(bool test_one_way) +{ + int err, idx; + uint16_t new_netidx; + 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*/ + err = brg_cfg_tbl_check_start(); + do { + tick1 = k_uptime_ticks(); + err = brg_cfg_tbl_check_itr(test_vector[idx].addr1, test_vector[idx].addr2, + test_vector[idx].net_idx1, &new_netidx); + ticks += k_uptime_ticks() - tick1; + } while (!err && new_netidx == BRG_NETIDX_NOMATCH); + + if (idx == CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX) { + zassert_equal(err, -ENOENT); + } else { + zassert_equal(err, 0); + zassert_equal(new_netidx, test_vector[idx].net_idx2); + } + + 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 + */ + err = brg_cfg_tbl_check_start(); + do { + tick1 = k_uptime_ticks(); + err = brg_cfg_tbl_check_itr(test_vector[idx].addr2, test_vector[idx].addr1, + test_vector[idx].net_idx2, &new_netidx); + ticks += k_uptime_ticks() - tick1; + } while (!err && new_netidx == BRG_NETIDX_NOMATCH); + + if (idx == CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX) { + zassert_equal(err, -ENOENT); + } else { + if (test_vector[idx].direction == 2) { + zassert_equal(err, 0); + zassert_equal(new_netidx, test_vector[idx].net_idx1); + } else { + zassert_equal(err, -ENOENT); + } + } + } + printk("ticks: %8u us: %u\n", ticks, k_ticks_to_us_floor32(ticks)); +} + +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_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_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_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_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_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_brg_cfg_tbl_reset(); + check_fill_all_bt_entries_randomly(); + print_brg_tbl(); + test_bridging_performance(false); +} +#endif diff --git a/tests/bluetooth/mesh/brg/testcase.yaml b/tests/bluetooth/mesh/brg/testcase.yaml new file mode 100644 index 000000000000000..b94a3d445e226e1 --- /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