From 533638e5d54634476da44b45fa5cbebd650c531e Mon Sep 17 00:00:00 2001 From: Aleksandr Khromykh Date: Wed, 9 Aug 2023 16:52:26 +0200 Subject: [PATCH 1/2] Bluetooth: Mesh: do not send time status often than 30 seconds Commit adds restriction for the Time model to not send Time Status more often than 30 seconds if this is aresult of state update after Time Set or Time Status commands. Signed-off-by: Aleksandr Khromykh --- include/bluetooth/mesh/time_srv.h | 4 +++ subsys/bluetooth/mesh/time_srv.c | 52 ++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/include/bluetooth/mesh/time_srv.h b/include/bluetooth/mesh/time_srv.h index 9c8037eefe6..f1d628d1b58 100644 --- a/include/bluetooth/mesh/time_srv.h +++ b/include/bluetooth/mesh/time_srv.h @@ -86,6 +86,8 @@ struct bt_mesh_time_srv_data { struct bt_mesh_time_tai_utc_change tai_utc_change; /* The Time Role of the Server instance */ enum bt_mesh_time_role role; + /* The timestamp of the last published Time Status. */ + int64_t timestamp; }; /** Time Server instance. @@ -103,6 +105,8 @@ struct bt_mesh_time_srv { struct bt_mesh_msg_ack_ctx ack_ctx; /** Model state structure */ struct bt_mesh_time_srv_data data; + /** Delayable work to randomize status relaying. */ + struct k_work_delayable status_delay; /** @brief Update callback. * diff --git a/subsys/bluetooth/mesh/time_srv.c b/subsys/bluetooth/mesh/time_srv.c index ddcf6f5280d..f0943d05121 100644 --- a/subsys/bluetooth/mesh/time_srv.c +++ b/subsys/bluetooth/mesh/time_srv.c @@ -10,6 +10,7 @@ #include "time_util.h" #define SUBSEC_STEPS 256U +#define STATUS_INTERVAL_MIN 30000ll struct bt_mesh_time_srv_settings_data { uint8_t role; @@ -186,6 +187,14 @@ static int send_time_status(struct bt_mesh_model *model, return bt_mesh_msg_send(model, ctx, &msg); } +static void time_status_send_after_delay(struct k_work *work) +{ + struct k_work_delayable *tmp = k_work_delayable_from_work(work); + struct bt_mesh_time_srv *srv = CONTAINER_OF(tmp, struct bt_mesh_time_srv, status_delay); + + (void)bt_mesh_time_srv_time_status_send(srv, NULL); +} + static int handle_time_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { @@ -217,7 +226,22 @@ static int handle_time_status(struct bt_mesh_model *model, struct bt_mesh_msg_ct } if (srv->data.role == BT_MESH_TIME_RELAY) { - (void)bt_mesh_time_srv_time_status_send(srv, NULL); + /* The time server shouldn't send out statuses more often than every 30 seconds. */ + if (srv->data.sync.uptime < (srv->data.timestamp + STATUS_INTERVAL_MIN)) { + return 0; + } + + /* Random delay has been already scheduled. */ + if (k_work_delayable_is_pending(&srv->status_delay)) { + return 0; + } + + uint8_t rnd; + + bt_rand(&rnd, sizeof(uint8_t)); + rnd = 20 + rnd % 30; + srv->data.sync.status.uncertainty += rnd; + k_work_schedule(&srv->status_delay, K_MSEC(rnd)); } return 0; @@ -243,6 +267,9 @@ static int handle_time_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx * srv->time_update_cb(srv, ctx, BT_MESH_TIME_SRV_SET_UPDATE); } + /* publish state changing */ + (void)bt_mesh_time_srv_time_status_send(srv, NULL); + send_time_status(model, ctx, srv->data.sync.uptime); return 0; @@ -387,18 +414,22 @@ static int bt_mesh_time_srv_init(struct bt_mesh_model *model) struct bt_mesh_time_srv *srv = model->user_data; srv->model = model; + srv->data.timestamp = -STATUS_INTERVAL_MIN; net_buf_simple_init(srv->pub.msg, 0); + k_work_init_delayable(&srv->status_delay, time_status_send_after_delay); + return 0; } static void bt_mesh_time_srv_reset(struct bt_mesh_model *model) { struct bt_mesh_time_srv *srv = model->user_data; - struct bt_mesh_time_srv_data data = { 0 }; + struct bt_mesh_time_srv_data data = { .timestamp = -STATUS_INTERVAL_MIN }; srv->data = data; net_buf_simple_reset(srv->pub.msg); + (void)k_work_cancel_delayable(&srv->status_delay); if (IS_ENABLED(CONFIG_BT_SETTINGS)) { (void)bt_mesh_model_data_store(srv->model, false, NULL, NULL, @@ -452,6 +483,7 @@ int _bt_mesh_time_srv_update_handler(struct bt_mesh_model *model) { struct bt_mesh_time_srv *srv = model->user_data; struct bt_mesh_time_status status; + int64_t uptime; int err; if (srv->data.role != BT_MESH_TIME_AUTHORITY && @@ -459,11 +491,13 @@ int _bt_mesh_time_srv_update_handler(struct bt_mesh_model *model) return -EPERM; } - err = bt_mesh_time_srv_status(srv, k_uptime_get(), &status); + uptime = k_uptime_get(); + err = bt_mesh_time_srv_status(srv, uptime, &status); if (err) { return err; } + srv->data.timestamp = uptime; /* Account for delay in TX processing: */ status.uncertainty += CONFIG_BT_MESH_TIME_MESH_HOP_UNCERTAINTY; @@ -476,7 +510,8 @@ int _bt_mesh_time_srv_update_handler(struct bt_mesh_model *model) int bt_mesh_time_srv_time_status_send(struct bt_mesh_time_srv *srv, struct bt_mesh_msg_ctx *ctx) { - srv->model->pub->ttl = 0; + int64_t uptime = k_uptime_get(); + int err; /** Mesh Model Specification 5.3.1.2.2: * The message (Time Status) may be sent as an unsolicited message at any time @@ -486,7 +521,14 @@ int bt_mesh_time_srv_time_status_send(struct bt_mesh_time_srv *srv, return -EOPNOTSUPP; } - return send_time_status(srv->model, ctx, k_uptime_get()); + srv->model->pub->ttl = 0; + + err = send_time_status(srv->model, ctx, uptime); + if (!err) { + srv->data.timestamp = uptime; + } + + return err; } int bt_mesh_time_srv_status(struct bt_mesh_time_srv *srv, uint64_t uptime, From a9325abfa174bf2845c4d68fbceece0dcbef1116 Mon Sep 17 00:00:00 2001 From: Aleksandr Khromykh Date: Tue, 15 Aug 2023 09:32:23 +0200 Subject: [PATCH 2/2] tests: Bluetooth: Mesh: time status timing of time model Commit adds unit test to check the Time Status timig of the Time model (including time restrictions). Signed-off-by: Aleksandr Khromykh --- .../bluetooth/mesh/time_model/CMakeLists.txt | 39 +++ .../subsys/bluetooth/mesh/time_model/prj.conf | 12 + .../bluetooth/mesh/time_model/src/main.c | 249 ++++++++++++++++++ .../bluetooth/mesh/time_model/testcase.yaml | 6 + 4 files changed, 306 insertions(+) create mode 100644 tests/subsys/bluetooth/mesh/time_model/CMakeLists.txt create mode 100644 tests/subsys/bluetooth/mesh/time_model/prj.conf create mode 100644 tests/subsys/bluetooth/mesh/time_model/src/main.c create mode 100644 tests/subsys/bluetooth/mesh/time_model/testcase.yaml diff --git a/tests/subsys/bluetooth/mesh/time_model/CMakeLists.txt b/tests/subsys/bluetooth/mesh/time_model/CMakeLists.txt new file mode 100644 index 00000000000..a567bb27755 --- /dev/null +++ b/tests/subsys/bluetooth/mesh/time_model/CMakeLists.txt @@ -0,0 +1,39 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(bt_mesh_time_model_test) + +target_include_directories(app PUBLIC + ${ZEPHYR_NRF_MODULE_DIR}/subsys/bluetooth/mesh + ${ZEPHYR_BASE}/subsys/bluetooth + ../common + ) + +FILE(GLOB app_sources src/*.c) + +target_sources(app PRIVATE + ${app_sources} + ${ZEPHYR_NRF_MODULE_DIR}/subsys/bluetooth/mesh/time_srv.c + ${ZEPHYR_NRF_MODULE_DIR}/subsys/bluetooth/mesh/time.c + ${ZEPHYR_NRF_MODULE_DIR}/subsys/bluetooth/mesh/time_util.c + ) + +target_compile_options(app + PRIVATE + -DCONFIG_BT_MESH_MODEL_KEY_COUNT=5 + -DCONFIG_BT_MESH_MODEL_GROUP_COUNT=5 + -DCONFIG_BT_LOG_LEVEL=0 + -DCONFIG_BT_MESH_TIME_SRV=1 + -DCONFIG_BT_MESH_TIME_SRV_CLOCK_ACCURACY=500 + -DCONFIG_BT_MESH_TIME_MESH_HOP_UNCERTAINTY=30 + -DCONFIG_BT_MESH_USES_TINYCRYPT + ) + +zephyr_ld_options( + ${LINKERFLAGPREFIX},--allow-multiple-definition + ) diff --git a/tests/subsys/bluetooth/mesh/time_model/prj.conf b/tests/subsys/bluetooth/mesh/time_model/prj.conf new file mode 100644 index 00000000000..f43b47f76db --- /dev/null +++ b/tests/subsys/bluetooth/mesh/time_model/prj.conf @@ -0,0 +1,12 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Ztest configuration +CONFIG_ZTEST=y +CONFIG_ZTEST_NEW_API=y +CONFIG_TEST_RANDOM_GENERATOR=y + +CONFIG_NET_BUF=y diff --git a/tests/subsys/bluetooth/mesh/time_model/src/main.c b/tests/subsys/bluetooth/mesh/time_model/src/main.c new file mode 100644 index 00000000000..b2a58df8602 --- /dev/null +++ b/tests/subsys/bluetooth/mesh/time_model/src/main.c @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include "time_internal.h" +#include "time_util.h" + +#define UNCERTAINTY_STEP_MS 10 +#define SUBSEC_STEPS 256U +#define STATUS_INTERVAL_MIN 30000ul +#define TEST_TIME 100000ul + +static struct bt_mesh_time_srv time_srv = BT_MESH_TIME_SRV_INIT(NULL); + +static struct bt_mesh_elem mock_elem[] = { + BT_MESH_ELEM(1, BT_MESH_MODEL_LIST(BT_MESH_MODEL_TIME_SRV(&time_srv)), BT_MESH_MODEL_NONE)}; + +struct mock_comp_data { + size_t elem_count; + struct bt_mesh_elem *elem; +} mock_comp = {.elem_count = 1, .elem = mock_elem}; + +static struct bt_mesh_msg_ctx test_ctx = { + .addr = 0x0001, + .send_ttl = 0, +}; + +static const struct bt_mesh_time_zone_change zone_change = {.new_offset = 3, .timestamp = 30}; + +static const struct bt_mesh_time_tai_utc_change tai_utc_change = {.delta_new = 4, .timestamp = 40}; + +static struct bt_mesh_time_status expected_status; +static uint32_t time_status_rx_number; +static bool is_randomized; +static bool is_unsolicited; + +static void tc_setup(void *f) +{ + struct bt_mesh_time_tai tai; + struct tm start_tm = { + /* 1st of Jan 2010 */ + .tm_year = 110, .tm_mon = 0, .tm_mday = 1, .tm_wday = 5, + .tm_hour = 0, .tm_min = 0, .tm_sec = 0, + }; + + time_status_rx_number = 0; + zassert_not_null(_bt_mesh_time_srv_cb.init, "Init cb is null"); + _bt_mesh_time_srv_cb.init(mock_elem[0].models); + + bt_mesh_time_srv_time_zone_change_set(&time_srv, &zone_change); + bt_mesh_time_srv_tai_utc_change_set(&time_srv, &tai_utc_change); + bt_mesh_time_srv_role_set(&time_srv, BT_MESH_TIME_RELAY); + + zassert_ok(ts_to_tai(&tai, &start_tm), "cannot convert tai time"); + time_srv.data.sync.uptime = k_uptime_get(); + time_srv.data.sync.status.tai = tai; +} + +static void tc_teardown(void *f) +{ + zassert_not_null(_bt_mesh_time_srv_cb.reset, "Reset cb is null"); + _bt_mesh_time_srv_cb.reset(mock_elem[0].models); +} + +int bt_mesh_model_extend(struct bt_mesh_model *extending_mod, struct bt_mesh_model *base_mod) +{ + return 0; +} + +void bt_mesh_model_msg_init(struct net_buf_simple *msg, uint32_t opcode) +{ +} + +int bt_mesh_msg_send(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_time_status status; + uint64_t expected_uncertity = + (expected_status.uncertainty + CONFIG_BT_MESH_TIME_MESH_HOP_UNCERTAINTY) / + UNCERTAINTY_STEP_MS; + int64_t steps = + (SUBSEC_STEPS * (k_uptime_get() - time_srv.data.sync.uptime)) / MSEC_PER_SEC; + + zassert_equal_ptr(time_srv.model, model); + + if (is_randomized) { + zassert_is_null(ctx); + } else { + if (is_unsolicited) { + zassert_is_null(ctx); + } else { + zassert_not_null(ctx); + } + is_unsolicited = !is_unsolicited; + } + + bt_mesh_time_decode_time_params(buf, &status); + + zassert_equal(status.tai.sec, expected_status.tai.sec); + zassert_equal(status.tai.subsec, expected_status.tai.subsec + steps); + zassert_equal(status.tai_utc_delta, expected_status.tai_utc_delta); + zassert_equal(status.is_authority, expected_status.is_authority); + zassert_equal(status.time_zone_offset, expected_status.time_zone_offset); + + if (is_randomized) { + zassert_between_inclusive(status.uncertainty, + expected_uncertity * UNCERTAINTY_STEP_MS + 20, + expected_uncertity * UNCERTAINTY_STEP_MS + 50); + } else { + zassert_equal(status.uncertainty, expected_uncertity * UNCERTAINTY_STEP_MS); + } + + time_status_rx_number++; + + return 0; +} + +int bt_mesh_model_send(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *msg, const struct bt_mesh_send_cb *cb, void *cb_data) +{ + return 0; +} + +int bt_rand(void *buf, size_t len) +{ + sys_rand_get(buf, len); + return 0; +} + +static void rx_time_status(void) +{ + /* Create a time status */ + BT_MESH_MODEL_BUF_DEFINE(msg, BT_MESH_TIME_OP_TIME_STATUS, + BT_MESH_TIME_MSG_MAXLEN_TIME_STATUS); + bt_mesh_time_srv_status(&time_srv, k_uptime_get(), &expected_status); + bt_mesh_time_encode_time_params(&msg, &expected_status); + + _bt_mesh_time_srv_op[1].func(time_srv.model, &test_ctx, &msg); + + k_sleep(K_SECONDS(1)); +} + +static void rx_time_set(void) +{ + /* Create a time set */ + BT_MESH_MODEL_BUF_DEFINE(msg, BT_MESH_TIME_OP_TIME_SET, BT_MESH_TIME_MSG_LEN_TIME_SET); + bt_mesh_time_srv_status(&time_srv, k_uptime_get(), &expected_status); + bt_mesh_time_encode_time_params(&msg, &expected_status); + + _bt_mesh_time_setup_srv_op[0].func(time_srv.model, &test_ctx, &msg); + + k_sleep(K_SECONDS(1)); +} + +/** Test scenario: Time Relay receives Time Status every second + * during 100 seconds. Expectation: device will relay only 4 Time Statuses + * with randomization. + */ +ZTEST(time_model, test_time_status_timing) +{ + int64_t start_time = k_uptime_get(); + + is_randomized = true; + + while (k_uptime_get() - start_time < TEST_TIME) { + rx_time_status(); + } + + zassert_equal(TEST_TIME / STATUS_INTERVAL_MIN + 1, time_status_rx_number); +} + +/** Test scenario: Time Relay receives Time Set every second + * during 100 seconds. Expectation: device will publish Time Status with publication settings + * and response Time Status to sender every received Time Set message without randomization. + */ +ZTEST(time_model, test_time_set_timing) +{ + int64_t start_time = k_uptime_get(); + + is_randomized = false; + is_unsolicited = true; + + while (k_uptime_get() - start_time < TEST_TIME) { + rx_time_set(); + } + + zassert_equal(2 * TEST_TIME / 1000, time_status_rx_number); +} + +/** Test scenario: Time Relay receives Time Status once. Then it receives Time Set and then + * it receives Time Status very second during 100 seconds. + * Expectation: previously received Time Status doesn't impact on Time Set replies both + * publishing and response. However, Time Set published reply triggers 30 seconds delay + * for next Time Status handler (Time Relay relays only 3 Time Statuses instead of 4). + */ +ZTEST(time_model, test_time_set_status_mix) +{ + is_randomized = true; + + rx_time_status(); + zassert_equal(1, time_status_rx_number); + + is_randomized = false; + is_unsolicited = true; + time_status_rx_number = 0; + + rx_time_set(); + zassert_equal(2, time_status_rx_number); + + int64_t start_time = k_uptime_get(); + + is_randomized = true; + time_status_rx_number = 0; + + while (k_uptime_get() - start_time < TEST_TIME) { + rx_time_status(); + } + + zassert_equal(TEST_TIME / STATUS_INTERVAL_MIN, time_status_rx_number); +} + +/** Test scenario: Time Relay publishes Time Status over periodic publishing handler. + * Then it receives Time Status every second during 100 seconds. + * Expectation: preiodic publishing triggers 30 seconds delay and device relays + * 3 statuses instead of 4. + */ +ZTEST(time_model, test_time_periodic_pub_status_mix) +{ + zassert_ok(_bt_mesh_time_srv_update_handler(mock_elem[0].models)); + + int64_t start_time = k_uptime_get(); + + is_randomized = true; + + while (k_uptime_get() - start_time < TEST_TIME) { + rx_time_status(); + } + + zassert_equal(TEST_TIME / STATUS_INTERVAL_MIN, time_status_rx_number); +} + +ZTEST_SUITE(time_model, NULL, NULL, tc_setup, tc_teardown, NULL); diff --git a/tests/subsys/bluetooth/mesh/time_model/testcase.yaml b/tests/subsys/bluetooth/mesh/time_model/testcase.yaml new file mode 100644 index 00000000000..545b471cc13 --- /dev/null +++ b/tests/subsys/bluetooth/mesh/time_model/testcase.yaml @@ -0,0 +1,6 @@ +tests: + bluetooth.mesh.time_model: + platform_allow: native_posix + tags: bluetooth ci_build + integration_platforms: + - native_posix