Bluetooth: Test: SC Indication

Test that SC Indication is correctly sent when the client reconnect and
the server updated the GATT database since last connection. Test that
the indication is sent even if the bond is not restored.

Signed-off-by: Théo Battrel <theo.battrel@nordicsemi.no>
This commit is contained in:
Théo Battrel 2023-09-15 10:57:38 +02:00 committed by Carles Cufí
commit 888a8c644c
10 changed files with 676 additions and 0 deletions

View file

@ -44,6 +44,7 @@ app=tests/bsim/bluetooth/host/gatt/settings compile
app=tests/bsim/bluetooth/host/gatt/settings conf_file=prj_2.conf compile
app=tests/bsim/bluetooth/host/gatt/ccc_store compile
app=tests/bsim/bluetooth/host/gatt/ccc_store conf_file=prj_2.conf compile
app=tests/bsim/bluetooth/host/gatt/sc_indicate compile
app=tests/bsim/bluetooth/host/l2cap/general compile
app=tests/bsim/bluetooth/host/l2cap/userdata compile

View file

@ -0,0 +1,25 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
if (NOT DEFINED ENV{BSIM_COMPONENTS_PATH})
message(FATAL_ERROR "This test requires the BabbleSim simulator. Please set \
the environment variable BSIM_COMPONENTS_PATH to point to its \
components folder. More information can be found in \
https://babblesim.github.io/folder_structure_and_env.html")
endif()
find_package(Zephyr HINTS $ENV{ZEPHYR_BASE})
project(bsim_test_auto_seq_req)
target_sources(app PRIVATE
src/main.c
src/central.c
src/peripheral.c
src/bs_bt_utils.c
)
zephyr_include_directories(
$ENV{BSIM_COMPONENTS_PATH}/libUtilv1/src/
$ENV{BSIM_COMPONENTS_PATH}/libPhyComv1/src/
)

View file

@ -0,0 +1,20 @@
CONFIG_BT=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="SC Indication Test"
CONFIG_LOG=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_SMP=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_SETTINGS=y
CONFIG_BT_SETTINGS=y
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_NVS=y
CONFIG_FLASH_MAP=y
CONFIG_SETTINGS_NVS=y
CONFIG_BT_GATT_DYNAMIC_DB=y

View file

@ -0,0 +1,182 @@
/**
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <zephyr/bluetooth/gap.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci_types.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(test_utils, LOG_LEVEL_DBG);
#include "bs_bt_utils.h"
struct bt_conn *g_conn;
DEFINE_FLAG(flag_is_connected);
void wait_connected(void)
{
LOG_DBG("Wait for connection...");
WAIT_FOR_FLAG(flag_is_connected);
}
void wait_disconnected(void)
{
LOG_DBG("Wait for disconnection...");
WAIT_FOR_FLAG_UNSET(flag_is_connected);
}
static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
{
LOG_DBG("security changed");
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
UNSET_FLAG(flag_is_connected);
}
struct bt_conn *get_g_conn(void)
{
return g_conn;
}
void clear_g_conn(void)
{
struct bt_conn *conn;
conn = g_conn;
g_conn = NULL;
BSIM_ASSERT(conn, "Test error: no g_conn!\n");
bt_conn_unref(conn);
}
static void connected(struct bt_conn *conn, uint8_t err)
{
BSIM_ASSERT((!g_conn || (conn == g_conn)), "Unexpected new connection.");
if (!g_conn) {
g_conn = bt_conn_ref(conn);
}
if (err != 0) {
clear_g_conn();
return;
}
SET_FLAG(flag_is_connected);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
.security_changed = security_changed,
};
static void stop_scan_and_connect(const bt_addr_le_t *addr,
int8_t rssi,
uint8_t type,
struct net_buf_simple *ad)
{
char addr_str[BT_ADDR_LE_STR_LEN];
int err;
if (g_conn != NULL) {
return;
}
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
printk("Got scan result, connecting.. dst %s, RSSI %d\n", addr_str, rssi);
err = bt_le_scan_stop();
BSIM_ASSERT(!err, "Err bt_le_scan_stop %d", err);
err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT, &g_conn);
BSIM_ASSERT(!err, "Err bt_conn_le_create %d", err);
}
void scan_connect_to_first_result(void)
{
int err;
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, stop_scan_and_connect);
BSIM_ASSERT(!err, "Err bt_le_scan_start %d", err);
}
void disconnect(void)
{
int err;
err = bt_conn_disconnect(g_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
BSIM_ASSERT(!err, "bt_conn_disconnect failed (%d)\n", err);
}
void set_security(bt_security_t sec)
{
int err;
err = bt_conn_set_security(g_conn, sec);
BSIM_ASSERT(!err, "Err bt_conn_set_security %d", err);
}
void create_adv(struct bt_le_ext_adv **adv)
{
int err;
struct bt_le_adv_param params = {};
params.options |= BT_LE_ADV_OPT_CONNECTABLE;
params.options |= BT_LE_ADV_OPT_EXT_ADV;
params.id = BT_ID_DEFAULT;
params.sid = 0;
params.interval_min = BT_GAP_ADV_FAST_INT_MIN_2;
params.interval_max = BT_GAP_ADV_FAST_INT_MAX_2;
err = bt_le_ext_adv_create(&params, NULL, adv);
BSIM_ASSERT(!err, "bt_le_ext_adv_create failed (%d)\n", err);
}
void start_adv(struct bt_le_ext_adv *adv)
{
int err;
err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT);
BSIM_ASSERT(!err, "bt_le_ext_adv_start failed (%d)\n", err);
}
void stop_adv(struct bt_le_ext_adv *adv)
{
int err;
err = bt_le_ext_adv_stop(adv);
BSIM_ASSERT(!err, "bt_le_ext_adv_stop failed (%d)\n", err);
}
/* The following flags are raised by events and lowered by test code. */
DEFINE_FLAG(flag_pairing_complete);
DEFINE_FLAG(flag_bonded);
DEFINE_FLAG(flag_not_bonded);
void pairing_complete(struct bt_conn *conn, bool bonded)
{
LOG_DBG("pairing complete");
SET_FLAG(flag_pairing_complete);
if (bonded) {
SET_FLAG(flag_bonded);
LOG_DBG("Bonded status: true");
} else {
SET_FLAG(flag_not_bonded);
LOG_DBG("Bonded status: false");
}
}
void pairing_failed(struct bt_conn *conn, enum bt_security_err err)
{
FAIL("Pairing failed\n");
}

