Tests: Bluetooth: Mesh: Add BLOB Server persistent storage BSim tests

These tests stop BLOB Transfer after BLOB Server reaches every phase
by rebooting devices. After restart, Server recovers transfer and client
continues to the next one. On second pass, Server stops after first
block completes and suspends itself, and after reboot continues to
completion.

Signed-off-by: Krzysztof Kopyściński <krzysztof.kopyscinski@codecoup.pl>
This commit is contained in:
Krzysztof Kopyściński 2023-04-07 10:56:45 +02:00 committed by Carles Cufí
parent 0d5c33b045
commit 893de36842
4 changed files with 282 additions and 4 deletions

View file

@ -35,6 +35,7 @@ if(CONFIG_SETTINGS)
if(CONFIG_BT_MESH_V1d1)
target_sources(app PRIVATE
src/test_dfu.c
src/test_blob.c
)
endif()

View file

@ -13,6 +13,7 @@ extern struct bst_test_list *test_rpc_install(struct bst_test_list *tests);
extern struct bst_test_list *test_provision_pst_install(struct bst_test_list *tests);
#if defined(CONFIG_BT_MESH_V1d1)
extern struct bst_test_list *test_dfu_install(struct bst_test_list *test);
extern struct bst_test_list *test_blob_pst_install(struct bst_test_list *test);
#endif /* defined(CONFIG_BT_MESH_V1d1) */
#elif defined(CONFIG_BT_MESH_GATT_PROXY)
extern struct bst_test_list *test_adv_install(struct bst_test_list *test);
@ -44,6 +45,7 @@ bst_test_install_t test_installers[] = {
#if defined(CONFIG_BT_MESH_V1d1)
test_provision_pst_install,
test_dfu_install,
test_blob_pst_install,
#endif /* defined(CONFIG_BT_MESH_V1d1) */
#elif defined(CONFIG_BT_MESH_GATT_PROXY)
test_adv_install,

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include "mesh_test.h"
#include "settings_test_backend.h"
#include "dfu_blob_common.h"
#include "mesh/blob.h"
#include "argparse.h"
@ -26,6 +27,8 @@ static enum {
BLOCK_GET_FAIL = 0,
XFER_GET_FAIL = 1
} msg_fail_type;
static bool recover_settings;
static enum bt_mesh_blob_xfer_phase expected_stop_phase;
static void test_args_parse(int argc, char *argv[])
{
@ -44,6 +47,20 @@ static void test_args_parse(int argc, char *argv[])
.option = "msg-fail-type",
.descript = "Message type to fail on"
},
{
.dest = &expected_stop_phase,
.type = 'i',
.name = "{inactive, start, wait-block, wait-chunk, complete, suspended}",
.option = "expected-phase",
.descript = "Expected DFU Server phase value restored from flash"
},
{
.dest = &recover_settings,
.type = 'b',
.name = "{0, 1}",
.option = "recover",
.descript = "Recover settings from persistent storage"
},
};
bs_args_parse_all_cmd_line(argc, argv, args_struct);
@ -60,6 +77,8 @@ static struct k_sem first_block_wr_sem;
static uint16_t partial_block;
ATOMIC_DEFINE(block_bitfield, 8);
static struct k_sem blob_srv_end_sem;
static int blob_chunk_wr(const struct bt_mesh_blob_io *io,
const struct bt_mesh_blob_xfer *xfer,
const struct bt_mesh_blob_block *block,
@ -79,6 +98,10 @@ static int blob_chunk_wr(const struct bt_mesh_blob_io *io,
k_sem_give(&first_block_wr_sem);
}
if (expected_stop_phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK) {
bt_mesh_scan_disable();
k_sem_give(&blob_srv_end_sem);
}
return 0;
}
@ -92,10 +115,22 @@ static int blob_chunk_rd(const struct bt_mesh_blob_io *io,
return 0;
}
static void blob_block_end(const struct bt_mesh_blob_io *io,
const struct bt_mesh_blob_xfer *xfer,
const struct bt_mesh_blob_block *block)
{
if (expected_stop_phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK ||
expected_stop_phase == BT_MESH_BLOB_XFER_PHASE_SUSPENDED) {
bt_mesh_scan_disable();
k_sem_give(&blob_srv_end_sem);
}
}
static const struct bt_mesh_blob_io blob_io = {
.open = blob_io_open,
.rd = blob_chunk_rd,
.wr = blob_chunk_wr,
.block_end = blob_block_end,
};
static uint8_t dev_key[16] = { 0xdd };
@ -164,8 +199,6 @@ static void blob_srv_suspended(struct bt_mesh_blob_srv *b)
k_sem_give(&blob_srv_suspend_sem);
}
static struct k_sem blob_srv_end_sem;
static void blob_srv_end(struct bt_mesh_blob_srv *b, uint64_t id, bool success)
{
k_sem_give(&blob_srv_end_sem);
@ -174,6 +207,7 @@ static void blob_srv_end(struct bt_mesh_blob_srv *b, uint64_t id, bool success)
static int blob_srv_recover(struct bt_mesh_blob_srv *b, struct bt_mesh_blob_xfer *xfer,
const struct bt_mesh_blob_io **io)
{
*io = &blob_io;
return 0;
}
@ -1373,6 +1407,192 @@ static void test_cli_fail_on_no_rsp(void)
PASS();
}
#if CONFIG_BT_SETTINGS
static void cli_stop_setup(void)
{
bt_mesh_device_setup(&prov, &cli_comp);
(void)target_srv_add(BLOB_CLI_ADDR + 1, true);
blob_cli_inputs_prepare(BLOB_GROUP_ADDR);
blob_cli_xfer.xfer.mode =
is_pull_mode ? BT_MESH_BLOB_XFER_MODE_PULL : BT_MESH_BLOB_XFER_MODE_PUSH;
blob_cli_xfer.xfer.size = CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX * 2;
blob_cli_xfer.xfer.id = 1;
blob_cli_xfer.xfer.block_size_log = 12;
blob_cli_xfer.xfer.chunk_size = 377;
blob_cli_xfer.inputs.timeout_base = 10;
}
static void cli_restore_suspended(void)
{
blob_cli.state = BT_MESH_BLOB_CLI_STATE_SUSPENDED;
blob_cli.inputs = &blob_cli_xfer.inputs;
blob_cli.xfer = &blob_cli_xfer.xfer;
blob_cli_xfer.xfer.id = 1;
blob_cli.io = &blob_io;
bt_mesh_blob_cli_resume(&blob_cli);
}
static void test_cli_stop(void)
{
int err;
if (!recover_settings) {
settings_test_backend_clear();
}
bt_mesh_test_cfg_set(NULL, 1000);
k_sem_init(&blob_caps_sem, 0, 1);
k_sem_init(&lost_target_sem, 0, 1);
k_sem_init(&blob_cli_end_sem, 0, 1);
k_sem_init(&blob_cli_suspend_sem, 0, 1);
switch (expected_stop_phase) {
case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START:
/* Nothing to do on client side in this step,
* just self-provision for future steps
*/
bt_mesh_device_setup(&prov, &cli_comp);
blob_cli_prov_and_conf(BLOB_CLI_ADDR);
break;
case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK:
/* Target will be unresponsive once first block completes */
cli_stop_setup();
err = bt_mesh_blob_cli_send(&blob_cli, &blob_cli_xfer.inputs,
&blob_cli_xfer.xfer, &blob_io);
if (err) {
FAIL("BLOB send failed (err: %d)", err);
}
if (k_sem_take(&blob_cli_suspend_sem, K_SECONDS(750))) {
FAIL("Suspend targets CB did not trigger for all expected lost targets");
}
break;
case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK:
cli_stop_setup();
cli_restore_suspended();
/* This will time out but gives time for server to process all messages */
k_sem_take(&blob_cli_end_sem, K_SECONDS(380));
break;
case BT_MESH_BLOB_XFER_PHASE_COMPLETE:
cli_stop_setup();
cli_restore_suspended();
if (k_sem_take(&blob_cli_end_sem, K_SECONDS(380))) {
FAIL("End CB did not trigger as expected for the cli");
}
ASSERT_TRUE(blob_cli.state == BT_MESH_BLOB_CLI_STATE_NONE);
break;
case BT_MESH_BLOB_XFER_PHASE_SUSPENDED:
/* Server will become unresponsive after receiving first chunk */
cli_stop_setup();
blob_cli_prov_and_conf(BLOB_CLI_ADDR);
err = bt_mesh_blob_cli_send(&blob_cli, &blob_cli_xfer.inputs,
&blob_cli_xfer.xfer, &blob_io);
if (err) {
FAIL("BLOB send failed (err: %d)", err);
}
if (k_sem_take(&blob_cli_suspend_sem, K_SECONDS(750))) {
FAIL("Lost targets CB did not trigger for all expected lost targets");
}
break;
default:
/* There is no use case to stop in Inactive phase */
FAIL();
}
PASS();
}
static void srv_check_reboot_and_continue(void)
{
ASSERT_EQUAL(BT_MESH_BLOB_XFER_PHASE_SUSPENDED, blob_srv.phase);
ASSERT_EQUAL(0, blob_srv.state.ttl);
ASSERT_EQUAL(BLOB_CLI_ADDR, blob_srv.state.cli);
ASSERT_EQUAL(1, blob_srv.state.timeout_base);
ASSERT_TRUE(BT_MESH_TX_SDU_MAX, blob_srv.state.mtu_size);
ASSERT_EQUAL(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX * 2, blob_srv.state.xfer.size);
ASSERT_EQUAL(12, blob_srv.state.xfer.block_size_log);
ASSERT_EQUAL(1, blob_srv.state.xfer.id);
ASSERT_TRUE(blob_srv.state.xfer.mode != BT_MESH_BLOB_XFER_MODE_NONE);
/* First block should be already received, second one pending */
ASSERT_FALSE(atomic_test_bit(blob_srv.state.blocks, 0));
ASSERT_TRUE(atomic_test_bit(blob_srv.state.blocks, 1));
k_sem_take(&blob_srv_end_sem, K_SECONDS(500));
}
static void test_srv_stop(void)
{
if (!recover_settings) {
settings_test_backend_clear();
}
bt_mesh_test_cfg_set(NULL, 1000);
k_sem_init(&blob_srv_end_sem, 0, 1);
k_sem_init(&first_block_wr_sem, 0, 1);
k_sem_init(&blob_srv_suspend_sem, 0, 1);
bt_mesh_device_setup(&prov, &srv_comp);
switch (expected_stop_phase) {
case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START:
blob_srv_prov_and_conf(bt_mesh_test_own_addr_get(BLOB_CLI_ADDR));
bt_mesh_blob_srv_recv(&blob_srv, 1, &blob_io, 0, 1);
ASSERT_EQUAL(BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START, blob_srv.phase);
break;
case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK:
ASSERT_EQUAL(BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START, blob_srv.phase);
ASSERT_OK(blob_srv.state.xfer.mode != BT_MESH_BLOB_XFER_MODE_NONE);
ASSERT_EQUAL(0, blob_srv.state.ttl);
k_sem_take(&blob_srv_end_sem, K_SECONDS(500));
ASSERT_EQUAL(BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK, blob_srv.phase);
break;
case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK:
__fallthrough;
case BT_MESH_BLOB_XFER_PHASE_COMPLETE:
srv_check_reboot_and_continue();
ASSERT_EQUAL(expected_stop_phase, blob_srv.phase);
break;
case BT_MESH_BLOB_XFER_PHASE_SUSPENDED:
/* This state is expected to be reached from freshly started procedure */
ASSERT_EQUAL(BT_MESH_BLOB_XFER_PHASE_INACTIVE, blob_srv.phase);
ASSERT_EQUAL(BT_MESH_BLOB_XFER_MODE_NONE, blob_srv.state.xfer.mode);
ASSERT_EQUAL(BT_MESH_TTL_DEFAULT, blob_srv.state.ttl);
blob_srv_prov_and_conf(bt_mesh_test_own_addr_get(BLOB_CLI_ADDR));
bt_mesh_blob_srv_recv(&blob_srv, 1, &blob_io, 0, 1);
k_sem_take(&blob_srv_suspend_sem, K_SECONDS(140));
ASSERT_EQUAL(BT_MESH_BLOB_XFER_PHASE_SUSPENDED, blob_srv.phase);
break;
default:
/* There is no use case to stop in Inactive phase */
FAIL();
}
PASS();
}
#endif /* CONFIG_BT_SETTINGS */
#define TEST_CASE(role, name, description) \
{ \
.test_id = "blob_" #role "_" #name, \
@ -1415,3 +1635,17 @@ struct bst_test_list *test_blob_install(struct bst_test_list *tests)
tests = bst_add_tests(tests, test_blob);
return tests;
}
#if CONFIG_BT_SETTINGS
static const struct bst_test_instance test_blob_pst[] = {
TEST_CASE(cli, stop,
"Client expecting server to stop after reaching configured phase and continuing"),
TEST_CASE(srv, stop, "Server stopping after reaching configured xfer phase"),
};
struct bst_test_list *test_blob_pst_install(struct bst_test_list *tests)
{
tests = bst_add_tests(tests, test_blob_pst);
return tests;
}
#endif

