blob: fe6deceaad73239b9f913479dace03d45e19bc6b [file] [log] [blame]
/*
* Copyright (c) 2022 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mesh_test.h"
#include "settings_test_backend.h"
#include "dfu_blob_common.h"
#include "friendship_common.h"
#include "mesh/blob.h"
#include "argparse.h"
#include "mesh/adv.h"
#define LOG_MODULE_NAME test_blob
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME, LOG_LEVEL_INF);
#define BLOB_GROUP_ADDR 0xc000
#define BLOB_CLI_ADDR 0x0001
#define SYNC_CHAN 0
#define CLI_DEV 0
#define SRV1_DEV 1
#define IMPOSTER_MODEL_ID 0xe000
static bool is_pull_mode;
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[])
{
bs_args_struct_t args_struct[] = {
{
.dest = &is_pull_mode,
.type = 'b',
.name = "{0, 1}",
.option = "use-pull-mode",
.descript = "Set transfer type to pull mode"
},
{
.dest = &msg_fail_type,
.type = 'i',
.name = "{0, 1}",
.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);
}
static int blob_io_open(const struct bt_mesh_blob_io *io,
const struct bt_mesh_blob_xfer *xfer,
enum bt_mesh_blob_io_mode mode)
{
return 0;
}
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,
const struct bt_mesh_blob_chunk *chunk)
{
partial_block += chunk->size;
ASSERT_TRUE(partial_block <= block->size, "Received block is too large");
if (partial_block == block->size) {
partial_block = 0;
ASSERT_FALSE(atomic_test_and_set_bit(block_bitfield, block->number),
"Received duplicate block");
}
if (atomic_test_bit(block_bitfield, 0)) {
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;
}
static int blob_chunk_rd(const struct bt_mesh_blob_io *io,
const struct bt_mesh_blob_xfer *xfer,
const struct bt_mesh_blob_block *block,
const struct bt_mesh_blob_chunk *chunk)
{
memset(chunk->data, 0, chunk->size);
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 };
static uint8_t app_key[16] = { 0xaa };
static uint8_t net_key[16] = { 0xcc };
static struct bt_mesh_prov prov;
static struct {
struct bt_mesh_blob_cli_inputs inputs;
struct bt_mesh_blob_target targets[5];
struct bt_mesh_blob_target_pull pull[5];
uint8_t target_count;
struct bt_mesh_blob_xfer xfer;
} blob_cli_xfer;
static struct k_sem blob_caps_sem;
static void blob_cli_caps(struct bt_mesh_blob_cli *b, const struct bt_mesh_blob_cli_caps *caps)
{
k_sem_give(&blob_caps_sem);
if (caps) {
ASSERT_EQUAL(caps->mtu_size, BT_MESH_RX_SDU_MAX - BT_MESH_MIC_SHORT);
ASSERT_EQUAL(caps->modes, BT_MESH_BLOB_XFER_MODE_ALL);
ASSERT_EQUAL(caps->max_size, CONFIG_BT_MESH_BLOB_SIZE_MAX);
ASSERT_EQUAL(caps->min_block_size_log, BLOB_BLOCK_SIZE_LOG_MIN);
ASSERT_EQUAL(caps->max_block_size_log, BLOB_BLOCK_SIZE_LOG_MAX);
ASSERT_EQUAL(caps->max_chunk_size, BLOB_CHUNK_SIZE_MAX(BT_MESH_RX_SDU_MAX));
ASSERT_EQUAL(caps->max_chunks, CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX);
}
}
static struct k_sem lost_target_sem;
static void blob_cli_lost_target(struct bt_mesh_blob_cli *b, struct bt_mesh_blob_target *blobt,
enum bt_mesh_blob_status reason)
{
ASSERT_FALSE(reason == BT_MESH_BLOB_SUCCESS);
ASSERT_TRUE(lost_target_find_and_remove(blobt->addr));
if (!lost_targets_rem()) {
k_sem_give(&lost_target_sem);
}
}
static struct k_sem blob_cli_suspend_sem;
static void blob_cli_suspended(struct bt_mesh_blob_cli *b)
{
k_sem_give(&blob_cli_suspend_sem);
}
static struct k_sem blob_cli_end_sem;
static bool cli_end_success;
static void blob_cli_end(struct bt_mesh_blob_cli *b, const struct bt_mesh_blob_xfer *xfer,
bool success)
{
cli_end_success = success;
k_sem_give(&blob_cli_end_sem);
}
static struct k_sem blob_srv_suspend_sem;
static void blob_srv_suspended(struct bt_mesh_blob_srv *b)
{
k_sem_give(&blob_srv_suspend_sem);
}
static void blob_srv_end(struct bt_mesh_blob_srv *b, uint64_t id, bool success)
{
k_sem_give(&blob_srv_end_sem);
}
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;
}
static int blob_srv_start(struct bt_mesh_blob_srv *srv, struct bt_mesh_msg_ctx *ctx,
struct bt_mesh_blob_xfer *xfer)
{
return 0;
}
static void blob_srv_resume(struct bt_mesh_blob_srv *srv)
{
}
static const struct bt_mesh_blob_srv_cb blob_srv_cb = {
.suspended = blob_srv_suspended,
.end = blob_srv_end,
.recover = blob_srv_recover,
.start = blob_srv_start,
.resume = blob_srv_resume,
};
static const struct bt_mesh_blob_cli_cb blob_cli_handlers = {
.caps = blob_cli_caps,
.lost_target = blob_cli_lost_target,
.suspended = blob_cli_suspended,
.end = blob_cli_end,
};
static struct bt_mesh_blob_srv blob_srv = { .cb = &blob_srv_cb };
static struct bt_mesh_blob_cli blob_cli = { .cb = &blob_cli_handlers };
static struct bt_mesh_cfg_cli cfg_cli;
static struct bt_mesh_sar_cfg_cli sar_cfg_cli;
static const struct bt_mesh_comp srv_comp = {
.elem =
(struct bt_mesh_elem[]){
BT_MESH_ELEM(1,
MODEL_LIST(BT_MESH_MODEL_CFG_SRV,
BT_MESH_MODEL_CFG_CLI(&cfg_cli),
BT_MESH_MODEL_SAR_CFG_SRV,
BT_MESH_MODEL_SAR_CFG_CLI(&sar_cfg_cli),
BT_MESH_MODEL_BLOB_SRV(&blob_srv)),
BT_MESH_MODEL_NONE),
},
.elem_count = 1,
};
static const struct bt_mesh_comp cli_comp = {
.elem =
(struct bt_mesh_elem[]){
BT_MESH_ELEM(1,
MODEL_LIST(BT_MESH_MODEL_CFG_SRV,
BT_MESH_MODEL_CFG_CLI(&cfg_cli),
BT_MESH_MODEL_SAR_CFG_SRV,
BT_MESH_MODEL_SAR_CFG_CLI(&sar_cfg_cli),
BT_MESH_MODEL_BLOB_CLI(&blob_cli)),
BT_MESH_MODEL_NONE),
},
.elem_count = 1,
};
static struct k_sem info_get_sem;
static int mock_handle_info_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
k_sem_give(&info_get_sem);
return 0;
}
static const struct bt_mesh_model_op model_op1[] = {
{ BT_MESH_BLOB_OP_INFO_GET, 0, mock_handle_info_get },
BT_MESH_MODEL_OP_END
};
static const struct bt_mesh_comp none_rsp_srv_comp = {
.elem =
(struct bt_mesh_elem[]){
BT_MESH_ELEM(1,
MODEL_LIST(BT_MESH_MODEL_CFG_SRV,
BT_MESH_MODEL_CFG_CLI(&cfg_cli),
BT_MESH_MODEL_SAR_CFG_SRV,
BT_MESH_MODEL_SAR_CFG_CLI(&sar_cfg_cli),
BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_BLOB_SRV,
model_op1, NULL, NULL, NULL)),
BT_MESH_MODEL_NONE),
},
.elem_count = 1,
};
static void provision(uint16_t addr)
{
int err;
err = bt_mesh_provision(net_key, 0, 0, 0, addr, dev_key);
if (err) {
FAIL("Provisioning failed (err %d)", err);
return;
}
}
static void common_configure(uint16_t addr)
{
uint8_t status;
int err;
err = bt_mesh_cfg_cli_app_key_add(0, addr, 0, 0, app_key, &status);
if (err || status) {
FAIL("AppKey add failed (err %d, status %u)", err, status);
return;
}
}
static void blob_srv_prov_and_conf(uint16_t addr)
{
uint8_t status;
int err;
provision(addr);
common_configure(addr);
err = bt_mesh_cfg_cli_mod_app_bind(0, addr, addr, 0, BT_MESH_MODEL_ID_BLOB_SRV, &status);
if (err || status) {
FAIL("Model %#4x bind failed (err %d, status %u)", BT_MESH_MODEL_ID_BLOB_SRV, err,
status);
return;
}
err = bt_mesh_cfg_cli_mod_sub_add(0, addr, addr, BLOB_GROUP_ADDR, BT_MESH_MODEL_ID_BLOB_SRV,
&status);
if (err || status) {
FAIL("Model %#4x sub add failed (err %d, status %u)", BT_MESH_MODEL_ID_BLOB_SRV,
err, status);
return;
}
common_sar_conf(addr);
}
static void blob_cli_prov_and_conf(uint16_t addr)
{
uint8_t status;
int err;
provision(addr);
common_configure(addr);
err = bt_mesh_cfg_cli_mod_app_bind(0, addr, addr, 0, BT_MESH_MODEL_ID_BLOB_CLI, &status);
if (err || status) {
FAIL("Model %#4x bind failed (err %d, status %u)", BT_MESH_MODEL_ID_BLOB_CLI, err,
status);
return;
}
common_sar_conf(addr);
}
static void blob_cli_inputs_prepare(uint16_t group)
{
blob_cli_xfer.inputs.ttl = BT_MESH_TTL_DEFAULT;
blob_cli_xfer.inputs.group = group;
blob_cli_xfer.inputs.app_idx = 0;
sys_slist_init(&blob_cli_xfer.inputs.targets);
for (int i = 0; i < blob_cli_xfer.target_count; ++i) {
/* Reset target context. */
uint16_t addr = blob_cli_xfer.targets[i].addr;
memset(&blob_cli_xfer.targets[i], 0, sizeof(struct bt_mesh_blob_target));
blob_cli_xfer.targets[i].addr = addr;
blob_cli_xfer.targets[i].pull = &blob_cli_xfer.pull[i];
sys_slist_append(&blob_cli_xfer.inputs.targets, &blob_cli_xfer.targets[i].n);
}
}
static struct bt_mesh_blob_target *target_srv_add(uint16_t addr, bool expect_lost)
{
if (expect_lost) {
lost_target_add(addr);
}
ASSERT_TRUE(blob_cli_xfer.target_count < ARRAY_SIZE(blob_cli_xfer.targets));
struct bt_mesh_blob_target *t = &blob_cli_xfer.targets[blob_cli_xfer.target_count];
t->addr = addr;
blob_cli_xfer.target_count++;
return t;
}
static void cli_caps_common_procedure(bool lost_targets)
{
int err;
bt_mesh_test_cfg_set(NULL, 60);
bt_mesh_device_setup(&prov, &cli_comp);
blob_cli_prov_and_conf(BLOB_CLI_ADDR);
blob_cli_inputs_prepare(BLOB_GROUP_ADDR);
k_sem_init(&blob_caps_sem, 0, 1);
k_sem_init(&lost_target_sem, 0, 1);
err = bt_mesh_blob_cli_caps_get(&blob_cli, &blob_cli_xfer.inputs);
if (err) {
FAIL("Boundary check start failed (err: %d)", err);
}
if (lost_targets) {
if (k_sem_take(&lost_target_sem, K_SECONDS(60))) {
FAIL("Lost targets CB did not trigger for all expected lost targets");
}
}
if (k_sem_take(&blob_caps_sem, K_SECONDS(60))) {
FAIL("Caps CB did not trigger at the end of caps procedure");
}
}
static void test_cli_caps_all_rsp(void)
{
struct bt_mesh_blob_target *srv1 = target_srv_add(BLOB_CLI_ADDR + 1, false);
struct bt_mesh_blob_target *srv2 = target_srv_add(BLOB_CLI_ADDR + 2, false);
cli_caps_common_procedure(false);
ASSERT_TRUE(srv1->acked);
ASSERT_FALSE(srv1->timedout);
ASSERT_TRUE(srv2->acked);
ASSERT_FALSE(srv2->timedout);
PASS();
}
static void test_cli_caps_partial_rsp(void)
{
struct bt_mesh_blob_target *srv1 = target_srv_add(BLOB_CLI_ADDR + 1, false);
struct bt_mesh_blob_target *srv2 = target_srv_add(BLOB_CLI_ADDR + 2, true);
cli_caps_common_procedure(true);
ASSERT_TRUE(srv1->acked);
ASSERT_FALSE(srv1->timedout);
ASSERT_FALSE(srv2->acked);
ASSERT_TRUE(srv2->timedout);
PASS();
}
static void test_cli_caps_no_rsp(void)
{
struct bt_mesh_blob_target *srv1 = target_srv_add(BLOB_CLI_ADDR + 1, true);
struct bt_mesh_blob_target *srv2 = target_srv_add(BLOB_CLI_ADDR + 2, true);
cli_caps_common_procedure(true);
ASSERT_FALSE(srv1->acked);
ASSERT_TRUE(srv1->timedout);
ASSERT_FALSE(srv2->acked);
ASSERT_TRUE(srv2->timedout);
PASS();
}
static void test_cli_caps_cancelled(void)
{
int err;
bt_mesh_test_cfg_set(NULL, 300);
bt_mesh_device_setup(&prov, &cli_comp);
blob_cli_prov_and_conf(BLOB_CLI_ADDR);
struct bt_mesh_blob_target *srv1 = target_srv_add(BLOB_CLI_ADDR + 1, false);
struct bt_mesh_blob_target *srv2 = target_srv_add(BLOB_CLI_ADDR + 2, true);
blob_cli_inputs_prepare(BLOB_GROUP_ADDR);
k_sem_init(&blob_caps_sem, 0, 1);
k_sem_init(&lost_target_sem, 0, 1);
/* Start first caps procedure */
err = bt_mesh_blob_cli_caps_get(&blob_cli, &blob_cli_xfer.inputs);
if (err) {
FAIL("Boundary check start failed (err: %d)", err);
}
/* Let first caps procedure run for a little while */
k_sleep(K_SECONDS(15));
/* Cancel first caps procedure */
bt_mesh_blob_cli_cancel(&blob_cli);
ASSERT_EQUAL(blob_cli.state, BT_MESH_BLOB_CLI_STATE_NONE);
/* Wait and assure that caps procedure is canceled */
if (!k_sem_take(&blob_caps_sem, K_SECONDS(60))) {
FAIL("Caps CB triggered unexpectedly");
}
/* Expect that the responsive srv responded, while the */
/* unresponsive srv has not yet timed out due to cancel call */
ASSERT_TRUE(srv1->acked);
ASSERT_FALSE(srv1->timedout);
ASSERT_FALSE(srv2->acked);
ASSERT_FALSE(srv2->timedout);
/* Start second caps procedure and verify that it completes as expected*/
blob_cli_inputs_prepare(BLOB_GROUP_ADDR);
err = bt_mesh_blob_cli_caps_get(&blob_cli, &blob_cli_xfer.inputs);
if (err) {
FAIL("Boundary check start failed (err: %d)", err);
}
if (k_sem_take(&blob_caps_sem, K_SECONDS(60))) {
FAIL("Caps CB did not trigger at the end of second caps procedure");
}
if (k_sem_take(&lost_target_sem, K_NO_WAIT)) {
FAIL("Lost targets CB did not trigger for all expeted lost targets");
}
ASSERT_TRUE(srv1->acked);
ASSERT_FALSE(srv1->timedout);
ASSERT_FALSE(srv2->acked);
ASSERT_TRUE(srv2->timedout);
PASS();
}
static void test_srv_caps_standard(void)
{
bt_mesh_test_cfg_set(NULL, 140);
bt_mesh_device_setup(&prov, &srv_comp);
blob_srv_prov_and_conf(bt_mesh_test_own_addr_get(BLOB_CLI_ADDR));
PASS();
}
static void test_srv_caps_no_rsp(void)
{
bt_mesh_test_cfg_set(NULL, 60);
bt_mesh_device_setup(&prov, &none_rsp_srv_comp);
blob_srv_prov_and_conf(bt_mesh_test_own_addr_get(BLOB_CLI_ADDR));
k_sem_init(&info_get_sem, 0, 1);
/* Checks that the client performs correct amount of retransmit attempts */
for (size_t j = 0; j < CONFIG_BT_MESH_BLOB_CLI_BLOCK_RETRIES; j++) {
int err = k_sem_take(&info_get_sem, K_SECONDS(15));
if (err) {
FAIL("Failed to receive expected number of info get messages from cli"
"(expected: %d, got %d)", CONFIG_BT_MESH_BLOB_CLI_BLOCK_RETRIES, j);
}
}
PASS();
}
static struct k_sem blob_broad_send_sem;
static bool broadcast_tx_complete_auto;
static void broadcast_send(struct bt_mesh_blob_cli *b, uint16_t dst)
{
ASSERT_EQUAL(BLOB_GROUP_ADDR, dst);
k_sem_give(&blob_broad_send_sem);
if (broadcast_tx_complete_auto) {
/* Mocks completion of transmission to trigger retransmit timer */
blob_cli_broadcast_tx_complete(&blob_cli);
}
}
static struct k_sem blob_broad_next_sem;
static void broadcast_next(struct bt_mesh_blob_cli *b)
{
k_sem_give(&blob_broad_next_sem);
}
static void test_cli_broadcast_basic(void)
{
bt_mesh_test_cfg_set(NULL, 300);
bt_mesh_device_setup(&prov, &cli_comp);
blob_cli_prov_and_conf(BLOB_CLI_ADDR);
struct bt_mesh_blob_target *srv1 = target_srv_add(BLOB_CLI_ADDR + 1, false);
struct bt_mesh_blob_target *srv2 = target_srv_add(BLOB_CLI_ADDR + 2, false);
struct blob_cli_broadcast_ctx tx = {
.send = broadcast_send,
.next = broadcast_next,
.acked = true,
.optional = false,
};
broadcast_tx_complete_auto = false;
k_sem_init(&blob_broad_send_sem, 0, 1);
k_sem_init(&blob_broad_next_sem, 0, 1);
blob_cli.inputs = &blob_cli_xfer.inputs;
blob_cli_inputs_prepare(BLOB_GROUP_ADDR);
/* Call broadcast and expect send CB to trigger */
blob_cli_broadcast(&blob_cli, &tx);
if (k_sem_take(&blob_broad_send_sem, K_SECONDS(15))) {
FAIL("Broadcast did not trigger send CB");
}
ASSERT_FALSE(srv1->acked);
ASSERT_FALSE(srv2->acked);
/* Run tx complete with two missing responses */
blob_cli_broadcast_tx_complete(&blob_cli);
if (k_sem_take(&blob_broad_send_sem, K_SECONDS(15))) {
FAIL("Tx_complete did not trigger send CB after timeout");
}
ASSERT_FALSE(srv1->acked);
ASSERT_FALSE(srv2->acked);
/* Mock rsp from first target server */
/* Run tx complete with one missing response */
blob_cli_broadcast_rsp(&blob_cli, srv1);
blob_cli_broadcast_tx_complete(&blob_cli);
if (k_sem_take(&blob_broad_send_sem, K_SECONDS(15))) {
FAIL("Tx_complete did not trigger send CB after timeout");
}
ASSERT_TRUE(srv1->acked);
ASSERT_FALSE(srv2->acked);
/* Mock rsp from second target server */
/* Run tx complete with response from all targets */
blob_cli_broadcast_tx_complete(&blob_cli);
blob_cli_broadcast_rsp(&blob_cli, srv2);
if (k_sem_take(&blob_broad_next_sem, K_SECONDS(15))) {
FAIL("Tx_complete did not trigger next CB after timeout");
}
ASSERT_TRUE(srv1->acked);
ASSERT_TRUE(srv2->acked);
/* Verify that a single broadcast call triggers a single send CB */
k_sem_init(&blob_broad_send_sem, 0, 2);
(void)target_srv_add(BLOB_CLI_ADDR + 3, false);
blob_cli_inputs_prepare(BLOB_GROUP_ADDR);
blob_cli_broadcast(&blob_cli, &tx);
k_sleep(K_SECONDS(80));
ASSERT_EQUAL(k_sem_count_get(&blob_broad_send_sem), 1);
PASS();
}
static void test_cli_broadcast_trans(void)
{
bt_mesh_test_cfg_set(NULL, 150);
bt_mesh_device_setup(&prov, &cli_comp);
blob_cli_prov_and_conf(BLOB_CLI_ADDR);
struct bt_mesh_blob_target *srv1 = target_srv_add(BLOB_CLI_ADDR + 1, true);
struct blob_cli_broadcast_ctx tx = {
.send = broadcast_send,
.next = broadcast_next,
.acked = true,
.optional = false
};
broadcast_tx_complete_auto = true;
k_sem_init(&blob_broad_send_sem, 0, 1);
k_sem_init(&blob_broad_next_sem, 0, 1);
k_sem_init(&lost_target_sem, 0, 1);
blob_cli.inputs = &blob_cli_xfer.inputs;
/* Run acked broadcast */
blob_cli_inputs_prepare(BLOB_GROUP_ADDR);
blob_cli_broadcast(&blob_cli, &tx);
/* Checks that the client performs correct amount of retransmit attempts */
for (size_t j = 0; j < CONFIG_BT_MESH_BLOB_CLI_BLOCK_RETRIES; j++) {
if (k_sem_take(&blob_broad_send_sem, K_SECONDS(15))) {
FAIL("Wrong number of attempted transmissions from blob cli"
"(expected: %d, got %d)",
CONFIG_BT_MESH_BLOB_CLI_BLOCK_RETRIES, j);
}
}
if (k_sem_take(&blob_broad_next_sem, K_SECONDS(15))) {
FAIL("Broadcast did not trigger next CB after retransmisson ran out of attempts");
}
if (k_sem_take(&lost_target_sem, K_NO_WAIT)) {
FAIL("Lost targets CB did not trigger for all expected lost targets");
}
ASSERT_TRUE(srv1->timedout);
/* Re-run with unacked broadcast */
tx.acked = false;
blob_cli_inputs_prepare(BLOB_GROUP_ADDR);
/* Call broadcast and expect send CB to trigger once*/
blob_cli_broadcast(&blob_cli, &tx);
if (k_sem_take(&blob_broad_send_sem, K_NO_WAIT)) {
FAIL("Broadcast did not trigger send CB");
}
if (k_sem_take(&blob_broad_next_sem, K_SECONDS(1))) {
FAIL("Broadcast did not trigger next CB");
}
/* Lost target CB should not trigger for unacked broadcast */
if (!k_sem_take(&lost_target_sem, K_NO_WAIT)) {
FAIL("Lost targets CB triggered unexpectedly");
}
ASSERT_FALSE(srv1->timedout);
/* Re-run with optional flag */
tx.acked = true;
tx.optional = true;
blob_cli_inputs_prepare(BLOB_GROUP_ADDR);
blob_cli_broadcast(&blob_cli, &tx);
for (size_t j = 0; j < CONFIG_BT_MESH_BLOB_CLI_BLOCK_RETRIES; j++) {
if (k_sem_take(&blob_broad_send_sem, K_SECONDS(15))) {
FAIL("Wrong number of attempted transmissions from blob cli"
"(expected: %d, got %d)",
CONFIG_BT_MESH_BLOB_CLI_BLOCK_RETRIES, j);
}
}
if (k_sem_take(&blob_broad_next_sem, K_SECONDS(15))) {
FAIL("Broadcast did not trigger next CB");
}
/* Lost target CB should not trigger for optional broadcast */
if (!k_sem_take(&lost_target_sem, K_NO_WAIT)) {
FAIL("Lost targets CB triggered unexpectedly");
}
ASSERT_FALSE(srv1->timedout);
PASS();
}
static uint16_t dst_addr_last;
static struct k_sem blob_broad_send_uni_sem;
static void broadcast_uni_send(struct bt_mesh_blob_cli *b, uint16_t dst)
{
dst_addr_last = dst;
k_sem_give(&blob_broad_send_uni_sem);
if (broadcast_tx_complete_auto) {
/* Mocks completion of transmission to trigger retransmit timer */
blob_cli_broadcast_tx_complete(&blob_cli);
}
}
static void test_cli_broadcast_unicast_seq(void)
{
bt_mesh_test_cfg_set(NULL, 60);
bt_mesh_device_setup(&prov, &cli_comp);
blob_cli_prov_and_conf(BLOB_CLI_ADDR);
struct bt_mesh_blob_target *srv1 = target_srv_add(BLOB_CLI_ADDR + 1, false);
struct bt_mesh_blob_target *srv2 = target_srv_add(BLOB_CLI_ADDR + 2, false);
struct blob_cli_broadcast_ctx tx = {
.send = broadcast_uni_send,
.next = broadcast_next,
.acked = true,
.optional = false,
};
k_sem_init(&blob_broad_send_uni_sem, 0, 1);
k_sem_init(&blob_broad_next_sem, 0, 1);
blob_cli.inputs = &blob_cli_xfer.inputs;
broadcast_tx_complete_auto = false;
/** Two responsive targets. Checks that:
* - Send CB alternates between targets
* - Don't retransmit to responded targets
* - Next CB is called as soon as all have responded
* (Test assumes at least 5 transmission attempts)
*/
BUILD_ASSERT(CONFIG_BT_MESH_BLOB_CLI_BLOCK_RETRIES >= 5);
blob_cli_inputs_prepare(BT_MESH_ADDR_UNASSIGNED);
blob_cli_broadcast(&blob_cli, &tx);
for (size_t i = 0; i < 2; i++) {
if (k_sem_take(&blob_broad_send_uni_sem, K_SECONDS(10))) {
FAIL("Broadcast did not trigger send CB");
}
ASSERT_EQUAL(BLOB_CLI_ADDR + 1, dst_addr_last);
blob_cli_broadcast_tx_complete(&blob_cli);
if (k_sem_take(&blob_broad_send_uni_sem, K_SECONDS(10))) {
FAIL("Tx complete did not trigger send CB");
}
ASSERT_EQUAL(BLOB_CLI_ADDR + 2, dst_addr_last);
blob_cli_broadcast_tx_complete(&blob_cli);
}
blob_cli_broadcast_rsp(&blob_cli, srv1);
for (size_t i = 0; i < 2; i++) {
if (k_sem_take(&blob_broad_send_uni_sem, K_SECONDS(10))) {
FAIL("Tx complete did not trigger send CB");
}
ASSERT_EQUAL(BLOB_CLI_ADDR + 2, dst_addr_last);
blob_cli_broadcast_tx_complete(&blob_cli);
}
blob_cli_broadcast_rsp(&blob_cli, srv2);
if (!k_sem_take(&blob_broad_send_uni_sem, K_SECONDS(10))) {
FAIL("Unexpected send CB");
}
if (k_sem_take(&blob_broad_next_sem, K_NO_WAIT)) {
FAIL("Broadcast did not trigger next CB");
}
PASS();
}
static void test_cli_broadcast_unicast(void)
{
bt_mesh_test_cfg_set(NULL, 120);
bt_mesh_device_setup(&prov, &cli_comp);
blob_cli_prov_and_conf(BLOB_CLI_ADDR);
(void)target_srv_add(BLOB_CLI_ADDR + 1, true);
(void)target_srv_add(BLOB_CLI_ADDR + 2, true);
struct blob_cli_broadcast_ctx tx = {
.send = broadcast_uni_send,
.next = broadcast_next,
.acked = true,
.optional = false,
};
k_sem_init(&blob_broad_send_uni_sem, 0, 1);
k_sem_init(&blob_broad_next_sem, 0, 1);
k_sem_init(&lost_target_sem, 0, 1);
blob_cli.inputs = &blob_cli_xfer.inputs;
broadcast_tx_complete_auto = true;
/** 1. Two non-responsive targets. Checks that:
* - Next CB is called after all retransmit attempts expires
* - All lost targets is registered
*/
blob_cli_inputs_prepare(BT_MESH_ADDR_UNASSIGNED);
blob_cli_broadcast(&blob_cli, &tx);
if (k_sem_take(&blob_broad_next_sem, K_SECONDS(60))) {
FAIL("Broadcast did not trigger next CB");
}
if (k_sem_take(&lost_target_sem, K_NO_WAIT)) {
FAIL("Lost targets CB did not trigger for all expected lost targets");
}
/** 2. Two non-responsive targets re-run. Checks that:
* - Already lost targets does not attempt new transmission
* (Next CB called immediately)
*/
blob_cli_broadcast(&blob_cli, &tx);
if (k_sem_take(&blob_broad_next_sem, K_NO_WAIT)) {
FAIL("Broadcast did not trigger immediate next CB");
}
/** 3. Two non-responsive targets (Abort after first attempt). Checks that:
* - First transmission calls send CB
* - After abort is called, neither send or next CB is called
*/
k_sem_init(&blob_broad_send_uni_sem, 0, 1);
blob_cli_inputs_prepare(BT_MESH_ADDR_UNASSIGNED);
blob_cli_broadcast(&blob_cli, &tx);
if (k_sem_take(&blob_broad_send_uni_sem, K_NO_WAIT)) {
FAIL("Broadcast did not trigger send CB");
}
blob_cli_broadcast_abort(&blob_cli);
if (!k_sem_take(&blob_broad_send_uni_sem, K_SECONDS(60))) {
FAIL("Unexpected send CB");
}
if (!k_sem_take(&blob_broad_next_sem, K_NO_WAIT)) {
FAIL("Unexpected next CB");
}
PASS();
}
static void test_cli_trans_complete(void)
{
int err;
bt_mesh_test_cfg_set(NULL, 400);
bt_mesh_device_setup(&prov, &cli_comp);
blob_cli_prov_and_conf(BLOB_CLI_ADDR);
(void)target_srv_add(BLOB_CLI_ADDR + 1, false);
(void)target_srv_add(BLOB_CLI_ADDR + 2, false);
(void)target_srv_add(BLOB_CLI_ADDR + 3, false);
(void)target_srv_add(BLOB_CLI_ADDR + 4, false);
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);
LOG_INF("Running transfer in %s", is_pull_mode ? "Pull mode" : "Push mode");
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;
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_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);
PASS();
}
static void test_srv_trans_complete(void)
{
bt_mesh_test_cfg_set(NULL, 400);
bt_mesh_device_setup(&prov, &srv_comp);
blob_srv_prov_and_conf(bt_mesh_test_own_addr_get(BLOB_CLI_ADDR));
k_sem_init(&first_block_wr_sem, 0, 1);
k_sem_init(&blob_srv_end_sem, 0, 1);
k_sem_init(&blob_srv_suspend_sem, 0, 1);
bt_mesh_blob_srv_recv(&blob_srv, 1, &blob_io, 0, 10);
if (k_sem_take(&blob_srv_end_sem, K_SECONDS(380))) {
FAIL("End CB did not trigger as expected for the srv");
}
ASSERT_TRUE(blob_srv.phase == BT_MESH_BLOB_XFER_PHASE_COMPLETE);
/* Check that all blocks is received */
ASSERT_TRUE(atomic_test_bit(block_bitfield, 0));
ASSERT_TRUE(atomic_test_bit(block_bitfield, 1));
/* Check that a third block was not received */
ASSERT_FALSE(atomic_test_bit(block_bitfield, 2));
PASS();
}
static void test_cli_trans_resume(void)
{
int err;
bt_mesh_test_cfg_set(NULL, 800);
bt_mesh_device_setup(&prov, &cli_comp);
blob_cli_prov_and_conf(BLOB_CLI_ADDR);
(void)target_srv_add(BLOB_CLI_ADDR + 1, true);
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);
LOG_INF("Running transfer in %s", is_pull_mode ? "Pull mode" : "Push mode");
/** Test resumption of suspended BLOB transfer (Push).
* Client initiates transfer with two blocks. After
* first block completes the server will be suspended.
* At this point the client will attempt to resume the
* transfer.
*/
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;
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(500))) {
FAIL("Suspend CB did not trigger as expected for the cli");
}
if (k_sem_take(&lost_target_sem, K_NO_WAIT)) {
FAIL("Lost targets CB did not trigger for the target srv");
}
ASSERT_TRUE(blob_cli.state == BT_MESH_BLOB_CLI_STATE_SUSPENDED);
/* Initiate resumption of BLOB transfer */
err = bt_mesh_blob_cli_resume(&blob_cli);
if (err) {
FAIL("BLOB resume failed (err: %d)", err);
}
if (k_sem_take(&blob_cli_end_sem, K_SECONDS(780))) {
FAIL("End CB did not trigger as expected for the cli");
}
ASSERT_TRUE(blob_cli.state == BT_MESH_BLOB_CLI_STATE_NONE);
PASS();
}
static void test_srv_trans_resume(void)
{
bt_mesh_test_cfg_set(NULL, 800);
bt_mesh_device_setup(&prov, &srv_comp);
blob_srv_prov_and_conf(bt_mesh_test_own_addr_get(BLOB_CLI_ADDR));
k_sem_init(&first_block_wr_sem, 0, 1);
k_sem_init(&blob_srv_end_sem, 0, 1);
k_sem_init(&blob_srv_suspend_sem, 0, 1);
/** Wait for a first blob block to be received, then simulate radio
* disruption to cause suspension of the blob srv. Re-enable the radio
* as soon as the server is suspended and wait to receive the second
* block.
*/
bt_mesh_blob_srv_recv(&blob_srv, 1, &blob_io, 0, 10);
/* Let server receive a couple of chunks from second block before disruption */
for (int i = 0; i < 3; i++) {
if (k_sem_take(&first_block_wr_sem, K_SECONDS(180))) {
FAIL("Server did not receive the first BLOB block");
}
}
bt_mesh_scan_disable();
partial_block = 0;
if (k_sem_take(&blob_srv_suspend_sem, K_SECONDS(140))) {
FAIL("Suspend CB did not trigger as expected for the srv");
}
ASSERT_TRUE(blob_srv.phase == BT_MESH_BLOB_XFER_PHASE_SUSPENDED);
/* Wait for BLOB client to suspend. Measured experimentally. */
k_sleep(K_SECONDS(140));
bt_mesh_scan_enable();
if (k_sem_take(&blob_srv_end_sem, K_SECONDS(780))) {
FAIL("End CB did not trigger as expected for the srv");
}
ASSERT_TRUE(blob_srv.phase == BT_MESH_BLOB_XFER_PHASE_COMPLETE);
/* Check that all blocks is received */
ASSERT_TRUE(atomic_test_bit(block_bitfield, 0));
ASSERT_TRUE(atomic_test_bit(block_bitfield, 1));
/* Check that a third block was not received */
ASSERT_FALSE(atomic_test_bit(block_bitfield, 2));
PASS();
}
static void cli_pull_mode_setup(void)
{
bt_mesh_device_setup(&prov, &cli_comp);
blob_cli_prov_and_conf(BLOB_CLI_ADDR);
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);
blob_cli_xfer.xfer.mode = BT_MESH_BLOB_XFER_MODE_PULL;
blob_cli_xfer.xfer.size = CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MIN * 3;
blob_cli_xfer.xfer.id = 1;
blob_cli_xfer.xfer.block_size_log = 8;
blob_cli_xfer.xfer.chunk_size = 36;
blob_cli_xfer.inputs.timeout_base = 10;
}
static void test_cli_trans_persistency_pull(void)
{
int err;
bt_mesh_test_cfg_set(NULL, 240);
(void)target_srv_add(BLOB_CLI_ADDR + 1, true);
(void)target_srv_add(BLOB_CLI_ADDR + 2, false);
cli_pull_mode_setup();
blob_cli_inputs_prepare(0);
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_end_sem, K_SECONDS(230))) {
FAIL("End CB did not trigger as expected for the cli");
}
ASSERT_TRUE(blob_cli.state == BT_MESH_BLOB_CLI_STATE_NONE);
PASS();
}
static void srv_pull_mode_setup(void)
{
bt_mesh_device_setup(&prov, &srv_comp);
blob_srv_prov_and_conf(bt_mesh_test_own_addr_get(BLOB_CLI_ADDR));
k_sem_init(&first_block_wr_sem, 0, 1);
k_sem_init(&blob_srv_end_sem, 0, 1);
k_sem_init(&blob_srv_suspend_sem, 0, 1);
}
static void test_srv_trans_persistency_pull(void)
{
bt_mesh_test_cfg_set(NULL, 240);
srv_pull_mode_setup();
bt_mesh_blob_srv_recv(&blob_srv, 1, &blob_io, 0, 10);
/* Target with address 0x0002 (the first one) will disappear after receiving the first
* block. Target with address 0x0003 (the second one) will stay alive.
*/
if (bt_mesh_test_own_addr_get(BLOB_CLI_ADDR) == 0x0002) {
/* Let the first target receive a couple of chunks from second block before
* disruption.
*/
for (int i = 0; i < 3; i++) {
if (k_sem_take(&first_block_wr_sem, K_SECONDS(100))) {
FAIL("Server did not receive the first BLOB block");
}
}
bt_mesh_scan_disable();
bt_mesh_blob_srv_cancel(&blob_srv);
PASS();
return;
}
if (k_sem_take(&blob_srv_end_sem, K_SECONDS(230))) {
FAIL("End CB did not trigger as expected for the srv");
}
ASSERT_TRUE(blob_srv.phase == BT_MESH_BLOB_XFER_PHASE_COMPLETE);
/* Check that all blocks is received */
ASSERT_TRUE(atomic_test_bit(block_bitfield, 0));
ASSERT_TRUE(atomic_test_bit(block_bitfield, 1));
ASSERT_TRUE(atomic_test_bit(block_bitfield, 2));
/* Check that a third block was not received */
ASSERT_FALSE(atomic_test_bit(block_bitfield, 3));
PASS();
}
/* Makes device unresponsive after I/O is opened */
static int fail_on_io_open(const struct bt_mesh_blob_io *io,
const struct bt_mesh_blob_xfer *xfer,
enum bt_mesh_blob_io_mode mode)
{
bt_mesh_scan_disable();
return 0;
}
/* Makes device unresponsive after receiving block start msg */
static int fail_on_block_start(const struct bt_mesh_blob_io *io,
const struct bt_mesh_blob_xfer *xfer,
const struct bt_mesh_blob_block *block)
{
bt_mesh_scan_disable();
return 0;
}
static void cli_common_fail_on_init(void)
{
bt_mesh_test_cfg_set(NULL, 800);
bt_mesh_device_setup(&prov, &cli_comp);
blob_cli_prov_and_conf(BLOB_CLI_ADDR);
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);
blob_cli_inputs_prepare(BLOB_GROUP_ADDR);
blob_cli_xfer.xfer.mode = BT_MESH_BLOB_XFER_MODE_PUSH;
blob_cli_xfer.xfer.size = CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX * 1;
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 test_cli_fail_on_persistency(void)
{
int err;
/** Test that Push mode BLOB transfer persists as long as at
* least one target is still active. During the test multiple
* servers will become unresponsive at different phases of the
* transfer:
* - Srv 0x0002 will not respond to Block start msg.
* - Srv 0x0003 will not respond to Block get msg.
* - Srv 0x0004 will not respond to Xfer get msg.
* - Srv 0x0005 is responsive all the way
* - Srv 0x0006 is a non-existing unresponsive node
*/
(void)target_srv_add(BLOB_CLI_ADDR + 1, true);
(void)target_srv_add(BLOB_CLI_ADDR + 2, true);
(void)target_srv_add(BLOB_CLI_ADDR + 3, true);
(void)target_srv_add(BLOB_CLI_ADDR + 4, false);
(void)target_srv_add(BLOB_CLI_ADDR + 5, true);
cli_common_fail_on_init();
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_end_sem, K_SECONDS(750))) {
FAIL("End CB did not trigger as expected for the cli");
}
ASSERT_TRUE(cli_end_success);
if (k_sem_take(&lost_target_sem, K_NO_WAIT)) {
FAIL("Lost targets CB did not trigger for all expected lost targets");
}
PASS();
}
static void common_fail_on_srv_init(const struct bt_mesh_comp *comp)
{
bt_mesh_test_cfg_set(NULL, 800);
bt_mesh_device_setup(&prov, comp);
blob_srv_prov_and_conf(bt_mesh_test_own_addr_get(BLOB_CLI_ADDR));
k_sem_init(&first_block_wr_sem, 0, 1);
k_sem_init(&blob_srv_end_sem, 0, 1);
k_sem_init(&blob_srv_suspend_sem, 0, 1);
}
static void test_srv_fail_on_block_start(void)
{
common_fail_on_srv_init(&srv_comp);
static const struct bt_mesh_blob_io io = {
.open = fail_on_io_open,
.rd = blob_chunk_rd,
.wr = blob_chunk_wr,
};
bt_mesh_blob_srv_recv(&blob_srv, 1, &io, 0, 1);
PASS();
}
static void test_srv_fail_on_block_get(void)
{
common_fail_on_srv_init(&srv_comp);
static const struct bt_mesh_blob_io io = {
.open = blob_io_open,
.rd = blob_chunk_rd,
.wr = blob_chunk_wr,
.block_start = fail_on_block_start,
};
bt_mesh_blob_srv_recv(&blob_srv, 1, &io, 0, 1);
PASS();
}
static int dummy_xfer_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
return 0;
}
static const struct bt_mesh_model_op model_op2[] = {
{ BT_MESH_BLOB_OP_XFER_GET, 0, dummy_xfer_get },
BT_MESH_MODEL_OP_END
};
/** Composition data for BLOB server where we intercept the
* BT_MESH_BLOB_OP_XFER_GET message handler through an imposter
* model. This is done to emulate a BLOB server that becomes
* unresponsive at the later stage of a BLOB transfer.
*/
static const struct bt_mesh_comp srv_broken_comp = {
.elem =
(struct bt_mesh_elem[]){
BT_MESH_ELEM(1,
MODEL_LIST(BT_MESH_MODEL_CFG_SRV,
BT_MESH_MODEL_CFG_CLI(&cfg_cli),
BT_MESH_MODEL_SAR_CFG_SRV,
BT_MESH_MODEL_SAR_CFG_CLI(&sar_cfg_cli),
BT_MESH_MODEL_CB(IMPOSTER_MODEL_ID,
model_op2, NULL, NULL, NULL),
BT_MESH_MODEL_BLOB_SRV(&blob_srv)),
BT_MESH_MODEL_NONE),
},
.elem_count = 1,
};
static void test_srv_fail_on_xfer_get(void)
{
common_fail_on_srv_init(&srv_broken_comp);
bt_mesh_blob_srv_recv(&blob_srv, 1, &blob_io, 0, 5);
PASS();
}
static void test_srv_fail_on_nothing(void)
{
common_fail_on_srv_init(&srv_comp);
bt_mesh_blob_srv_recv(&blob_srv, 1, &blob_io, 0, 5);
PASS();
}
static void test_cli_fail_on_no_rsp(void)
{
int err;
/** Test fail conditions upon non-responsive servers
* during push transfer. Depending on the set
* test message type it tests the following:
*
* msg_fail_type = BLOCK_GET_FAIL - BLOB transfer suspends
* when targets does not respond to Block get.
* msg_fail_type = XFER_GET_FAIL - BLOB transfer stops
* when targets does not respond to Xfer get message.
*/
(void)target_srv_add(BLOB_CLI_ADDR + 1, true);
(void)target_srv_add(BLOB_CLI_ADDR + 2, true);
cli_common_fail_on_init();
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);
}
switch (msg_fail_type) {
case BLOCK_GET_FAIL:
if (k_sem_take(&blob_cli_suspend_sem, K_SECONDS(750))) {
FAIL("Suspend CB did not trigger as expected for the cli");
}
break;
case XFER_GET_FAIL:
if (k_sem_take(&blob_cli_end_sem, K_SECONDS(750))) {
FAIL("End CB did not trigger as expected for the cli");
}
ASSERT_FALSE(cli_end_success);
break;
default:
FAIL("Did not recognize the message type of the test");
}
if (k_sem_take(&lost_target_sem, K_NO_WAIT)) {
FAIL("Lost targets CB did not trigger for all expected lost targets");
}
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 */
static void test_cli_friend_pull(void)
{
int err;
bt_mesh_test_cfg_set(NULL, 1000);
bt_mesh_test_friendship_init(1);
cli_pull_mode_setup();
bt_mesh_friend_set(BT_MESH_FEATURE_ENABLED);
for (int i = 1; i <= CONFIG_BT_MESH_FRIEND_LPN_COUNT; i++) {
ASSERT_OK_MSG(bt_mesh_test_friendship_evt_wait(BT_MESH_TEST_FRIEND_ESTABLISHED,
K_SECONDS(5)),
"Friendship not established");
(void)target_srv_add(BLOB_CLI_ADDR + i, false);
}
blob_cli_inputs_prepare(0);
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_end_sem, K_SECONDS(750))) {
FAIL("End CB did not trigger as expected for the cli");
}
ASSERT_TRUE(blob_cli.state == BT_MESH_BLOB_CLI_STATE_NONE);
PASS();
}
static void test_srv_lpn_pull(void)
{
bt_mesh_test_cfg_set(NULL, 1000);
bt_mesh_test_friendship_init(1);
srv_pull_mode_setup();
/* This test is used to establish friendship with single lpn as well as
* with many lpn devices. If legacy advertiser is used friendship with
* many lpn devices is established normally due to bad precision of advertiser.
* If extended advertiser is used simultaneous lpn running causes the situation
* when Friend Request from several devices collide in emulated radio channel.
* This shift of start moment helps to avoid Friend Request collisions.
*/
k_sleep(K_MSEC(10 * get_device_nbr()));
bt_mesh_lpn_set(true);
ASSERT_OK_MSG(bt_mesh_test_friendship_evt_wait(BT_MESH_TEST_LPN_ESTABLISHED, K_SECONDS(5)),
"LPN not established");
bt_mesh_blob_srv_recv(&blob_srv, 1, &blob_io, 0, 10);
if (k_sem_take(&blob_srv_end_sem, K_SECONDS(750))) {
FAIL("End CB did not trigger as expected for the srv");
}
ASSERT_TRUE(blob_srv.phase == BT_MESH_BLOB_XFER_PHASE_COMPLETE);
/* Check that all blocks is received */
ASSERT_TRUE(atomic_test_bit(block_bitfield, 0));
ASSERT_TRUE(atomic_test_bit(block_bitfield, 1));
ASSERT_TRUE(atomic_test_bit(block_bitfield, 2));
PASS();
}
#define TEST_CASE(role, name, description) \
{ \
.test_id = "blob_" #role "_" #name, \
.test_descr = description, \
.test_args_f = test_args_parse, \
.test_tick_f = bt_mesh_test_timeout, \
.test_main_f = test_##role##_##name, \
}
static const struct bst_test_instance test_blob[] = {
TEST_CASE(cli, caps_all_rsp, "Caps procedure: All responsive targets"),
TEST_CASE(cli, caps_partial_rsp, "Caps procedure: Mixed response from targets"),
TEST_CASE(cli, caps_no_rsp, "Caps procedure: No response from targets"),
TEST_CASE(cli, caps_cancelled, "Caps procedure: Cancel caps"),
TEST_CASE(cli, broadcast_basic, "Test basic broadcast API and CBs "),
TEST_CASE(cli, broadcast_trans, "Test all broadcast transmission types"),
TEST_CASE(cli, broadcast_unicast_seq, "Test broadcast with unicast addr (Sequential)"),
TEST_CASE(cli, broadcast_unicast, "Test broadcast with unicast addr"),
TEST_CASE(cli, trans_complete, "Transfer completes successfully on client (Default: Push)"),
TEST_CASE(cli, trans_resume, "Resume BLOB transfer after srv suspension (Default: Push)"),
TEST_CASE(cli, fail_on_persistency, "BLOB Client doesn't give up BLOB Transfer"),
TEST_CASE(cli, trans_persistency_pull, "Test transfer persistency in Pull mode"),
TEST_CASE(cli, fail_on_no_rsp, "BLOB Client end transfer if no targets rsp to Xfer Get"),
TEST_CASE(cli, friend_pull, "BLOB Client on friend node completes transfer in pull mode"),
TEST_CASE(srv, caps_standard, "Standard responsive blob server"),
TEST_CASE(srv, caps_no_rsp, "Non-responsive blob server"),
TEST_CASE(srv, trans_complete, "Transfer completes successfully on server"),
TEST_CASE(srv, trans_resume, "Self suspending server after first received block"),
TEST_CASE(srv, trans_persistency_pull, "Test transfer persistency in Pull mode"),
TEST_CASE(srv, fail_on_block_start, "Server failing right before first block start msg"),
TEST_CASE(srv, fail_on_block_get, "Server failing right before first block get msg"),
TEST_CASE(srv, fail_on_xfer_get, "Server failing right before first xfer get msg"),
TEST_CASE(srv, fail_on_nothing, "Non-failing server"),
TEST_CASE(srv, lpn_pull, "BLOB Server on LPN completes transfer in pull mode"),
BSTEST_END_MARKER
};
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