View file

@ -0,0 +1,67 @@
/**
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "bstests.h"
#include "bs_tracing.h"
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
extern enum bst_result_t bst_result;
#define DECLARE_FLAG(flag) extern atomic_t flag
#define DEFINE_FLAG(flag) atomic_t flag = (atomic_t) false
#define SET_FLAG(flag) (void)atomic_set(&flag, (atomic_t) true)
#define UNSET_FLAG(flag) (void)atomic_set(&flag, (atomic_t) false)
#define WAIT_FOR_FLAG(flag) \
while (!(bool)atomic_get(&flag)) { \
(void)k_sleep(K_MSEC(1)); \
}
#define WAIT_FOR_FLAG_UNSET(flag) \
while ((bool)atomic_get(&flag)) { \
(void)k_sleep(K_MSEC(1)); \
}
#define TAKE_FLAG(flag) \
while (!(bool)atomic_cas(&flag, true, false)) { \
(void)k_sleep(K_MSEC(1)); \
}
#define GET_FLAG(flag) (bool)atomic_get(&flag)
#define BSIM_ASSERT(expr, ...) \
do { \
if (!(expr)) { \
FAIL(__VA_ARGS__); \
} \
} while (0)
#define FAIL(...) \
do { \
bst_result = Failed; \
bs_trace_error_time_line(__VA_ARGS__); \
} while (0)
#define PASS(...) \
do { \
bst_result = Passed; \
bs_trace_info_time(1, __VA_ARGS__); \
} while (0)
DECLARE_FLAG(flag_pairing_complete);
DECLARE_FLAG(flag_bonded);
DECLARE_FLAG(flag_not_bonded);
void scan_connect_to_first_result(void);
struct bt_conn *get_g_conn(void);
void clear_g_conn(void);
void disconnect(void);
void wait_connected(void);
void wait_disconnected(void);
void create_adv(struct bt_le_ext_adv **adv);
void start_adv(struct bt_le_ext_adv *adv);
void stop_adv(struct bt_le_ext_adv *adv);
void set_security(bt_security_t sec);
void pairing_complete(struct bt_conn *conn, bool bonded);
void pairing_failed(struct bt_conn *conn, enum bt_security_err err);

View file

@ -0,0 +1,195 @@
/**
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include <stdint.h>
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/addr.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/settings/settings.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(test_central, LOG_LEVEL_DBG);
#include "bs_bt_utils.h"
DEFINE_FLAG(flag_discovered);
DEFINE_FLAG(flag_subscribed);
DEFINE_FLAG(flag_indicated);
enum GATT_HANDLES {
SC,
CCC,
NUM_HANDLES,
};
static uint16_t gatt_handles[NUM_HANDLES] = {0};
static struct bt_gatt_subscribe_params subscribe_params;
static void sc_subscribed(struct bt_conn *conn,
uint8_t err,
struct bt_gatt_subscribe_params *params)
{
LOG_DBG("subscribed");
SET_FLAG(flag_subscribed);
}
static uint8_t sc_indicated(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
const void *data,
uint16_t length)
{
LOG_DBG("indication received");
SET_FLAG(flag_indicated);
return BT_GATT_ITER_CONTINUE;
}
static void subscribe(void)
{
int err;
subscribe_params.ccc_handle = gatt_handles[CCC];
subscribe_params.value_handle = gatt_handles[SC];
subscribe_params.value = BT_GATT_CCC_INDICATE;
subscribe_params.subscribe = sc_subscribed;
subscribe_params.notify = sc_indicated;
err = bt_gatt_subscribe(get_g_conn(), &subscribe_params);
BSIM_ASSERT(!err, "bt_gatt_subscribe failed (%d)\n", err);
WAIT_FOR_FLAG(flag_subscribed);
}
static uint8_t discover_func(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
if (attr == NULL) {
for (size_t i = 0U; i < ARRAY_SIZE(gatt_handles); i++) {
LOG_DBG("handle[%d] = 0x%x", i, gatt_handles[i]);
BSIM_ASSERT(gatt_handles[i] != 0, "did not find all handles\n");
}
(void)memset(params, 0, sizeof(*params));
SET_FLAG(flag_discovered);
return BT_GATT_ITER_STOP;
}
if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) {
const struct bt_gatt_chrc *chrc = (struct bt_gatt_chrc *)attr->user_data;
static const struct bt_uuid_16 ccc_uuid = BT_UUID_INIT_16(BT_UUID_GATT_CCC_VAL);
if (bt_uuid_cmp(chrc->uuid, BT_UUID_GATT_SC) == 0) {
int err;
LOG_DBG("found sc");
gatt_handles[SC] = chrc->value_handle;
params->uuid = &ccc_uuid.uuid;
params->start_handle = attr->handle + 2;
params->type = BT_GATT_DISCOVER_DESCRIPTOR;
err = bt_gatt_discover(conn, params);
BSIM_ASSERT(!err, "bt_gatt_discover failed (%d)\n", err);
return BT_GATT_ITER_STOP;
}
} else if (params->type == BT_GATT_DISCOVER_DESCRIPTOR &&
bt_uuid_cmp(params->uuid, BT_UUID_GATT_CCC) == 0) {
LOG_DBG("found ccc");
gatt_handles[CCC] = attr->handle;
SET_FLAG(flag_discovered);
return BT_GATT_ITER_STOP;
}
return BT_GATT_ITER_CONTINUE;
}
static void gatt_discover(void)
{
int err;
static struct bt_gatt_discover_params discover_params;
discover_params.uuid = NULL;
discover_params.func = discover_func;
discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
err = bt_gatt_discover(get_g_conn(), &discover_params);
BSIM_ASSERT(!err, "bt_gatt_discover failed (%d)\n", err);
WAIT_FOR_FLAG(flag_discovered);
LOG_DBG("sc handle: %d", gatt_handles[SC]);
LOG_DBG("ccc handle: %d", gatt_handles[CCC]);
}
void central(void)
{
/*
* test goal: check that service changed indication is sent on
* reconnection when the server's GATT database has been updated since
* last connection
*
* the central will connect, bond with the peripheral and then
* disconnect after doing that, the central will try to connect again,
* this time it will not elevate the security
*
* to pass the test, the central will wait to receive the service
* changed indication
*/
int err;
struct bt_conn_auth_info_cb bt_conn_auth_info_cb = {
.pairing_failed = pairing_failed,
.pairing_complete = pairing_complete,
};
err = bt_enable(NULL);
BSIM_ASSERT(!err, "bt_enable failed (%d)\n", err);
err = bt_conn_auth_info_cb_register(&bt_conn_auth_info_cb);
BSIM_ASSERT(!err, "bt_conn_auth_info_cb_register failed.\n");
err = settings_load();
BSIM_ASSERT(!err, "settings_load failed (%d)\n", err);
scan_connect_to_first_result();
wait_connected();
set_security(BT_SECURITY_L2);
TAKE_FLAG(flag_pairing_complete);
TAKE_FLAG(flag_bonded);
/* subscribe to the service changed indication */
gatt_discover();
subscribe();
disconnect();
wait_disconnected();
clear_g_conn();
scan_connect_to_first_result();
wait_connected();
/* wait for service change indication */
WAIT_FOR_FLAG(flag_indicated);
PASS("PASS\n");
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "bstests.h"
#include "bs_bt_utils.h"
#define BS_SECONDS_TO_US(dur_sec) ((bs_time_t)dur_sec * USEC_PER_SEC)
#define TEST_TIMEOUT_SIMULATED BS_SECONDS_TO_US(60)
extern void central(void);
extern void peripheral(void);
static void test_tick(bs_time_t HW_device_time)
{
bs_trace_debug_time(0, "Simulation ends now.\n");
if (bst_result != Passed) {
bst_result = Failed;
bs_trace_error("Test did not pass before simulation ended.\n");
}
}
static void test_init(void)
{
bst_ticker_set_next_tick_absolute(TEST_TIMEOUT_SIMULATED);
bst_result = In_progress;
}
static const struct bst_test_instance test_to_add[] = {
{
.test_id = "central",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = central,
},
{
.test_id = "peripheral",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = peripheral,
},
BSTEST_END_MARKER,
};
static struct bst_test_list *install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_to_add);
};
bst_test_install_t test_installers[] = {install, NULL};
int main(void)
{
bst_main();
return 0;
}