View file

@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Copyright 2023 Nordic Semiconductor
# SPDX-License-Identifier: Apache-2.0
source $(dirname "${BASH_SOURCE[0]}")/../../_mesh_test.sh
# Note:
# Tests must be added in pairs and in sequence.
# Tests with recover set to 0 clear previous settings and start new procedure.
# Tests with recover set to 1 load stored settings and continue procedure to next phase.
# Test cases are designed to be run using single target.
conf=prj_mesh1d1_conf
overlay=overlay_pst_conf
RunTest blob_recover_phase blob_cli_stop blob_srv_stop -- -argstest \
recover=0 expected-phase=1
conf=prj_mesh1d1_conf
overlay=overlay_pst_conf
RunTest blob_recover_phase blob_cli_stop blob_srv_stop -- -argstest \
recover=1 expected-phase=2
conf=prj_mesh1d1_conf
overlay=overlay_pst_conf
RunTest blob_recover_phase blob_cli_stop blob_srv_stop -- -argstest \
recover=1 expected-phase=3
conf=prj_mesh1d1_conf
overlay=overlay_pst_conf
RunTest blob_recover_phase blob_cli_stop blob_srv_stop -- -argstest \
recover=1 expected-phase=4
# Test reaching suspended state and continuation after reboot on new procedure.
conf=prj_mesh1d1_conf
overlay=overlay_pst_conf
RunTest blob_recover_phase blob_cli_stop blob_srv_stop -- -argstest \
recover=0 expected-phase=5
conf=prj_mesh1d1_conf
overlay=overlay_pst_conf
RunTest blob_recover_phase blob_cli_stop blob_srv_stop -- -argstest \
recover=1 expected-phase=4