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 <omkar.kulkarni@nordicsemi.no>
This commit is contained in:
Omkar Kulkarni 2024-06-25 16:45:50 +02:00 committed by Anas Nashif
commit 092f808ea4
12 changed files with 951 additions and 2 deletions

View file

@ -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"

View file

@ -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)

View file

@ -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

View file

@ -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 <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;
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
}

View file

@ -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_ */

View file

@ -5,6 +5,7 @@
*/
#include <zephyr/bluetooth/mesh.h>
#include "brg_cfg.h"
#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL
#include <zephyr/logging/log.h>
@ -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,
};

View file

@ -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)

View file

@ -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,
};

View file

@ -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)

View file

@ -0,0 +1,3 @@
CONFIG_ZTEST=y
CONFIG_ZTEST_MOCKING=y
CONFIG_BT_MESH_BRG_CFG_SRV=y

View file

@ -0,0 +1,501 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/ztest.h>
#include <zephyr/net/buf.h>
#include <zephyr/bluetooth/mesh.h>
#include <stdlib.h>
#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);
}

View file

@ -0,0 +1,10 @@
tests:
bluetooth.mesh.brg:
platform_allow:
- native_posix
- native_sim
tags:
- bluetooth
- mesh
integration_platforms:
- native_sim