View file

@ -0,0 +1,86 @@
/**
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include <stdint.h>
#include <zephyr/bluetooth/addr.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/settings/settings.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(test_peripheral, LOG_LEVEL_DBG);
#include "bs_bt_utils.h"
#define UUID_1 BT_UUID_DECLARE_128(0xdb, 0x1f, 0xe2, 0x52, 0xf3, 0xc6, 0x43, 0x66, \
0xb3, 0x92, 0x5d, 0xc6, 0xe7, 0xc9, 0x59, 0x9d)
#define UUID_2 BT_UUID_DECLARE_128(0x3f, 0xa4, 0x7f, 0x44, 0x2e, 0x2a, 0x43, 0x05, \
0xab, 0x38, 0x07, 0x8d, 0x16, 0xbf, 0x99, 0xf1)
static void new_svc_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
ARG_UNUSED(attr);
bool notif_enabled = (value == BT_GATT_CCC_NOTIFY);
LOG_DBG("CCC Update: notification %s", notif_enabled ? "enabled" : "disabled");
}
static struct bt_gatt_attr attrs[] = {
BT_GATT_PRIMARY_SERVICE(UUID_1),
BT_GATT_CHARACTERISTIC(UUID_2, BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_NONE, NULL, NULL, NULL),
BT_GATT_CCC(new_svc_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
};
static struct bt_gatt_service svc = {
.attrs = attrs,
.attr_count = ARRAY_SIZE(attrs),
};
void peripheral(void)
{
/*
* test goal: check that service changed indication is sent on
* reconnection when the server's GATT database has been updated since
* last connection
*
* the peripheral will wait for connection/disconnection, when
* disconnected it will register a new service, when reconnecting, the
* central should receive an indication
*/
int err;
struct bt_le_ext_adv *adv = NULL;
err = bt_enable(NULL);
BSIM_ASSERT(!err, "bt_enable failed (%d)\n", err);
err = settings_load();
BSIM_ASSERT(!err, "settings_load failed (%d)\n", err);
create_adv(&adv);
start_adv(adv);
wait_connected();
stop_adv(adv);
wait_disconnected();
clear_g_conn();
/* add a new service to trigger the service changed indication */
err = bt_gatt_service_register(&svc);
BSIM_ASSERT(!err, "bt_gatt_service_register failed (%d)\n", err);
LOG_DBG("New service added");
start_adv(adv);
wait_connected();
PASS("Done\n");
}

View file

@ -0,0 +1,18 @@
#!/bin/env bash
# Copyright 2023 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
set -eu
# Terminate running simulations (if any)
${BSIM_COMPONENTS_PATH}/common/stop_bsim.sh
test_name='sc_indicate'
: "${BSIM_OUT_PATH:?BSIM_OUT_PATH must be defined}"
bsim_bin="${BSIM_OUT_PATH}/bin"
BOARD="${BOARD:-nrf52_bsim}"
test_exe="${bsim_bin}/bs_${BOARD}_tests_bsim_bluetooth_host_gatt_${test_name}_prj_conf"
west build -b nrf52_bsim -d build && \
cp -v build/zephyr/zephyr.exe "${test_exe}"

View file

@ -0,0 +1,24 @@
#!/bin/env bash
# Copyright 2023 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
source ${ZEPHYR_BASE}/tests/bsim/sh_common.source
test_name='sc_indicate'
test_exe="bs_${BOARD}_tests_bsim_bluetooth_host_gatt_${test_name}_prj_conf"
simulation_id="${test_name}"
verbosity_level=2
EXECUTE_TIMEOUT=30
cd ${BSIM_OUT_PATH}/bin
Execute "./${test_exe}" \
-v=${verbosity_level} -s=${simulation_id} -d=0 -testid=central
Execute "./${test_exe}" \
-v=${verbosity_level} -s=${simulation_id} -d=1 -testid=peripheral
Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \
-D=2 -sim_length=60e6
wait_for_background_jobs