tests bsim: Change folder structure

Bsim won't be limited anymore to BT tests.
In preparation for adding more tests in network areas
swap the tests/bluetooth/bsim with tests/bsim/bluetooth

There is no other changes in this commit beyond that.

Signed-off-by: Alberto Escolar Piedras <alberto.escolar.piedras@nordicsemi.no>
This commit is contained in:
Alberto Escolar Piedras 2023-03-17 15:29:24 +01:00 committed by Carles Cufí
commit f27c0b4905
500 changed files with 222 additions and 222 deletions

View file

@ -0,0 +1,21 @@
# 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 REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(bsim_test_gatt)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources} )
zephyr_include_directories(
$ENV{BSIM_COMPONENTS_PATH}/libUtilv1/src/
$ENV{BSIM_COMPONENTS_PATH}/libPhyComv1/src/
)

View file

@ -0,0 +1,16 @@
CONFIG_BT=y
CONFIG_BT_DEVICE_NAME="GATT tester"
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_BT_SMP=y
CONFIG_BT_GATT_DYNAMIC_DB=y
CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y
CONFIG_BT_L2CAP_ECRED=y
CONFIG_BT_EATT=y
CONFIG_BT_EATT_MAX=1
CONFIG_BT_EATT_AUTO_CONNECT=n
CONFIG_ASSERT=y
CONFIG_BT_TESTING=y

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "common.h"
#include "argparse.h"
void test_tick(bs_time_t HW_device_time)
{
if (bst_result != Passed) {
FAIL("test failed (not passed after %i seconds)\n", WAIT_TIME);
}
}
void test_init(void)
{
bst_ticker_set_next_tick_absolute(WAIT_TIME);
bst_result = In_progress;
}
#define CHANNEL_ID 0
#define MSG_SIZE 1
void backchannel_init(void)
{
uint device_number = get_device_nbr();
uint peer_number = device_number ^ 1;
uint device_numbers[] = { peer_number };
uint channel_numbers[] = { CHANNEL_ID };
uint *ch;
ch = bs_open_back_channel(device_number, device_numbers, channel_numbers,
ARRAY_SIZE(channel_numbers));
if (!ch) {
FAIL("Unable to open backchannel\n");
}
}
void backchannel_sync_send(void)
{
uint8_t sync_msg[MSG_SIZE] = { get_device_nbr() };
printk("Sending sync\n");
bs_bc_send_msg(CHANNEL_ID, sync_msg, ARRAY_SIZE(sync_msg));
}
void backchannel_sync_wait(void)
{
uint8_t sync_msg[MSG_SIZE];
while (true) {
if (bs_bc_is_msg_received(CHANNEL_ID) > 0) {
bs_bc_receive_msg(CHANNEL_ID, sync_msg, ARRAY_SIZE(sync_msg));
if (sync_msg[0] != get_device_nbr()) {
/* Received a message from another device, exit */
break;
}
}
k_sleep(K_MSEC(1));
}
printk("Sync received\n");
}

View file

@ -0,0 +1,69 @@
/**
* Common functions and helpers for BSIM GATT tests
*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include "bs_types.h"
#include "bs_tracing.h"
#include "time_machine.h"
#include "bstests.h"
#include "bs_pc_backchannel.h"
#include <zephyr/types.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
extern enum bst_result_t bst_result;
#define WAIT_TIME (60 * 1e6) /*seconds*/
#define CREATE_FLAG(flag) static 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 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)
#define CHRC_SIZE 10
#define TEST_SERVICE_UUID \
BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, \
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x00)
#define TEST_CHRC_UUID \
BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, \
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0x00)
#define TEST_ADDITIONAL_CHRC_UUID \
BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, \
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0x11)
void test_tick(bs_time_t HW_device_time);
void test_init(void);
void backchannel_init(void);
void backchannel_sync_send(void);
void backchannel_sync_wait(void);

View file

@ -0,0 +1,572 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gatt.h>
#include "common.h"
CREATE_FLAG(flag_is_connected);
CREATE_FLAG(flag_discover_complete);
CREATE_FLAG(flag_write_complete);
CREATE_FLAG(flag_chan_1_read);
CREATE_FLAG(flag_chan_2_read);
CREATE_FLAG(flag_db_hash_read);
CREATE_FLAG(flag_encrypted);
static struct bt_conn *g_conn;
static uint16_t chrc_handle;
static uint16_t csf_handle;
static const struct bt_uuid *test_svc_uuid = TEST_SERVICE_UUID;
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err != 0) {
FAIL("Failed to connect to %s (%u)\n", addr, err);
return;
}
printk("Connected to %s\n", addr);
SET_FLAG(flag_is_connected);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
if (conn != g_conn) {
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);
bt_conn_unref(g_conn);
g_conn = NULL;
UNSET_FLAG(flag_is_connected);
}
void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
{
if (err != BT_SECURITY_ERR_SUCCESS) {
FAIL("Encryption failed\n");
} else if (level < BT_SECURITY_L2) {
FAIL("Insufficient security\n");
} else {
SET_FLAG(flag_encrypted);
}
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
.security_changed = security_changed,
};
void device_found(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;
}
/* We're only interested in connectable events */
if (type != BT_HCI_ADV_IND && type != BT_HCI_ADV_DIRECT_IND) {
return;
}
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
printk("Device found: %s (RSSI %d)\n", addr_str, rssi);
printk("Stopping scan\n");
err = bt_le_scan_stop();
if (err != 0) {
FAIL("Could not stop scan (err %d)\n");
return;
}
err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT, &g_conn);
if (err != 0) {
FAIL("Could not connect to peer (err %d)", err);
}
}
static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
int err;
if (attr == NULL) {
if (chrc_handle == 0) {
FAIL("Did not discover chrc (%x)\n", chrc_handle);
}
(void)memset(params, 0, sizeof(*params));
SET_FLAG(flag_discover_complete);
return BT_GATT_ITER_STOP;
}
printk("[ATTRIBUTE] handle %u\n", attr->handle);
if (params->type == BT_GATT_DISCOVER_PRIMARY &&
bt_uuid_cmp(params->uuid, TEST_SERVICE_UUID) == 0) {
printk("Found test service\n");
params->uuid = NULL;
params->start_handle = attr->handle + 1;
params->type = BT_GATT_DISCOVER_CHARACTERISTIC;
err = bt_gatt_discover(conn, params);
if (err != 0) {
FAIL("Discover failed (err %d)\n", err);
}
return BT_GATT_ITER_STOP;
} else if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) {
const struct bt_gatt_chrc *chrc = (struct bt_gatt_chrc *)attr->user_data;
if (bt_uuid_cmp(chrc->uuid, TEST_CHRC_UUID) == 0) {
printk("Found chrc\n");
chrc_handle = chrc->value_handle;
} else if (bt_uuid_cmp(chrc->uuid, BT_UUID_GATT_CLIENT_FEATURES) == 0) {
printk("Found csf\n");
csf_handle = chrc->value_handle;
}
}
return BT_GATT_ITER_CONTINUE;
}
static void gatt_discover(const struct bt_uuid *uuid, uint8_t type)
{
static struct bt_gatt_discover_params discover_params;
int err;
printk("Discovering services and characteristics\n");
discover_params.uuid = uuid;
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 = type;
discover_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
UNSET_FLAG(flag_discover_complete);
err = bt_gatt_discover(g_conn, &discover_params);
if (err != 0) {
FAIL("Discover failed(err %d)\n", err);
}
WAIT_FOR_FLAG(flag_discover_complete);
printk("Discover complete\n");
}
static struct bt_gatt_read_params chan_1_read = {
.handle_count = 1,
.single = {
.handle = 0, /* Will be set later */
.offset = 0,
},
.chan_opt = BT_ATT_CHAN_OPT_NONE,
};
static struct bt_gatt_read_params chan_2_read = {
.handle_count = 1,
.single = {
.handle = 0, /* Will be set later */
.offset = 0,
},
.chan_opt = BT_ATT_CHAN_OPT_NONE,
};
static struct bt_gatt_read_params db_hash_read = {
.handle_count = 0,
.by_uuid = {
.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE,
.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE,
.uuid = BT_UUID_GATT_DB_HASH,
},
.chan_opt = BT_ATT_CHAN_OPT_NONE,
};
void expect_status(uint8_t err, uint8_t status)
{
if (err != status) {
FAIL("Unexpected status from read: 0x%02X, expected 0x%02X\n", err, status);
}
}
static uint8_t gatt_read_expect_success_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params, const void *data,
uint16_t length)
{
printk("GATT read cb: err 0x%02X\n", err);
expect_status(err, BT_ATT_ERR_SUCCESS);
if (params == &db_hash_read) {
SET_FLAG(flag_db_hash_read);
} else if (params == &chan_1_read) {
SET_FLAG(flag_chan_1_read);
} else if (params == &chan_2_read) {
SET_FLAG(flag_chan_2_read);
} else {
FAIL("Unexpected params\n");
}
return 0;
}
static uint8_t gatt_read_expect_err_unlikely_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
printk("GATT read cb: err 0x%02X\n", err);
expect_status(err, BT_ATT_ERR_UNLIKELY);
if (params == &chan_1_read) {
SET_FLAG(flag_chan_1_read);
} else if (params == &chan_2_read) {
SET_FLAG(flag_chan_2_read);
} else {
FAIL("Unexpected params\n");
}
return 0;
}
static uint8_t gatt_read_expect_err_out_of_sync_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
printk("GATT read cb: err 0x%02X\n", err);
expect_status(err, BT_ATT_ERR_DB_OUT_OF_SYNC);
if (params == &chan_1_read) {
SET_FLAG(flag_chan_1_read);
} else if (params == &chan_2_read) {
SET_FLAG(flag_chan_2_read);
} else {
FAIL("Unexpected params\n");
}
return 0;
}
static void gatt_read(struct bt_gatt_read_params *read_params)
{
int err;
printk("Reading\n");
err = bt_gatt_read(g_conn, read_params);
if (err != 0) {
FAIL("bt_gatt_read failed: %d\n", err);
}
}
static void write_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params)
{
if (err != BT_ATT_ERR_SUCCESS) {
FAIL("Write failed: 0x%02X\n", err);
}
SET_FLAG(flag_write_complete);
}
static void enable_robust_caching(void)
{
/* Client Supported Features Characteristic Value
* Bit 0: Robust Caching
* Bit 1: EATT
*/
static const uint8_t csf[] = { BIT(0) | BIT(1) };
static struct bt_gatt_write_params write_params = {
.func = write_cb,
.offset = 0,
.data = csf,
.length = sizeof(csf),
.chan_opt = BT_ATT_CHAN_OPT_NONE,
};
int err;
printk("Writing to Client Supported Features Characteristic\n");
write_params.handle = csf_handle;
UNSET_FLAG(flag_write_complete);
err = bt_gatt_write(g_conn, &write_params);
if (err) {
FAIL("bt_gatt_write failed (err %d)\n", err);
}
WAIT_FOR_FLAG(flag_write_complete);
printk("Success\n");
}
static void test_main_common(bool connect_eatt)
{
int err;
backchannel_init();
err = bt_enable(NULL);
if (err != 0) {
FAIL("Bluetooth discover failed (err %d)\n", err);
}
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, device_found);
if (err != 0) {
FAIL("Scanning failed to start (err %d)\n", err);
}
printk("Scanning successfully started\n");
WAIT_FOR_FLAG(flag_is_connected);
err = bt_conn_set_security(g_conn, BT_SECURITY_L2);
if (err) {
FAIL("Failed to start encryption procedure\n");
}
WAIT_FOR_FLAG(flag_encrypted);
gatt_discover(test_svc_uuid, BT_GATT_DISCOVER_PRIMARY);
gatt_discover(BT_UUID_GATT_CLIENT_FEATURES, BT_GATT_DISCOVER_CHARACTERISTIC);
enable_robust_caching();
if (connect_eatt) {
while (bt_eatt_count(g_conn) < 1) {
/* Wait for EATT channel to connect, in case it hasn't already */
k_sleep(K_MSEC(10));
}
}
/* Tell the server to register additional service */
backchannel_sync_send();
/* Wait for new service to be added by server */
backchannel_sync_wait();
chan_1_read.single.handle = chrc_handle;
chan_2_read.single.handle = chrc_handle;
}
static void test_main_db_hash_read_eatt(void)
{
test_main_common(true);
/* Read the DB hash to become change-aware */
db_hash_read.func = gatt_read_expect_success_cb;
gatt_read(&db_hash_read);
WAIT_FOR_FLAG(flag_db_hash_read);
/* These shall now succeed */
chan_1_read.func = gatt_read_expect_success_cb;
chan_2_read.func = gatt_read_expect_success_cb;
UNSET_FLAG(flag_chan_1_read);
UNSET_FLAG(flag_chan_2_read);
gatt_read(&chan_1_read);
gatt_read(&chan_2_read);
WAIT_FOR_FLAG(flag_chan_1_read);
WAIT_FOR_FLAG(flag_chan_2_read);
/* Signal to server that reads are done */
backchannel_sync_send();
PASS("GATT client Passed\n");
}
static void test_main_out_of_sync_eatt(void)
{
test_main_common(true);
chan_1_read.func = gatt_read_expect_err_out_of_sync_cb;
chan_2_read.func = gatt_read_expect_err_out_of_sync_cb;
gatt_read(&chan_1_read);
gatt_read(&chan_2_read);
/* Wait until received response on both reads. When robust caching is implemented
* on the client side, the waiting shall be done automatically by the host when
* reading the DB hash.
*/
WAIT_FOR_FLAG(flag_chan_1_read);
WAIT_FOR_FLAG(flag_chan_2_read);
/* Read the DB hash to become change-aware */
db_hash_read.func = gatt_read_expect_success_cb;
gatt_read(&db_hash_read);
WAIT_FOR_FLAG(flag_db_hash_read);
/* These shall now succeed */
chan_1_read.func = gatt_read_expect_success_cb;
chan_2_read.func = gatt_read_expect_success_cb;
UNSET_FLAG(flag_chan_1_read);
UNSET_FLAG(flag_chan_2_read);
gatt_read(&chan_1_read);
gatt_read(&chan_2_read);
WAIT_FOR_FLAG(flag_chan_1_read);
WAIT_FOR_FLAG(flag_chan_2_read);
/* Signal to server that reads are done */
backchannel_sync_send();
PASS("GATT client Passed\n");
}
static void test_main_retry_reads_eatt(void)
{
test_main_common(true);
chan_1_read.func = gatt_read_expect_err_out_of_sync_cb;
chan_2_read.func = gatt_read_expect_err_out_of_sync_cb;
gatt_read(&chan_1_read);
gatt_read(&chan_2_read);
/* Wait until received response on both reads. When robust caching is implemented
* on the client side, the waiting shall be done automatically by the host when
* reading the DB hash.
*/
WAIT_FOR_FLAG(flag_chan_1_read);
WAIT_FOR_FLAG(flag_chan_2_read);
/* Retry the reads, these shall time out */
chan_1_read.func = gatt_read_expect_err_unlikely_cb;
chan_2_read.func = gatt_read_expect_err_unlikely_cb;
UNSET_FLAG(flag_chan_1_read);
UNSET_FLAG(flag_chan_2_read);
gatt_read(&chan_1_read);
gatt_read(&chan_2_read);
WAIT_FOR_FLAG(flag_chan_1_read);
WAIT_FOR_FLAG(flag_chan_2_read);
/* Signal to server that reads are done */
backchannel_sync_send();
PASS("GATT client Passed\n");
}
static void test_main_db_hash_read_no_eatt(void)
{
test_main_common(false);
/* Read the DB hash to become change-aware */
db_hash_read.func = gatt_read_expect_success_cb;
gatt_read(&db_hash_read);
WAIT_FOR_FLAG(flag_db_hash_read);
/* Read shall now succeed */
chan_1_read.func = gatt_read_expect_success_cb;
UNSET_FLAG(flag_chan_1_read);
gatt_read(&chan_1_read);
WAIT_FOR_FLAG(flag_chan_1_read);
/* Signal to server that reads are done */
backchannel_sync_send();
PASS("GATT client Passed\n");
}
static void test_main_out_of_sync_no_eatt(void)
{
test_main_common(false);
chan_1_read.func = gatt_read_expect_err_out_of_sync_cb;
gatt_read(&chan_1_read);
WAIT_FOR_FLAG(flag_chan_1_read);
/* Read the DB hash to become change-aware */
db_hash_read.func = gatt_read_expect_success_cb;
gatt_read(&db_hash_read);
WAIT_FOR_FLAG(flag_db_hash_read);
/* Read shall now succeed */
chan_1_read.func = gatt_read_expect_success_cb;
UNSET_FLAG(flag_chan_1_read);
gatt_read(&chan_1_read);
WAIT_FOR_FLAG(flag_chan_1_read);
/* Signal to server that reads are done */
backchannel_sync_send();
PASS("GATT client Passed\n");
}
static void test_main_retry_reads_no_eatt(void)
{
test_main_common(false);
chan_1_read.func = gatt_read_expect_err_out_of_sync_cb;
gatt_read(&chan_1_read);
WAIT_FOR_FLAG(flag_chan_1_read);
/* Read again to become change-aware */
chan_1_read.func = gatt_read_expect_success_cb;
UNSET_FLAG(flag_chan_1_read);
gatt_read(&chan_1_read);
WAIT_FOR_FLAG(flag_chan_1_read);
/* Signal to server that reads are done */
backchannel_sync_send();
PASS("GATT client Passed\n");
}
static const struct bst_test_instance test_vcs[] = {
{
.test_id = "gatt_client_db_hash_read_eatt",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_db_hash_read_eatt,
},
{
.test_id = "gatt_client_out_of_sync_eatt",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_out_of_sync_eatt,
},
{
.test_id = "gatt_client_retry_reads_eatt",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_retry_reads_eatt,
},
{
.test_id = "gatt_client_db_hash_read_no_eatt",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_db_hash_read_no_eatt,
},
{
.test_id = "gatt_client_out_of_sync_no_eatt",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_out_of_sync_no_eatt,
},
{
.test_id = "gatt_client_retry_reads_no_eatt",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_retry_reads_no_eatt,
},
BSTEST_END_MARKER,
};
struct bst_test_list *test_gatt_client_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_vcs);
}

View file

@ -0,0 +1,173 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "common.h"
extern enum bst_result_t bst_result;
CREATE_FLAG(flag_is_connected);
CREATE_FLAG(flag_is_encrypted);
static struct bt_conn *g_conn;
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err != 0) {
FAIL("Failed to connect to %s (%u)\n", addr, err);
return;
}
printk("Connected to %s\n", addr);
g_conn = bt_conn_ref(conn);
SET_FLAG(flag_is_connected);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
if (conn != g_conn) {
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);
bt_conn_unref(g_conn);
g_conn = NULL;
UNSET_FLAG(flag_is_connected);
}
static void security_changed(struct bt_conn *conn, bt_security_t level,
enum bt_security_err security_err)
{
if (security_err == BT_SECURITY_ERR_SUCCESS && level > BT_SECURITY_L1) {
SET_FLAG(flag_is_encrypted);
}
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
.security_changed = security_changed,
};
#define ARRAY_ITEM(i, _) i
static const uint8_t chrc_data[] = { LISTIFY(CHRC_SIZE, ARRAY_ITEM, (,)) }; /* 1, 2, 3 ... */
static ssize_t read_test_chrc(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
printk("Characteristic read\n");
return bt_gatt_attr_read(conn, attr, buf, len, offset, (const void *)chrc_data, CHRC_SIZE);
}
BT_GATT_SERVICE_DEFINE(test_svc, BT_GATT_PRIMARY_SERVICE(TEST_SERVICE_UUID),
BT_GATT_CHARACTERISTIC(TEST_CHRC_UUID, BT_GATT_CHRC_READ, BT_GATT_PERM_READ,
read_test_chrc, NULL, NULL));
static struct bt_gatt_attr additional_attributes[] = {
BT_GATT_CHARACTERISTIC(TEST_ADDITIONAL_CHRC_UUID, 0, BT_GATT_PERM_NONE, NULL, NULL, NULL),
};
static struct bt_gatt_service additional_gatt_service = BT_GATT_SERVICE(additional_attributes);
static void test_main_common(bool connect_eatt)
{
int err;
const struct bt_data ad[] = { BT_DATA_BYTES(BT_DATA_FLAGS,
(BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)) };
backchannel_init();
err = bt_enable(NULL);
if (err != 0) {
FAIL("Bluetooth init failed (err %d)\n", err);
return;
}
printk("Bluetooth initialized\n");
err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
if (err != 0) {
FAIL("Advertising failed to start (err %d)\n", err);
return;
}
printk("Advertising successfully started\n");
WAIT_FOR_FLAG(flag_is_connected);
if (connect_eatt) {
WAIT_FOR_FLAG(flag_is_encrypted);
err = bt_eatt_connect(g_conn, CONFIG_BT_EATT_MAX);
if (err) {
FAIL("Failed to connect EATT channels (err %d)\n", err);
return;
}
}
/* Wait for client to do discovery and configuration */
backchannel_sync_wait();
printk("Registering additional service\n");
err = bt_gatt_service_register(&additional_gatt_service);
if (err < 0) {
FAIL("Registering additional service failed (err %d)\n", err);
}
/* Signal to client that additional service is registered */
backchannel_sync_send();
/* Wait for client to be done reading */
backchannel_sync_wait();
PASS("GATT server passed\n");
}
static void test_main_eatt(void)
{
test_main_common(true);
}
static void test_main_no_eatt(void)
{
test_main_common(false);
}
static const struct bst_test_instance test_gatt_server[] = {
{
.test_id = "gatt_server_eatt",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_eatt,
},
{
.test_id = "gatt_server_no_eatt",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_no_eatt,
},
BSTEST_END_MARKER,
};
struct bst_test_list *test_gatt_server_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_gatt_server);
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "bstests.h"
extern struct bst_test_list *test_gatt_server_install(struct bst_test_list *tests);
extern struct bst_test_list *test_gatt_client_install(struct bst_test_list *tests);
bst_test_install_t test_installers[] = {
test_gatt_server_install,
test_gatt_client_install,
NULL
};
void main(void)
{
bst_main();
}

View file

@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
verbosity_level=2
process_ids=""
exit_code=0
function Execute() {
if [ ! -f $1 ]; then
echo -e " \e[91m$(pwd)/$(basename $1) cannot be found (did you forget to\
compile it?)\e[39m"
exit 1
fi
timeout 120 $@ &
process_ids="$process_ids $!"
}
: "${BSIM_OUT_PATH:?BSIM_OUT_PATH must be defined}"
#Give a default value to BOARD if it does not have one yet:
BOARD="${BOARD:-nrf52_bsim}"
cd ${BSIM_OUT_PATH}/bin
Execute ./bs_${BOARD}_tests_bsim_bluetooth_host_gatt_caching_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=0 -testid=${client_id}
Execute ./bs_${BOARD}_tests_bsim_bluetooth_host_gatt_caching_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=1 -testid=${server_id}
Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \
-D=2 -sim_length=60e6 $@
for process_id in $process_ids; do
wait $process_id || let "exit_code=$?"
done
exit $exit_code #the last exit code != 0

View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_caching_db_hash_read_eatt" \
client_id="gatt_client_db_hash_read_eatt" \
server_id="gatt_server_eatt" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_caching_db_hash_read_no_eatt" \
client_id="gatt_client_db_hash_read_no_eatt" \
server_id="gatt_server_no_eatt" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_caching_out_of_sync_eatt" \
client_id="gatt_client_out_of_sync_eatt" \
server_id="gatt_server_eatt" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_caching_out_of_sync_no_eatt" \
client_id="gatt_client_out_of_sync_no_eatt" \
server_id="gatt_server_no_eatt" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_caching_retry_reads_eatt" \
client_id="gatt_client_retry_reads_eatt" \
server_id="gatt_server_eatt" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_caching_retry_reads_no_eatt" \
client_id="gatt_client_retry_reads_no_eatt" \
server_id="gatt_server_no_eatt" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,21 @@
# 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 REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(bsim_test_gatt)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources} )
zephyr_include_directories(
$ENV{BSIM_COMPONENTS_PATH}/libUtilv1/src/
$ENV{BSIM_COMPONENTS_PATH}/libPhyComv1/src/
)

View file

@ -0,0 +1,6 @@
CONFIG_BT=y
CONFIG_BT_DEVICE_NAME="GATT tester"
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_BT_ATT_PREPARE_COUNT=3

View file

@ -0,0 +1,20 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "common.h"
void test_tick(bs_time_t HW_device_time)
{
if (bst_result != Passed) {
FAIL("test failed (not passed after %i seconds)\n", WAIT_TIME);
}
}
void test_init(void)
{
bst_ticker_set_next_tick_absolute(WAIT_TIME);
bst_result = In_progress;
}

View file

@ -0,0 +1,66 @@
/**
* Common functions and helpers for BSIM GATT tests
*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include "bs_types.h"
#include "bs_tracing.h"
#include "time_machine.h"
#include "bstests.h"
#include <zephyr/types.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
extern enum bst_result_t bst_result;
#define WAIT_TIME (30 * 1e6) /*seconds*/
#define CREATE_FLAG(flag) static 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 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)
#define CHRC_SIZE 10
#define LONG_CHRC_SIZE 40
#define TEST_SERVICE_UUID \
BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, \
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x00)
#define TEST_CHRC_UUID \
BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, \
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0x00)
#define TEST_LONG_CHRC_UUID \
BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, \
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0x11)
void test_tick(bs_time_t HW_device_time);
void test_init(void);

View file

@ -0,0 +1,304 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gatt.h>
#include "common.h"
CREATE_FLAG(flag_is_connected);
CREATE_FLAG(flag_discover_complete);
CREATE_FLAG(flag_write_complete);
CREATE_FLAG(flag_read_complete);
static struct bt_conn *g_conn;
static uint16_t chrc_handle;
static uint16_t long_chrc_handle;
static struct bt_uuid *test_svc_uuid = TEST_SERVICE_UUID;
#define ARRAY_ITEM(i, _) i
static uint8_t chrc_data[] = { LISTIFY(CHRC_SIZE, ARRAY_ITEM, (,)) }; /* 1, 2, 3 ... */
static uint8_t long_chrc_data[] = { LISTIFY(LONG_CHRC_SIZE, ARRAY_ITEM, (,)) }; /* 1, 2, 3 ... */
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err != 0) {
FAIL("Failed to connect to %s (%u)\n", addr, err);
return;
}
printk("Connected to %s\n", addr);
g_conn = conn;
SET_FLAG(flag_is_connected);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
if (conn != g_conn) {
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);
bt_conn_unref(g_conn);
g_conn = NULL;
UNSET_FLAG(flag_is_connected);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};
void device_found(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;
}
/* We're only interested in connectable events */
if (type != BT_HCI_ADV_IND && type != BT_HCI_ADV_DIRECT_IND) {
return;
}
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
printk("Device found: %s (RSSI %d)\n", addr_str, rssi);
printk("Stopping scan\n");
err = bt_le_scan_stop();
if (err != 0) {
FAIL("Could not stop scan: %d");
return;
}
err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN,
BT_LE_CONN_PARAM_DEFAULT, &g_conn);
if (err != 0) {
FAIL("Could not connect to peer: %d", err);
}
}
static uint8_t discover_func(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
int err;
if (attr == NULL) {
if (chrc_handle == 0 || long_chrc_handle == 0) {
FAIL("Did not discover chrc (%x) or long_chrc (%x)",
chrc_handle, long_chrc_handle);
}
(void)memset(params, 0, sizeof(*params));
SET_FLAG(flag_discover_complete);
return BT_GATT_ITER_STOP;
}
printk("[ATTRIBUTE] handle %u\n", attr->handle);
if (params->type == BT_GATT_DISCOVER_PRIMARY &&
bt_uuid_cmp(params->uuid, TEST_SERVICE_UUID) == 0) {
printk("Found test service\n");
params->uuid = NULL;
params->start_handle = attr->handle + 1;
params->type = BT_GATT_DISCOVER_CHARACTERISTIC;
err = bt_gatt_discover(conn, params);
if (err != 0) {
FAIL("Discover failed (err %d)\n", err);
}
return BT_GATT_ITER_STOP;
} else if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) {
struct bt_gatt_chrc *chrc = (struct bt_gatt_chrc *)attr->user_data;
if (bt_uuid_cmp(chrc->uuid, TEST_CHRC_UUID) == 0) {
printk("Found chrc\n");
chrc_handle = chrc->value_handle;
} else if (bt_uuid_cmp(chrc->uuid, TEST_LONG_CHRC_UUID) == 0) {
printk("Found long_chrc\n");
long_chrc_handle = chrc->value_handle;
}
}
return BT_GATT_ITER_CONTINUE;
}
static void gatt_discover(void)
{
static struct bt_gatt_discover_params discover_params;
int err;
printk("Discovering services and characteristics\n");
discover_params.uuid = test_svc_uuid;
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_PRIMARY;
err = bt_gatt_discover(g_conn, &discover_params);
if (err != 0) {
FAIL("Discover failed(err %d)\n", err);
}
WAIT_FOR_FLAG(flag_discover_complete);
printk("Discover complete\n");
}
static void gatt_write_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_write_params *params)
{
if (err != BT_ATT_ERR_SUCCESS) {
FAIL("Write failed: 0x%02X\n", err);
}
(void)memset(params, 0, sizeof(*params));
SET_FLAG(flag_write_complete);
}
static void gatt_write(uint16_t handle)
{
static struct bt_gatt_write_params write_params;
int err;
if (handle == chrc_handle) {
printk("Writing to chrc\n");
write_params.data = chrc_data;
write_params.length = sizeof(chrc_data);
} else if (handle) {
printk("Writing to long_chrc\n");
write_params.data = long_chrc_data;
write_params.length = sizeof(long_chrc_data);
}
write_params.func = gatt_write_cb;
write_params.handle = handle;
UNSET_FLAG(flag_write_complete);
err = bt_gatt_write(g_conn, &write_params);
if (err != 0) {
FAIL("bt_gatt_write failed: %d\n", err);
}
WAIT_FOR_FLAG(flag_write_complete);
printk("success\n");
}
static uint8_t gatt_read_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
if (err != BT_ATT_ERR_SUCCESS) {
FAIL("Read failed: 0x%02X\n", err);
}
if (params->single.handle == chrc_handle) {
if (length != CHRC_SIZE ||
memcmp(data, chrc_data, length) != 0) {
FAIL("chrc data different than expected", err);
}
} else if (params->single.handle == chrc_handle) {
if (length != LONG_CHRC_SIZE ||
memcmp(data, long_chrc_data, length) != 0) {
FAIL("long_chrc data different than expected", err);
}
}
(void)memset(params, 0, sizeof(*params));
SET_FLAG(flag_read_complete);
return 0;
}
static void gatt_read(uint16_t handle)
{
static struct bt_gatt_read_params read_params;
int err;
printk("Reading chrc\n");
read_params.func = gatt_read_cb;
read_params.handle_count = 1;
read_params.single.handle = chrc_handle;
read_params.single.offset = 0;
UNSET_FLAG(flag_read_complete);
err = bt_gatt_read(g_conn, &read_params);
if (err != 0) {
FAIL("bt_gatt_read failed: %d\n", err);
}
WAIT_FOR_FLAG(flag_read_complete);
printk("success\n");
}
static void test_main(void)
{
int err;
err = bt_enable(NULL);
if (err != 0) {
FAIL("Bluetooth discover failed (err %d)\n", err);
}
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, device_found);
if (err != 0) {
FAIL("Scanning failed to start (err %d)\n", err);
}
printk("Scanning successfully started\n");
WAIT_FOR_FLAG(flag_is_connected);
gatt_discover();
/* Write and read a few times to ensure stateless behavior */
for (size_t i = 0; i < 3; i++) {
gatt_write(chrc_handle);
gatt_read(chrc_handle);
gatt_write(long_chrc_handle);
gatt_read(long_chrc_handle);
}
PASS("GATT client Passed\n");
}
static const struct bst_test_instance test_vcs[] = {
{
.test_id = "gatt_client",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main
},
BSTEST_END_MARKER
};
struct bst_test_list *test_gatt_client_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_vcs);
}

View file

@ -0,0 +1,180 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "common.h"
extern enum bst_result_t bst_result;
CREATE_FLAG(flag_is_connected);
static struct bt_conn *g_conn;
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err != 0) {
FAIL("Failed to connect to %s (%u)\n", addr, err);
return;
}
printk("Connected to %s\n", addr);
g_conn = bt_conn_ref(conn);
SET_FLAG(flag_is_connected);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
if (conn != g_conn) {
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);
bt_conn_unref(g_conn);
g_conn = NULL;
UNSET_FLAG(flag_is_connected);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};
static uint8_t chrc_data[CHRC_SIZE];
static uint8_t long_chrc_data[LONG_CHRC_SIZE];
static ssize_t read_test_chrc(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
return bt_gatt_attr_read(conn, attr, buf, len, offset,
(void *)chrc_data, sizeof(chrc_data));
}
static ssize_t write_test_chrc(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
const void *buf, uint16_t len,
uint16_t offset, uint8_t flags)
{
printk("chrc len %u offset %u\n", len, offset);
if (len > sizeof(chrc_data)) {
printk("Invalid chrc length\n");
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
} else if (offset + len > sizeof(chrc_data)) {
printk("Invalid chrc offset and length\n");
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (flags != 0) {
FAIL("Invalid flags %u\n", flags);
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
}
(void)memcpy(chrc_data + offset, buf, len);
return len;
}
static ssize_t read_long_test_chrc(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
return bt_gatt_attr_read(conn, attr, buf, len, offset,
(void *)long_chrc_data, sizeof(long_chrc_data));
}
static ssize_t write_long_test_chrc(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
const void *buf, uint16_t len,
uint16_t offset, uint8_t flags)
{
static uint8_t prepare_count;
printk("long_chrc len %u offset %u\n", len, offset);
if (len > sizeof(long_chrc_data)) {
printk("Invalid long_chrc length\n");
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
} else if (offset + len > sizeof(long_chrc_data)) {
printk("Invalid long_chrc offset and length\n");
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (flags & BT_GATT_WRITE_FLAG_PREPARE) {
printk("prepare_count %u\n", prepare_count++);
return BT_GATT_ERR(BT_ATT_ERR_SUCCESS);
}
(void)memcpy(long_chrc_data + offset, buf, len);
prepare_count = 0;
return len;
}
BT_GATT_SERVICE_DEFINE(test_svc,
BT_GATT_PRIMARY_SERVICE(TEST_SERVICE_UUID),
BT_GATT_CHARACTERISTIC(TEST_CHRC_UUID,
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_READ,
BT_GATT_PERM_WRITE | BT_GATT_PERM_READ,
read_test_chrc, write_test_chrc, NULL),
BT_GATT_CHARACTERISTIC(TEST_LONG_CHRC_UUID,
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_READ,
BT_GATT_PERM_WRITE | BT_GATT_PERM_READ | BT_GATT_PERM_PREPARE_WRITE,
read_long_test_chrc, write_long_test_chrc, NULL),
);
static void test_main(void)
{
int err;
const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR))
};
err = bt_enable(NULL);
if (err != 0) {
FAIL("Bluetooth init failed (err %d)\n", err);
return;
}
printk("Bluetooth initialized\n");
err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
if (err != 0) {
FAIL("Advertising failed to start (err %d)\n", err);
return;
}
printk("Advertising successfully started\n");
WAIT_FOR_FLAG(flag_is_connected);
PASS("GATT server passed\n");
}
static const struct bst_test_instance test_gatt_server[] = {
{
.test_id = "gatt_server",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main
},
BSTEST_END_MARKER
};
struct bst_test_list *test_gatt_server_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_gatt_server);
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "bstests.h"
extern struct bst_test_list *test_gatt_server_install(struct bst_test_list *tests);
extern struct bst_test_list *test_gatt_client_install(struct bst_test_list *tests);
bst_test_install_t test_installers[] = {
test_gatt_server_install,
test_gatt_client_install,
NULL
};
void main(void)
{
bst_main();
}

View file

@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
# Basic GATT test: A central acting as a GATT client scans for and connects
# to a peripheral acting as a GATT server. The GATT client will then attempt
# to write and read to and from a few GATT characteristics.
simulation_id="gatt"
verbosity_level=2
process_ids=""; exit_code=0
function Execute(){
if [ ! -f $1 ]; then
echo -e " \e[91m`pwd`/`basename $1` cannot be found (did you forget to\
compile it?)\e[39m"
exit 1
fi
timeout 120 $@ & process_ids="$process_ids $!"
}
: "${BSIM_OUT_PATH:?BSIM_OUT_PATH must be defined}"
#Give a default value to BOARD if it does not have one yet:
BOARD="${BOARD:-nrf52_bsim}"
cd ${BSIM_OUT_PATH}/bin
Execute ./bs_${BOARD}_tests_bsim_bluetooth_host_gatt_general_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=0 -testid=gatt_client
Execute ./bs_${BOARD}_tests_bsim_bluetooth_host_gatt_general_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=1 -testid=gatt_server
Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \
-D=2 -sim_length=60e6 $@
for process_id in $process_ids; do
wait $process_id || let "exit_code=$?"
done
exit $exit_code #the last exit code != 0

View file

@ -0,0 +1,21 @@
# 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 REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(bsim_test_gatt)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources} )
zephyr_include_directories(
$ENV{BSIM_COMPONENTS_PATH}/libUtilv1/src/
$ENV{BSIM_COMPONENTS_PATH}/libPhyComv1/src/
)

View file

@ -0,0 +1,17 @@
CONFIG_BT=y
CONFIG_BT_DEVICE_NAME="GATT tester"
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_BT_GATT_AUTO_DISCOVER_CCC=y
CONFIG_BT_SMP=y
CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y
CONFIG_BT_L2CAP_ECRED=y
CONFIG_BT_EATT=y
CONFIG_BT_EATT_MAX=5
CONFIG_ASSERT=y
CONFIG_BT_TESTING=y
CONFIG_BT_DEBUG_LOG=y

View file

@ -0,0 +1,20 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "common.h"
void test_tick(bs_time_t HW_device_time)
{
if (bst_result != Passed) {
FAIL("test failed (not passed after %i seconds)\n", WAIT_TIME);
}
}
void test_init(void)
{
bst_ticker_set_next_tick_absolute(WAIT_TIME);
bst_result = In_progress;
}

View file

@ -0,0 +1,69 @@
/**
* Common functions and helpers for BSIM GATT tests
*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include "bs_types.h"
#include "bs_tracing.h"
#include "time_machine.h"
#include "bstests.h"
#include <zephyr/types.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
extern enum bst_result_t bst_result;
#define WAIT_TIME (60 * 1e6) /*seconds*/
#define CREATE_FLAG(flag) static 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 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)
#define CHRC_SIZE 10
#define LONG_CHRC_SIZE 40
#define TEST_SERVICE_UUID \
BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, \
0x07, 0x08, 0x09, 0x00, 0x00)
#define TEST_CHRC_UUID \
BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, \
0x07, 0x08, 0x09, 0xFF, 0x00)
#define TEST_LONG_CHRC_UUID \
BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, \
0x07, 0x08, 0x09, 0xFF, 0x11)
void test_tick(bs_time_t HW_device_time);
void test_init(void);
#define NOTIFICATION_COUNT 10
BUILD_ASSERT(NOTIFICATION_COUNT % 2 == 0);

View file

@ -0,0 +1,469 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gatt.h>
#include "common.h"
CREATE_FLAG(flag_is_connected);
CREATE_FLAG(flag_is_encrypted);
CREATE_FLAG(flag_discover_complete);
CREATE_FLAG(flag_short_subscribed);
CREATE_FLAG(flag_long_subscribed);
static struct bt_conn *g_conn;
static uint16_t chrc_handle;
static uint16_t long_chrc_handle;
static struct bt_uuid *test_svc_uuid = TEST_SERVICE_UUID;
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err != 0) {
FAIL("Failed to connect to %s (%u)\n", addr, err);
return;
}
printk("Connected to %s\n", addr);
SET_FLAG(flag_is_connected);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
if (conn != g_conn) {
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);
bt_conn_unref(g_conn);
g_conn = NULL;
UNSET_FLAG(flag_is_connected);
}
void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
{
if (err) {
FAIL("Encryption failer (%d)\n", err);
} else if (level < BT_SECURITY_L2) {
FAIL("Insufficient sec level (%d)\n", level);
} else {
SET_FLAG(flag_is_encrypted);
}
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
.security_changed = security_changed,
};
void device_found(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;
}
/* We're only interested in connectable events */
if (type != BT_HCI_ADV_IND && type != BT_HCI_ADV_DIRECT_IND) {
return;
}
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
printk("Device found: %s (RSSI %d)\n", addr_str, rssi);
printk("Stopping scan\n");
err = bt_le_scan_stop();
if (err != 0) {
FAIL("Could not stop scan: %d");
return;
}
err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT, &g_conn);
if (err != 0) {
FAIL("Could not connect to peer: %d", err);
}
}
static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
int err;
if (attr == NULL) {
if (chrc_handle == 0 || long_chrc_handle == 0) {
FAIL("Did not discover chrc (%x) or long_chrc (%x)", chrc_handle,
long_chrc_handle);
}
(void)memset(params, 0, sizeof(*params));
SET_FLAG(flag_discover_complete);
return BT_GATT_ITER_STOP;
}
printk("[ATTRIBUTE] handle %u\n", attr->handle);
if (params->type == BT_GATT_DISCOVER_PRIMARY &&
bt_uuid_cmp(params->uuid, TEST_SERVICE_UUID) == 0) {
printk("Found test service\n");
params->uuid = NULL;
params->start_handle = attr->handle + 1;
params->type = BT_GATT_DISCOVER_CHARACTERISTIC;
err = bt_gatt_discover(conn, params);
if (err != 0) {
FAIL("Discover failed (err %d)\n", err);
}
return BT_GATT_ITER_STOP;
} else if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) {
const struct bt_gatt_chrc *chrc = (struct bt_gatt_chrc *)attr->user_data;
if (bt_uuid_cmp(chrc->uuid, TEST_CHRC_UUID) == 0) {
printk("Found chrc\n");
chrc_handle = chrc->value_handle;
} else if (bt_uuid_cmp(chrc->uuid, TEST_LONG_CHRC_UUID) == 0) {
printk("Found long_chrc\n");
long_chrc_handle = chrc->value_handle;
}
}
return BT_GATT_ITER_CONTINUE;
}
static void gatt_discover(enum bt_att_chan_opt opt)
{
static struct bt_gatt_discover_params discover_params;
int err;
printk("Discovering services and characteristics\n");
discover_params.uuid = test_svc_uuid;
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_PRIMARY;
discover_params.chan_opt = opt;
err = bt_gatt_discover(g_conn, &discover_params);
if (err != 0) {
FAIL("Discover failed(err %d)\n", err);
}
WAIT_FOR_FLAG(flag_discover_complete);
printk("Discover complete\n");
}
static void test_short_subscribed(struct bt_conn *conn, uint8_t err,
struct bt_gatt_write_params *params)
{
if (err) {
FAIL("Subscribe failed (err %d)\n", err);
}
SET_FLAG(flag_short_subscribed);
if (!params) {
printk("params NULL\n");
return;
}
if (params->handle == chrc_handle) {
printk("Subscribed to short characteristic\n");
} else {
FAIL("Unknown handle %d\n", params->handle);
}
}
static void test_long_subscribed(struct bt_conn *conn, uint8_t err,
struct bt_gatt_write_params *params)
{
if (err) {
FAIL("Subscribe failed (err %d)\n", err);
}
SET_FLAG(flag_long_subscribed);
if (!params) {
printk("params NULL\n");
return;
}
if (params->handle == long_chrc_handle) {
printk("Subscribed to long characteristic\n");
} else {
FAIL("Unknown handle %d\n", params->handle);
}
}
static volatile size_t num_notifications;
uint8_t test_notify(struct bt_conn *conn, struct bt_gatt_subscribe_params *params, const void *data,
uint16_t length)
{
printk("Received notification #%u with length %d\n", num_notifications++, length);
return BT_GATT_ITER_CONTINUE;
}
static struct bt_gatt_discover_params disc_params_short;
static struct bt_gatt_subscribe_params sub_params_short = {
.notify = test_notify,
.write = test_short_subscribed,
.ccc_handle = 0, /* Auto-discover CCC*/
.disc_params = &disc_params_short, /* Auto-discover CCC */
.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE,
.value = BT_GATT_CCC_NOTIFY,
};
static struct bt_gatt_discover_params disc_params_long;
static struct bt_gatt_subscribe_params sub_params_long = {
.notify = test_notify,
.write = test_long_subscribed,
.ccc_handle = 0, /* Auto-discover CCC*/
.disc_params = &disc_params_long, /* Auto-discover CCC */
.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE,
.value = BT_GATT_CCC_NOTIFY,
};
static void gatt_subscribe_short(enum bt_att_chan_opt opt)
{
int err;
sub_params_short.value_handle = chrc_handle;
sub_params_short.chan_opt = opt;
err = bt_gatt_subscribe(g_conn, &sub_params_short);
if (err < 0) {
FAIL("Failed to subscribe\n");
} else {
printk("Subscribe request sent\n");
}
}
static void gatt_unsubscribe_short(enum bt_att_chan_opt opt)
{
int err;
sub_params_short.value_handle = chrc_handle;
sub_params_short.chan_opt = opt;
err = bt_gatt_unsubscribe(g_conn, &sub_params_short);
if (err < 0) {
FAIL("Failed to unsubscribe\n");
} else {
printk("Unsubscribe request sent\n");
}
}
static void gatt_subscribe_long(enum bt_att_chan_opt opt)
{
int err;
UNSET_FLAG(flag_long_subscribed);
sub_params_long.value_handle = long_chrc_handle;
sub_params_long.chan_opt = opt;
err = bt_gatt_subscribe(g_conn, &sub_params_long);
if (err < 0) {
FAIL("Failed to subscribe\n");
} else {
printk("Subscribe request sent\n");
}
}
static void gatt_unsubscribe_long(enum bt_att_chan_opt opt)
{
int err;
UNSET_FLAG(flag_long_subscribed);
sub_params_long.value_handle = long_chrc_handle;
sub_params_long.chan_opt = opt;
err = bt_gatt_unsubscribe(g_conn, &sub_params_long);
if (err < 0) {
FAIL("Failed to unsubscribe\n");
} else {
printk("Unsubscribe request sent\n");
}
}
static void setup(void)
{
int err;
err = bt_enable(NULL);
if (err != 0) {
FAIL("Bluetooth discover failed (err %d)\n", err);
}
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, device_found);
if (err != 0) {
FAIL("Scanning failed to start (err %d)\n", err);
}
printk("Scanning successfully started\n");
WAIT_FOR_FLAG(flag_is_connected);
err = bt_conn_set_security(g_conn, BT_SECURITY_L2);
if (err) {
FAIL("Starting encryption procedure failed (%d)\n", err);
}
WAIT_FOR_FLAG(flag_is_encrypted);
while (bt_eatt_count(g_conn) < CONFIG_BT_EATT_MAX) {
k_sleep(K_MSEC(10));
}
printk("EATT connected\n");
}
static void test_main_none(void)
{
setup();
gatt_discover(BT_ATT_CHAN_OPT_NONE);
gatt_subscribe_short(BT_ATT_CHAN_OPT_NONE);
gatt_subscribe_long(BT_ATT_CHAN_OPT_NONE);
WAIT_FOR_FLAG(flag_short_subscribed);
WAIT_FOR_FLAG(flag_long_subscribed);
printk("Subscribed\n");
while (num_notifications < NOTIFICATION_COUNT) {
k_sleep(K_MSEC(100));
}
gatt_unsubscribe_short(BT_ATT_CHAN_OPT_NONE);
gatt_unsubscribe_long(BT_ATT_CHAN_OPT_NONE);
WAIT_FOR_FLAG(flag_short_subscribed);
WAIT_FOR_FLAG(flag_long_subscribed);
printk("Unsubscribed\n");
PASS("GATT client Passed\n");
}
static void test_main_unenhanced(void)
{
setup();
gatt_discover(BT_ATT_CHAN_OPT_UNENHANCED_ONLY);
gatt_subscribe_short(BT_ATT_CHAN_OPT_UNENHANCED_ONLY);
gatt_subscribe_long(BT_ATT_CHAN_OPT_UNENHANCED_ONLY);
WAIT_FOR_FLAG(flag_short_subscribed);
WAIT_FOR_FLAG(flag_long_subscribed);
printk("Subscribed\n");
while (num_notifications < NOTIFICATION_COUNT) {
k_sleep(K_MSEC(100));
}
gatt_unsubscribe_short(BT_ATT_CHAN_OPT_UNENHANCED_ONLY);
gatt_unsubscribe_long(BT_ATT_CHAN_OPT_UNENHANCED_ONLY);
WAIT_FOR_FLAG(flag_short_subscribed);
WAIT_FOR_FLAG(flag_long_subscribed);
printk("Unsubscribed\n");
PASS("GATT client Passed\n");
}
static void test_main_enhanced(void)
{
setup();
gatt_discover(BT_ATT_CHAN_OPT_ENHANCED_ONLY);
gatt_subscribe_short(BT_ATT_CHAN_OPT_ENHANCED_ONLY);
gatt_subscribe_long(BT_ATT_CHAN_OPT_ENHANCED_ONLY);
WAIT_FOR_FLAG(flag_short_subscribed);
WAIT_FOR_FLAG(flag_long_subscribed);
printk("Subscribed\n");
while (num_notifications < NOTIFICATION_COUNT) {
k_sleep(K_MSEC(100));
}
gatt_unsubscribe_short(BT_ATT_CHAN_OPT_ENHANCED_ONLY);
gatt_unsubscribe_long(BT_ATT_CHAN_OPT_ENHANCED_ONLY);
WAIT_FOR_FLAG(flag_short_subscribed);
WAIT_FOR_FLAG(flag_long_subscribed);
printk("Unsubscribed\n");
PASS("GATT client Passed\n");
}
static void test_main_mixed(void)
{
setup();
gatt_discover(BT_ATT_CHAN_OPT_ENHANCED_ONLY);
gatt_subscribe_short(BT_ATT_CHAN_OPT_ENHANCED_ONLY);
gatt_subscribe_long(BT_ATT_CHAN_OPT_UNENHANCED_ONLY);
WAIT_FOR_FLAG(flag_short_subscribed);
WAIT_FOR_FLAG(flag_long_subscribed);
printk("Subscribed\n");
while (num_notifications < NOTIFICATION_COUNT) {
k_sleep(K_MSEC(100));
}
gatt_unsubscribe_short(BT_ATT_CHAN_OPT_UNENHANCED_ONLY);
gatt_unsubscribe_long(BT_ATT_CHAN_OPT_ENHANCED_ONLY);
WAIT_FOR_FLAG(flag_short_subscribed);
WAIT_FOR_FLAG(flag_long_subscribed);
printk("Unsubscribed\n");
PASS("GATT client Passed\n");
}
static const struct bst_test_instance test_vcs[] = {
{
.test_id = "gatt_client_none",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_none,
},
{
.test_id = "gatt_client_unenhanced",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_unenhanced,
},
{
.test_id = "gatt_client_enhanced",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_enhanced,
},
{
.test_id = "gatt_client_mixed",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_mixed,
},
BSTEST_END_MARKER,
};
struct bst_test_list *test_gatt_client_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_vcs);
}

View file

@ -0,0 +1,300 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "common.h"
extern enum bst_result_t bst_result;
CREATE_FLAG(flag_is_connected);
CREATE_FLAG(flag_short_subscribe);
CREATE_FLAG(flag_long_subscribe);
static struct bt_conn *g_conn;
#define ARRAY_ITEM(i, _) i
const uint8_t chrc_data[] = { LISTIFY(CHRC_SIZE, ARRAY_ITEM, (,)) }; /* 1, 2, 3 ... */
const uint8_t long_chrc_data[] = { LISTIFY(LONG_CHRC_SIZE, ARRAY_ITEM, (,)) }; /* 1, 2, 3 ... */
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err != 0) {
FAIL("Failed to connect to %s (%u)\n", addr, err);
return;
}
printk("Connected to %s\n", addr);
g_conn = bt_conn_ref(conn);
SET_FLAG(flag_is_connected);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
if (conn != g_conn) {
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);
bt_conn_unref(g_conn);
g_conn = NULL;
UNSET_FLAG(flag_is_connected);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};
static ssize_t read_test_chrc(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
printk("Read short\n");
return bt_gatt_attr_read(conn, attr, buf, len, offset, (void *)chrc_data,
sizeof(chrc_data));
}
static ssize_t read_long_test_chrc(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
printk("Read long\n");
return bt_gatt_attr_read(conn, attr, buf, len, offset, (void *)long_chrc_data,
sizeof(long_chrc_data));
}
static void short_subscribe(const struct bt_gatt_attr *attr, uint16_t value)
{
const bool notif_enabled = (value == BT_GATT_CCC_NOTIFY);
if (notif_enabled) {
SET_FLAG(flag_short_subscribe);
}
printk("Short notifications %s\n", notif_enabled ? "enabled" : "disabled");
}
static void long_subscribe(const struct bt_gatt_attr *attr, uint16_t value)
{
const bool notif_enabled = (value == BT_GATT_CCC_NOTIFY);
if (notif_enabled) {
SET_FLAG(flag_long_subscribe);
}
printk("Long notifications %s\n", notif_enabled ? "enabled" : "disabled");
}
BT_GATT_SERVICE_DEFINE(test_svc, BT_GATT_PRIMARY_SERVICE(TEST_SERVICE_UUID),
BT_GATT_CHARACTERISTIC(TEST_CHRC_UUID,
BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_READ,
BT_GATT_PERM_READ, read_test_chrc, NULL, NULL),
BT_GATT_CUD("Short test_svc format description", BT_GATT_PERM_READ),
BT_GATT_CCC(short_subscribe, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
BT_GATT_CHARACTERISTIC(TEST_LONG_CHRC_UUID,
BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_READ,
BT_GATT_PERM_READ, read_long_test_chrc, NULL, NULL),
BT_GATT_CCC(long_subscribe, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE));
static volatile size_t num_notifications_sent;
static void notification_sent(struct bt_conn *conn, void *user_data)
{
size_t *length = user_data;
printk("Sent notification #%u with length %d\n", num_notifications_sent++, *length);
}
static void short_notify(enum bt_att_chan_opt opt)
{
static size_t length = CHRC_SIZE;
static struct bt_gatt_notify_params params = {
.attr = &attr_test_svc[1],
.data = chrc_data,
.len = CHRC_SIZE,
.func = notification_sent,
.user_data = &length,
.uuid = NULL,
};
int err;
params.chan_opt = opt;
do {
err = bt_gatt_notify_cb(g_conn, &params);
if (err == -ENOMEM) {
k_sleep(K_MSEC(10));
} else if (err) {
FAIL("Short notify failed (err %d)\n", err);
}
} while (err);
}
static void long_notify(enum bt_att_chan_opt opt)
{
static size_t length = LONG_CHRC_SIZE;
static struct bt_gatt_notify_params params = {
.attr = &attr_test_svc[5],
.data = long_chrc_data,
.len = LONG_CHRC_SIZE,
.func = notification_sent,
.user_data = &length,
.uuid = NULL,
};
int err;
params.chan_opt = opt;
do {
err = bt_gatt_notify_cb(g_conn, &params);
if (err == -ENOMEM) {
k_sleep(K_MSEC(10));
} else if (err) {
FAIL("Long notify failed (err %d)\n", err);
}
} while (err);
}
static void setup(void)
{
int err;
const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
};
err = bt_enable(NULL);
if (err != 0) {
FAIL("Bluetooth init failed (err %d)\n", err);
return;
}
printk("Bluetooth initialized\n");
err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
if (err != 0) {
FAIL("Advertising failed to start (err %d)\n", err);
return;
}
printk("Advertising successfully started\n");
WAIT_FOR_FLAG(flag_is_connected);
while (bt_eatt_count(g_conn) < CONFIG_BT_EATT_MAX) {
k_sleep(K_MSEC(100));
}
printk("EATT connected\n");
WAIT_FOR_FLAG(flag_short_subscribe);
WAIT_FOR_FLAG(flag_long_subscribe);
}
static void test_main_none(void)
{
setup();
for (int i = 0; i < NOTIFICATION_COUNT / 2; i++) {
short_notify(BT_ATT_CHAN_OPT_NONE);
long_notify(BT_ATT_CHAN_OPT_NONE);
}
while (num_notifications_sent < NOTIFICATION_COUNT) {
k_sleep(K_MSEC(100));
}
PASS("GATT server passed\n");
}
static void test_main_enhanced(void)
{
setup();
for (int i = 0; i < NOTIFICATION_COUNT / 2; i++) {
short_notify(BT_ATT_CHAN_OPT_ENHANCED_ONLY);
long_notify(BT_ATT_CHAN_OPT_ENHANCED_ONLY);
}
while (num_notifications_sent < NOTIFICATION_COUNT) {
k_sleep(K_MSEC(100));
}
PASS("GATT server passed\n");
}
static void test_main_unenhanced(void)
{
setup();
for (int i = 0; i < NOTIFICATION_COUNT / 2; i++) {
short_notify(BT_ATT_CHAN_OPT_UNENHANCED_ONLY);
long_notify(BT_ATT_CHAN_OPT_UNENHANCED_ONLY);
}
while (num_notifications_sent < NOTIFICATION_COUNT) {
k_sleep(K_MSEC(100));
}
PASS("GATT server passed\n");
}
static void test_main_mixed(void)
{
setup();
for (int i = 0; i < NOTIFICATION_COUNT / 2; i++) {
short_notify(BT_ATT_CHAN_OPT_UNENHANCED_ONLY);
long_notify(BT_ATT_CHAN_OPT_ENHANCED_ONLY);
}
while (num_notifications_sent < NOTIFICATION_COUNT) {
k_sleep(K_MSEC(100));
}
PASS("GATT server passed\n");
}
static const struct bst_test_instance test_gatt_server[] = {
{
.test_id = "gatt_server_none",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_none,
},
{
.test_id = "gatt_server_unenhanced",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_unenhanced,
},
{
.test_id = "gatt_server_enhanced",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_enhanced,
},
{
.test_id = "gatt_server_mixed",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_mixed,
},
BSTEST_END_MARKER,
};
struct bst_test_list *test_gatt_server_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_gatt_server);
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "bstests.h"
extern struct bst_test_list *test_gatt_server_install(struct bst_test_list *tests);
extern struct bst_test_list *test_gatt_client_install(struct bst_test_list *tests);
bst_test_install_t test_installers[] = {
test_gatt_server_install,
test_gatt_client_install,
NULL
};
void main(void)
{
bst_main();
}

View file

@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
verbosity_level=2
process_ids=""
exit_code=0
function Execute() {
if [ ! -f $1 ]; then
echo -e " \e[91m$(pwd)/$(basename $1) cannot be found (did you forget to\
compile it?)\e[39m"
exit 1
fi
timeout 30 $@ &
process_ids="$process_ids $!"
}
: "${BSIM_OUT_PATH:?BSIM_OUT_PATH must be defined}"
#Give a default value to BOARD if it does not have one yet:
BOARD="${BOARD:-nrf52_bsim}"
cd ${BSIM_OUT_PATH}/bin
Execute ./bs_${BOARD}_tests_bsim_bluetooth_host_gatt_notify_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=0 -testid=${client_id}
Execute ./bs_${BOARD}_tests_bsim_bluetooth_host_gatt_notify_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=1 -testid=${server_id}
Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \
-D=2 -sim_length=60e6 $@
for process_id in $process_ids; do
wait $process_id || let "exit_code=$?"
done
exit $exit_code #the last exit code != 0

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_enhanced_enhanced" \
server_id="gatt_server_enhanced" \
client_id="gatt_client_enhanced" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_enhanced_mixed" \
server_id="gatt_server_enhanced" \
client_id="gatt_client_mixed" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_enhanced_none" \
server_id="gatt_server_enhanced" \
client_id="gatt_client_none" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_enhanced_unenhanced" \
server_id="gatt_server_enhanced" \
client_id="gatt_client_unenhanced" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_mixed_enhanced" \
server_id="gatt_server_mixed" \
client_id="gatt_client_enhanced" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_mixed_mixed" \
server_id="gatt_server_mixed" \
client_id="gatt_client_mixed" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_mixed_none" \
server_id="gatt_server_mixed" \
client_id="gatt_client_none" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_mixed_unenhanced" \
server_id="gatt_server_mixed" \
client_id="gatt_client_unenhanced" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_none_enhanced" \
server_id="gatt_server_none" \
client_id="gatt_client_enhanced" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_none_mixed" \
server_id="gatt_server_none" \
client_id="gatt_client_mixed" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_none_none" \
server_id="gatt_server_none" \
client_id="gatt_client_none" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_none_unenhanced" \
server_id="gatt_server_none" \
client_id="gatt_client_unenhanced" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_unenhanced_enhanced" \
server_id="gatt_server_unenhanced" \
client_id="gatt_client_enhanced" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_unenhanced_mixed" \
server_id="gatt_server_unenhanced" \
client_id="gatt_client_mixed" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_unenhanced_none" \
server_id="gatt_server_unenhanced" \
client_id="gatt_client_none" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="gatt_notify_unenhanced_unenhanced" \
server_id="gatt_server_unenhanced" \
client_id="gatt_client_unenhanced" \
$(dirname "${BASH_SOURCE[0]}")/_run_test.sh

View file

@ -0,0 +1,21 @@
# 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 REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(bsim_test_gatt)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources} )
zephyr_include_directories(
$ENV{BSIM_COMPONENTS_PATH}/libUtilv1/src/
$ENV{BSIM_COMPONENTS_PATH}/libPhyComv1/src/
)

View file

@ -0,0 +1,18 @@
CONFIG_BT=y
CONFIG_BT_DEVICE_NAME="GATT tester"
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_BT_GATT_AUTO_DISCOVER_CCC=y
CONFIG_BT_SMP=y
CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y
CONFIG_BT_L2CAP_ECRED=y
CONFIG_BT_EATT=y
CONFIG_BT_EATT_MAX=5
CONFIG_BT_GATT_NOTIFY_MULTIPLE=y
CONFIG_ASSERT=y
CONFIG_BT_TESTING=y
CONFIG_BT_DEBUG_LOG=y

View file

@ -0,0 +1,20 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "common.h"
void test_tick(bs_time_t HW_device_time)
{
if (bst_result != Passed) {
FAIL("test failed (not passed after %i seconds)\n", WAIT_TIME);
}
}
void test_init(void)
{
bst_ticker_set_next_tick_absolute(WAIT_TIME);
bst_result = In_progress;
}

View file

@ -0,0 +1,74 @@
/**
* Common functions and helpers for BSIM GATT tests
*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include "bs_types.h"
#include "bs_tracing.h"
#include "time_machine.h"
#include "bstests.h"
#include <zephyr/types.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
extern enum bst_result_t bst_result;
#define WAIT_TIME (60 * 1e6) /*seconds*/
#define CREATE_FLAG(flag) static atomic_t flag = (atomic_t)false
#define FORCE_FLAG(flag, val) (void)atomic_set(&flag, (atomic_t)val)
#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 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)
#define CHRC_SIZE 10
#define LONG_CHRC_SIZE 40
#define TEST_SERVICE_UUID \
BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, \
0x07, 0x08, 0x09, 0x00, 0x00)
#define TEST_CHRC_UUID \
BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, \
0x07, 0x08, 0x09, 0xFF, 0x00)
#define TEST_LONG_CHRC_UUID \
BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, \
0x07, 0x08, 0x09, 0xFF, 0x11)
void test_tick(bs_time_t HW_device_time);
void test_init(void);
#define NOTIFICATION_COUNT 10
BUILD_ASSERT(NOTIFICATION_COUNT % 2 == 0);

View file

@ -0,0 +1,378 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gatt.h>
#include "common.h"
CREATE_FLAG(flag_is_connected);
CREATE_FLAG(flag_is_encrypted);
CREATE_FLAG(flag_discover_complete);
CREATE_FLAG(flag_write_complete);
CREATE_FLAG(flag_subscribed_short);
CREATE_FLAG(flag_subscribed_long);
static struct bt_conn *g_conn;
static uint16_t chrc_handle;
static uint16_t long_chrc_handle;
static uint16_t csf_handle;
static struct bt_uuid *test_svc_uuid = TEST_SERVICE_UUID;
static void exchange_func(struct bt_conn *conn, uint8_t err, struct bt_gatt_exchange_params *params)
{
if (!err) {
printk("MTU exchange done\n");
} else {
printk("MTU exchange failed (err %" PRIu8 ")\n", err);
}
}
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
static struct bt_gatt_exchange_params exchange_params;
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err != 0) {
FAIL("Failed to connect to %s (%u)\n", addr, err);
return;
}
printk("Connected to %s\n", addr);
SET_FLAG(flag_is_connected);
exchange_params.func = exchange_func;
err = bt_gatt_exchange_mtu(conn, &exchange_params);
if (err) {
printk("MTU exchange failed (err %d)", err);
}
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
if (conn != g_conn) {
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);
bt_conn_unref(g_conn);
g_conn = NULL;
UNSET_FLAG(flag_is_connected);
}
void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
{
if (err) {
FAIL("Encryption failer (%d)\n", err);
} else if (level < BT_SECURITY_L2) {
FAIL("Insufficient sec level (%d)\n", level);
} else {
SET_FLAG(flag_is_encrypted);
}
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
.security_changed = security_changed,
};
void device_found(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;
}
/* We're only interested in connectable events */
if (type != BT_HCI_ADV_IND && type != BT_HCI_ADV_DIRECT_IND) {
return;
}
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
printk("Device found: %s (RSSI %d)\n", addr_str, rssi);
printk("Stopping scan\n");
err = bt_le_scan_stop();
if (err != 0) {
FAIL("Could not stop scan: %d");
return;
}
err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT, &g_conn);
if (err != 0) {
FAIL("Could not connect to peer: %d", err);
}
}
static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
int err;
if (attr == NULL) {
if (chrc_handle == 0 || long_chrc_handle == 0) {
FAIL("Did not discover chrc (%x) or long_chrc (%x)", chrc_handle,
long_chrc_handle);
}
(void)memset(params, 0, sizeof(*params));
SET_FLAG(flag_discover_complete);
return BT_GATT_ITER_STOP;
}
printk("[ATTRIBUTE] handle %u\n", attr->handle);
if (params->type == BT_GATT_DISCOVER_PRIMARY &&
bt_uuid_cmp(params->uuid, TEST_SERVICE_UUID) == 0) {
printk("Found test service\n");
params->uuid = NULL;
params->start_handle = attr->handle + 1;
params->type = BT_GATT_DISCOVER_CHARACTERISTIC;
err = bt_gatt_discover(conn, params);
if (err != 0) {
FAIL("Discover failed (err %d)\n", err);
}
return BT_GATT_ITER_STOP;
} else if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) {
const struct bt_gatt_chrc *chrc = (struct bt_gatt_chrc *)attr->user_data;
if (bt_uuid_cmp(chrc->uuid, TEST_CHRC_UUID) == 0) {
printk("Found chrc\n");
chrc_handle = chrc->value_handle;
} else if (bt_uuid_cmp(chrc->uuid, TEST_LONG_CHRC_UUID) == 0) {
printk("Found long_chrc\n");
long_chrc_handle = chrc->value_handle;
} else if (bt_uuid_cmp(chrc->uuid, BT_UUID_GATT_CLIENT_FEATURES) == 0) {
printk("Found csf\n");
csf_handle = chrc->value_handle;
}
}
return BT_GATT_ITER_CONTINUE;
}
static void gatt_discover(const struct bt_uuid *uuid, uint8_t type)
{
static struct bt_gatt_discover_params discover_params;
int err;
printk("Discovering services and characteristics\n");
discover_params.uuid = uuid;
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 = type;
UNSET_FLAG(flag_discover_complete);
err = bt_gatt_discover(g_conn, &discover_params);
if (err != 0) {
FAIL("Discover failed(err %d)\n", err);
}
WAIT_FOR_FLAG(flag_discover_complete);
printk("Discover complete\n");
}
static void test_subscribed(struct bt_conn *conn,
uint8_t err,
struct bt_gatt_subscribe_params *params)
{
if (err) {
FAIL("Subscribe failed (err %d)\n", err);
}
if (!params) {
FAIL("params NULL\n");
}
if (params->value_handle == chrc_handle) {
FORCE_FLAG(flag_subscribed_short, (bool)params->value);
printk("Subscribed to short characteristic\n");
} else if (params->value_handle == long_chrc_handle) {
FORCE_FLAG(flag_subscribed_long, (bool)params->value);
printk("Subscribed to long characteristic\n");
} else {
FAIL("Unknown handle %d\n", params->value_handle);
}
}
static volatile size_t num_notifications;
uint8_t test_notify(struct bt_conn *conn, struct bt_gatt_subscribe_params *params, const void *data,
uint16_t length)
{
printk("Received notification #%u with length %d\n", num_notifications++, length);
return BT_GATT_ITER_CONTINUE;
}
static struct bt_gatt_discover_params disc_params_short;
static struct bt_gatt_subscribe_params sub_params_short = {
.notify = test_notify,
.subscribe = test_subscribed,
.ccc_handle = 0, /* Auto-discover CCC*/
.disc_params = &disc_params_short, /* Auto-discover CCC */
.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE,
.value = BT_GATT_CCC_NOTIFY,
};
static struct bt_gatt_discover_params disc_params_long;
static struct bt_gatt_subscribe_params sub_params_long = {
.notify = test_notify,
.subscribe = test_subscribed,
.ccc_handle = 0, /* Auto-discover CCC*/
.disc_params = &disc_params_long, /* Auto-discover CCC */
.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE,
.value = BT_GATT_CCC_NOTIFY,
};
static void write_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params)
{
if (err != BT_ATT_ERR_SUCCESS) {
FAIL("Write failed: 0x%02X\n", err);
}
SET_FLAG(flag_write_complete);
}
static void write_csf(void)
{
/* Client Supported Features Characteristic Value
* Bit 0: Robust Caching
* Bit 1: EATT
* Bit 2: Multiple Handle Value Notifications
*/
static const uint8_t csf[] = { BIT(2) };
static struct bt_gatt_write_params write_params = {
.func = write_cb,
.offset = 0,
.data = csf,
.length = sizeof(csf),
};
int err;
printk("Writing to Client Supported Features Characteristic\n");
write_params.handle = csf_handle;
UNSET_FLAG(flag_write_complete);
err = bt_gatt_write(g_conn, &write_params);
if (err) {
FAIL("bt_gatt_write failed (err %d)\n", err);
}
WAIT_FOR_FLAG(flag_write_complete);
printk("Success\n");
}
static void subscribe(struct bt_gatt_subscribe_params *params, bool subscribe)
{
int err;
if (subscribe) {
err = bt_gatt_subscribe(g_conn, params);
} else {
err = bt_gatt_unsubscribe(g_conn, params);
}
if (err < 0) {
FAIL("Failed to %ssubscribe (err %d)\n", subscribe ? "un":"", err);
} else {
printk("%ssubscribe request sent\n", subscribe ? "un":"");
}
}
static void test_main(void)
{
int err;
err = bt_enable(NULL);
if (err != 0) {
FAIL("Bluetooth discover failed (err %d)\n", err);
}
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, device_found);
if (err != 0) {
FAIL("Scanning failed to start (err %d)\n", err);
}
printk("Scanning successfully started\n");
WAIT_FOR_FLAG(flag_is_connected);
err = bt_conn_set_security(g_conn, BT_SECURITY_L2);
if (err) {
FAIL("Starting encryption procedure failed (%d)\n", err);
}
WAIT_FOR_FLAG(flag_is_encrypted);
while (bt_eatt_count(g_conn) < CONFIG_BT_EATT_MAX) {
k_sleep(K_MSEC(10));
}
printk("EATT connected\n");
gatt_discover(test_svc_uuid, BT_GATT_DISCOVER_PRIMARY);
gatt_discover(BT_UUID_GATT_CLIENT_FEATURES, BT_GATT_DISCOVER_CHARACTERISTIC);
write_csf();
sub_params_short.value_handle = chrc_handle;
sub_params_long.value_handle = long_chrc_handle;
subscribe(&sub_params_short, true);
subscribe(&sub_params_long, true);
WAIT_FOR_FLAG(flag_subscribed_short);
WAIT_FOR_FLAG(flag_subscribed_long);
printk("Subscribed\n");
while (num_notifications < NOTIFICATION_COUNT) {
k_sleep(K_MSEC(100));
}
subscribe(&sub_params_short, false);
subscribe(&sub_params_long, false);
WAIT_FOR_FLAG_UNSET(flag_subscribed_short);
WAIT_FOR_FLAG_UNSET(flag_subscribed_long);
printk("Unsubscribed\n");
PASS("GATT client Passed\n");
}
static const struct bst_test_instance test_vcs[] = {
{
.test_id = "gatt_client",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main,
},
BSTEST_END_MARKER,
};
struct bst_test_list *test_gatt_client_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_vcs);
}

View file

@ -0,0 +1,195 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "common.h"
extern enum bst_result_t bst_result;
CREATE_FLAG(flag_is_connected);
CREATE_FLAG(flag_short_subscribe);
CREATE_FLAG(flag_long_subscribe);
static struct bt_conn *g_conn;
#define ARRAY_ITEM(i, _) i
const uint8_t chrc_data[] = { LISTIFY(CHRC_SIZE, ARRAY_ITEM, (,)) }; /* 1, 2, 3 ... */
const uint8_t long_chrc_data[] = { LISTIFY(LONG_CHRC_SIZE, ARRAY_ITEM, (,)) }; /* 1, 2, 3 ... */
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err != 0) {
FAIL("Failed to connect to %s (%u)\n", addr, err);
return;
}
printk("Connected to %s\n", addr);
g_conn = bt_conn_ref(conn);
SET_FLAG(flag_is_connected);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
if (conn != g_conn) {
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);
bt_conn_unref(g_conn);
g_conn = NULL;
UNSET_FLAG(flag_is_connected);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};
static void short_subscribe(const struct bt_gatt_attr *attr, uint16_t value)
{
const bool notif_enabled = (value == BT_GATT_CCC_NOTIFY);
if (notif_enabled) {
SET_FLAG(flag_short_subscribe);
}
printk("Short notifications %s\n", notif_enabled ? "enabled" : "disabled");
}
static void long_subscribe(const struct bt_gatt_attr *attr, uint16_t value)
{
const bool notif_enabled = (value == BT_GATT_CCC_NOTIFY);
if (notif_enabled) {
SET_FLAG(flag_long_subscribe);
}
printk("Long notifications %s\n", notif_enabled ? "enabled" : "disabled");
}
BT_GATT_SERVICE_DEFINE(test_svc, BT_GATT_PRIMARY_SERVICE(TEST_SERVICE_UUID),
BT_GATT_CHARACTERISTIC(TEST_CHRC_UUID,
BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_READ,
BT_GATT_PERM_READ, NULL, NULL, NULL),
BT_GATT_CUD("Short test_svc format description", BT_GATT_PERM_READ),
BT_GATT_CCC(short_subscribe, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
BT_GATT_CHARACTERISTIC(TEST_LONG_CHRC_UUID,
BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_READ,
BT_GATT_PERM_READ, NULL, NULL, NULL),
BT_GATT_CCC(long_subscribe, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE));
static volatile size_t num_notifications_sent;
static void notification_sent(struct bt_conn *conn, void *user_data)
{
printk("Sent notification #%u\n", num_notifications_sent++);
}
static inline void multiple_notify(const struct bt_gatt_attr *attrs[2])
{
int err;
static struct bt_gatt_notify_params params[] = {
{
.data = long_chrc_data,
.len = LONG_CHRC_SIZE,
.func = notification_sent,
.uuid = NULL,
},
{
.data = chrc_data,
.len = CHRC_SIZE,
.func = notification_sent,
.uuid = NULL,
},
};
params[0].attr = attrs[0];
params[1].attr = attrs[1];
do {
err = bt_gatt_notify_multiple(g_conn, ARRAY_SIZE(params), params);
if (err == -ENOMEM) {
k_sleep(K_MSEC(10));
} else if (err) {
FAIL("multiple notify failed (err %d)\n", err);
}
} while (err);
}
static void test_main(void)
{
int err;
const struct bt_gatt_attr *attrs[2];
const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
};
err = bt_enable(NULL);
if (err != 0) {
FAIL("Bluetooth init failed (err %d)\n", err);
return;
}
printk("Bluetooth initialized\n");
err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
if (err != 0) {
FAIL("Advertising failed to start (err %d)\n", err);
return;
}
printk("Advertising successfully started\n");
WAIT_FOR_FLAG(flag_is_connected);
WAIT_FOR_FLAG(flag_short_subscribe);
WAIT_FOR_FLAG(flag_long_subscribe);
/* Long characteristic [attr=value] */
attrs[0] = bt_gatt_find_by_uuid(NULL, 0, TEST_LONG_CHRC_UUID);
/* Short characteristic [attr=descriptor] */
attrs[1] = &attr_test_svc[1];
for (int i = 0; i < NOTIFICATION_COUNT / 2; i++) {
multiple_notify(attrs);
}
while (num_notifications_sent < NOTIFICATION_COUNT / 2) {
k_sleep(K_MSEC(100));
}
k_sleep(K_MSEC(1000));
if (num_notifications_sent != NOTIFICATION_COUNT) {
FAIL("Unexpected notification callback value\n");
}
PASS("GATT server passed\n");
}
static const struct bst_test_instance test_gatt_server[] = {
{
.test_id = "gatt_server",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main,
},
BSTEST_END_MARKER,
};
struct bst_test_list *test_gatt_server_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_gatt_server);
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "bstests.h"
extern struct bst_test_list *test_gatt_server_install(struct bst_test_list *tests);
extern struct bst_test_list *test_gatt_client_install(struct bst_test_list *tests);
bst_test_install_t test_installers[] = {
test_gatt_server_install,
test_gatt_client_install,
NULL
};
void main(void)
{
bst_main();
}

View file

@ -0,0 +1,42 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
# Usage:
# one script instance per device, e.g. to run gdb on the client:
# `_notify-debug.sh client debug`
# `_notify-debug.sh server`
# `_notify-debug.sh`
#
# GDB can be run on the two devices at the same time without issues, just append
# `debug` when running the script.
simulation_id="notify_multiple"
verbosity_level=2
process_ids=""; exit_code=0
: "${BSIM_OUT_PATH:?BSIM_OUT_PATH must be defined}"
#Give a default value to BOARD if it does not have one yet:
BOARD="${BOARD:-nrf52_bsim}"
cd ${BSIM_OUT_PATH}/bin
if [[ $2 == "debug" ]]; then
GDB_P="gdb --args "
fi
if [[ $1 == "client" ]]; then
$GDB_P ./bs_${BOARD}_tests_bsim_bluetooth_host_gatt_notify_multiple_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=0 -testid=gatt_client
elif [[ $1 == "server" ]]; then
$GDB_P ./bs_${BOARD}_tests_bsim_bluetooth_host_gatt_notify_multiple_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=1 -testid=gatt_server
else
./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \
-D=2 -sim_length=60e6 $@
fi

View file

@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
simulation_id="notify_multiple"
verbosity_level=2
process_ids=""; exit_code=0
function Execute(){
if [ ! -f $1 ]; then
echo -e " \e[91m`pwd`/`basename $1` cannot be found (did you forget to\
compile it?)\e[39m"
exit 1
fi
timeout 120 $@ & process_ids="$process_ids $!"
}
: "${BSIM_OUT_PATH:?BSIM_OUT_PATH must be defined}"
#Give a default value to BOARD if it does not have one yet:
BOARD="${BOARD:-nrf52_bsim}"
cd ${BSIM_OUT_PATH}/bin
Execute ./bs_${BOARD}_tests_bsim_bluetooth_host_gatt_notify_multiple_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=0 -testid=gatt_client
Execute ./bs_${BOARD}_tests_bsim_bluetooth_host_gatt_notify_multiple_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=1 -testid=gatt_server
Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \
-D=2 -sim_length=60e6 $@
for process_id in $process_ids; do
wait $process_id || let "exit_code=$?"
done
exit $exit_code #the last exit code != 0

View file

@ -0,0 +1,27 @@
# 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 REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(bsim_test_gatt_settings)
target_sources(app PRIVATE
src/server.c
src/client.c
src/utils.c
src/gatt_utils.c
src/settings.c
src/main.c
)
zephyr_include_directories(
$ENV{BSIM_COMPONENTS_PATH}/libUtilv1/src/
$ENV{BSIM_COMPONENTS_PATH}/libPhyComv1/src/
)

View file

@ -0,0 +1,26 @@
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_DEVICE_NAME="GATT settings"
CONFIG_LOG=y
CONFIG_ASSERT=y
CONFIG_BT_TESTING=y
CONFIG_BT_AUTO_PHY_UPDATE=n
CONFIG_BT_AUTO_DATA_LEN_UPDATE=n
CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n
CONFIG_BT_SMP=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_BT_GATT_DYNAMIC_DB=y
CONFIG_BT_GATT_CACHING=y
CONFIG_SETTINGS=y
CONFIG_SETTINGS_CUSTOM=y
CONFIG_BT_SETTINGS=y
CONFIG_BT_GATT_AUTO_DISCOVER_CCC=y
CONFIG_BT_GATT_AUTO_RESUBSCRIBE=n
CONFIG_BT_PRIVACY=n

View file

@ -0,0 +1,26 @@
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_DEVICE_NAME="GATT settings"
CONFIG_LOG=y
CONFIG_ASSERT=y
CONFIG_BT_TESTING=y
CONFIG_BT_AUTO_PHY_UPDATE=n
CONFIG_BT_AUTO_DATA_LEN_UPDATE=n
CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n
CONFIG_BT_SMP=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_BT_GATT_DYNAMIC_DB=y
CONFIG_BT_GATT_CACHING=y
CONFIG_SETTINGS=y
CONFIG_SETTINGS_CUSTOM=y
CONFIG_BT_SETTINGS=y
CONFIG_BT_GATT_AUTO_DISCOVER_CCC=y
CONFIG_BT_GATT_AUTO_RESUBSCRIBE=n
CONFIG_BT_PRIVACY=y

View file

@ -0,0 +1,172 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "utils.h"
#include "gatt_utils.h"
#include <zephyr/bluetooth/addr.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/settings/settings.h>
#include <zephyr/toolchain/gcc.h>
#include <stdint.h>
#include <string.h>
void client_round_0(void)
{
struct bt_conn *conn;
printk("start round 0...........\n");
conn = connect_as_peripheral();
printk("connected: conn %p\n", conn);
gatt_discover();
activate_robust_caching();
/* subscribe to the SC indication, so we don't have to ATT read to
* become change-aware.
*/
gatt_subscribe_to_service_changed(true);
read_test_char(true);
/* We should normally wait until we are bonded to write the CCC / CF
* characteristics, but here we bond after the fact on purpose, to
* simulate a client that has this exact behavior.
* The CCC and CF should still persist on reboot.
*/
wait_bonded();
disconnect(conn);
}
void client_round_1(void)
{
struct bt_conn *conn;
printk("start round 1...........\n");
conn = connect_as_peripheral();
printk("connected: conn %p\n", conn);
wait_secured();
/* server should remember we are change-aware */
read_test_char(true);
disconnect(conn);
}
void client_round_2(void)
{
struct bt_conn *conn;
printk("start round 2...........\n");
conn = connect_as_peripheral();
printk("connected: conn %p\n", conn);
wait_secured();
/* We are change-unaware. wait until the Service Changed indication is
* received, that should then make us change-aware.
*/
wait_for_sc_indication();
read_test_char(true);
/* We sleep just enough so that the server's `delayed store` work item
* is executed. We still trigger a disconnect, even though the server
* device will be unresponsive for this round.
*/
k_sleep(K_MSEC(CONFIG_BT_SETTINGS_DELAYED_STORE_MS));
disconnect(conn);
}
void client_round_3(void)
{
struct bt_conn *conn;
printk("start round 3...........\n");
conn = connect_as_peripheral();
printk("connected: conn %p\n", conn);
wait_secured();
/* server should remember we are change-aware */
read_test_char(true);
/* Unsubscribe from the SC indication.
*
* In the next round, we will be change-unaware, so the first ATT read
* will fail, but the second one will succeed and we will be marked as
* change-aware again.
*/
gatt_subscribe_to_service_changed(false);
disconnect(conn);
}
void client_round_4(void)
{
struct bt_conn *conn;
printk("start round 4...........\n");
conn = connect_as_peripheral();
printk("connected: conn %p\n", conn);
wait_secured();
/* GATT DB has changed again.
* Before disc: svc1
* After disc: svc1 + svc2
* At boot: svc1
* Expect a failure on the first read of the same GATT handle.
*/
read_test_char(false);
read_test_char(true);
disconnect(conn);
}
void client_round_5(void)
{
printk("start round 5...........\n");
printk("don't need to do anything, central will "
"not connect to us\n");
}
void client_round_6(void)
{
struct bt_conn *conn;
printk("start round 6...........\n");
conn = connect_as_peripheral();
printk("connected: conn %p\n", conn);
wait_secured();
/* GATT DB has changed again.
* Expect a failure on the first read of the same GATT handle.
*/
read_test_char(false);
read_test_char(true);
disconnect(conn);
}
void client_procedure(void)
{
bt_enable(NULL);
settings_load();
client_round_0();
client_round_1();
client_round_2();
client_round_3();
client_round_4();
client_round_5();
client_round_6();
PASS("PASS\n");
}

View file

@ -0,0 +1,373 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "utils.h"
#include "argparse.h"
#include "bs_pc_backchannel.h"
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/sys/__assert.h>
/* Custom Service Variables */
static struct bt_uuid_128 test_svc_uuid = BT_UUID_INIT_128(
0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12,
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12);
static struct bt_uuid_128 test_svc_uuid_2 = BT_UUID_INIT_128(
0xf1, 0xdd, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12,
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12);
static struct bt_uuid_128 test_chrc_uuid = BT_UUID_INIT_128(
0xf2, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12,
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12);
static uint8_t test_value[] = { 'T', 'e', 's', 't', '\0' };
DEFINE_FLAG(flag_client_read);
static ssize_t read_test(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
const char *value = attr->user_data;
printk("Client has read from test char\n");
SET_FLAG(flag_client_read);
return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
strlen(value));
}
void wait_for_client_read(void)
{
WAIT_FOR_FLAG(flag_client_read);
}
static ssize_t write_test(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset,
uint8_t flags)
{
uint8_t *value = attr->user_data;
printk("Client has written to test char\n");
if (offset + len > sizeof(test_value)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
memcpy(value + offset, buf, len);
return len;
}
static struct bt_gatt_attr test_attrs[] = {
/* Vendor Primary Service Declaration */
BT_GATT_PRIMARY_SERVICE(&test_svc_uuid),
BT_GATT_CHARACTERISTIC(&test_chrc_uuid.uuid,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ |
BT_GATT_PERM_WRITE,
read_test, write_test, test_value),
};
static struct bt_gatt_attr test_attrs_2[] = {
/* Vendor Primary Service Declaration */
BT_GATT_PRIMARY_SERVICE(&test_svc_uuid_2),
BT_GATT_CHARACTERISTIC(&test_chrc_uuid.uuid,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ_ENCRYPT |
BT_GATT_PERM_WRITE_ENCRYPT,
read_test, write_test, test_value),
};
static struct bt_gatt_service test_svc = BT_GATT_SERVICE(test_attrs);
static struct bt_gatt_service test_svc_2 = BT_GATT_SERVICE(test_attrs_2);
void gatt_register_service_1(void)
{
int err = bt_gatt_service_register(&test_svc);
__ASSERT(!err, "Failed to register GATT service (err %d)\n", err);
}
void gatt_register_service_2(void)
{
/* This service is only used to trigger a GATT DB change.
* No reads or writes will be attempted.
*/
int err = bt_gatt_service_register(&test_svc_2);
__ASSERT(!err, "Failed to register GATT service (err %d)\n", err);
}
/* We need to discover:
* - Dynamic service
* - Client Features (to set robust caching)
* - Service Changed (to sub to indications)
*/
enum GATT_HANDLES {
CLIENT_FEATURES,
SERVICE_CHANGED,
TEST_CHAR,
NUM_HANDLES,
};
uint16_t gatt_handles[NUM_HANDLES] = {0};
DEFINE_FLAG(flag_discovered);
static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
int err;
if (attr == NULL) {
for (int i = 0; i < ARRAY_SIZE(gatt_handles); i++) {
printk("handle[%d] = 0x%x\n", i, gatt_handles[i]);
if (gatt_handles[i] == 0) {
FAIL("Did not discover all characteristics\n");
}
}
(void)memset(params, 0, sizeof(*params));
SET_FLAG(flag_discovered);
return BT_GATT_ITER_STOP;
}
if (params->type == BT_GATT_DISCOVER_PRIMARY &&
bt_uuid_cmp(params->uuid, &test_svc_uuid.uuid) == 0) {
printk("Found test service\n");
params->uuid = NULL;
params->start_handle = attr->handle + 1;
params->type = BT_GATT_DISCOVER_CHARACTERISTIC;
err = bt_gatt_discover(conn, params);
if (err != 0) {
FAIL("Discover failed (err %d)\n", err);
}
return BT_GATT_ITER_STOP;
} else if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) {
const struct bt_gatt_chrc *chrc = (struct bt_gatt_chrc *)attr->user_data;
if (bt_uuid_cmp(chrc->uuid, BT_UUID_GATT_CLIENT_FEATURES) == 0) {
printk("Found client supported features\n");
gatt_handles[CLIENT_FEATURES] = chrc->value_handle;
} else if (bt_uuid_cmp(chrc->uuid, BT_UUID_GATT_SC) == 0) {
printk("Found service changed\n");
gatt_handles[SERVICE_CHANGED] = chrc->value_handle;
} else if (bt_uuid_cmp(chrc->uuid, &test_chrc_uuid.uuid) == 0) {
printk("Found test characteristic\n");
gatt_handles[TEST_CHAR] = chrc->value_handle;
}
}
return BT_GATT_ITER_CONTINUE;
}
DEFINE_FLAG(flag_sc_indicated);
static uint8_t sc_indicated(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
const void *data, uint16_t length)
{
if (!data) {
params->value_handle = 0U;
return BT_GATT_ITER_STOP;
}
printk("SC received\n");
SET_FLAG(flag_sc_indicated);
return BT_GATT_ITER_CONTINUE;
}
void wait_for_sc_indication(void)
{
WAIT_FOR_FLAG(flag_sc_indicated);
}
DEFINE_FLAG(flag_sc_subscribed);
static void sc_subscribed(struct bt_conn *conn, uint8_t err,
struct bt_gatt_subscribe_params *params)
{
if (params->value) {
printk("SC subscribed\n");
SET_FLAG(flag_sc_subscribed);
} else {
printk("SC unsubscribed\n");
UNSET_FLAG(flag_sc_subscribed);
}
}
static struct bt_gatt_discover_params disc_params;
static struct bt_gatt_subscribe_params subscribe_params;
void gatt_subscribe_to_service_changed(bool subscribe)
{
int err;
subscribe_params.value_handle = gatt_handles[SERVICE_CHANGED];
subscribe_params.notify = sc_indicated;
subscribe_params.subscribe = sc_subscribed;
if (subscribe) {
/* Use the BT_GATT_AUTO_DISCOVER_CCC feature */
subscribe_params.ccc_handle = 0;
subscribe_params.disc_params = &disc_params,
subscribe_params.value = BT_GATT_CCC_INDICATE;
subscribe_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
err = bt_gatt_subscribe(get_conn(), &subscribe_params);
WAIT_FOR_FLAG(flag_sc_subscribed);
} else {
/* Params are already set to the correct values by the previous
* call of this fn.
*/
err = bt_gatt_unsubscribe(get_conn(), &subscribe_params);
WAIT_FOR_FLAG_UNSET(flag_sc_subscribed);
}
if (err != 0) {
FAIL("Subscription failed(err %d)\n", err);
} else {
printk("%subscribed %s SC indications\n",
subscribe ? "S" : "Uns",
subscribe ? "to" : "from");
}
}
void gatt_discover(void)
{
static struct bt_gatt_discover_params discover_params;
int err;
printk("Discovering services and characteristics\n");
UNSET_FLAG(flag_discovered);
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_conn(), &discover_params);
if (err != 0) {
FAIL("Discover failed(err %d)\n", err);
}
WAIT_FOR_FLAG(flag_discovered);
printk("Discover complete\n");
}
DEFINE_FLAG(flag_written);
static void write_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params)
{
if (err != BT_ATT_ERR_SUCCESS) {
FAIL("Write failed: 0x%02X\n", err);
}
SET_FLAG(flag_written);
}
#define CF_BIT_ROBUST_CACHING 0
void activate_robust_caching(void)
{
int err;
static const uint8_t csf = BIT(CF_BIT_ROBUST_CACHING);
static struct bt_gatt_write_params write_params = {
.func = write_cb,
.offset = 0,
.data = &csf,
.length = sizeof(csf),
};
write_params.handle = gatt_handles[CLIENT_FEATURES];
UNSET_FLAG(flag_written);
err = bt_gatt_write(get_conn(), &write_params);
__ASSERT(!err, "Failed to enable robust caching\n");
WAIT_FOR_FLAG(flag_written);
printk("Robust caching enabled\n");
}
DEFINE_FLAG(flag_read);
static uint8_t _expect_success(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
/* printk("GATT read cb: err 0x%02X\n", err); */
__ASSERT(err == 0, "Failed to read: err 0x%x\n", err);
SET_FLAG(flag_read);
return 0;
}
static uint8_t _expect_out_of_sync_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
/* printk("GATT read cb: err 0x%02X\n", err); */
__ASSERT(err == BT_ATT_ERR_DB_OUT_OF_SYNC,
"Didn't get expected error code: err 0x%x\n", err);
SET_FLAG(flag_read);
return 0;
}
static void read_char(uint16_t handle, bool expect_success)
{
int err;
struct bt_gatt_read_params read_params = {
.handle_count = 1,
.single = {
.handle = handle,
.offset = 0,
},
};
if (expect_success) {
read_params.func = _expect_success;
} else {
read_params.func = _expect_out_of_sync_cb;
}
UNSET_FLAG(flag_read);
err = bt_gatt_read(get_conn(), &read_params);
__ASSERT(!err, "Failed to read char\n");
WAIT_FOR_FLAG(flag_read);
}
void read_test_char(bool expect_success)
{
read_char(gatt_handles[TEST_CHAR], expect_success);
}
void gatt_clear_flags(void)
{
UNSET_FLAG(flag_client_read);
UNSET_FLAG(flag_discovered);
UNSET_FLAG(flag_sc_indicated);
UNSET_FLAG(flag_sc_subscribed);
UNSET_FLAG(flag_written);
UNSET_FLAG(flag_read);
}

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
void gatt_register_service_1(void);
void gatt_register_service_2(void);
void gatt_discover(void);
void activate_robust_caching(void);
void read_test_char(bool expect_success);
void wait_for_client_read(void);
void gatt_subscribe_to_service_changed(bool subscribe);
void wait_for_sc_indication(void);
void gatt_clear_flags(void);

View file

@ -0,0 +1,211 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "utils.h"
#include "main.h"
#include "argparse.h"
#include "bs_pc_backchannel.h"
#include "bstests.h"
#include <zephyr/sys/__assert.h>
void server_procedure(void);
void client_procedure(void);
#define BS_SECONDS(dur_sec) ((bs_time_t)dur_sec * USEC_PER_SEC)
#define TEST_TIMEOUT_SIMULATED BS_SECONDS(30)
static int test_round;
static int final_round;
static char *settings_file;
int get_test_round(void)
{
return test_round;
}
bool is_final_round(void)
{
return test_round == final_round;
}
char *get_settings_file(void)
{
return settings_file;
}
static void test_args(int argc, char **argv)
{
__ASSERT(argc == 3, "Please specify only 3 test arguments\n");
test_round = atol(argv[0]);
final_round = atol(argv[1]);
settings_file = argv[2];
bs_trace_raw(0, "Test round %u\n", test_round);
bs_trace_raw(0, "Final round %u\n", final_round);
}
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");
}
}
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 = "server",
.test_pre_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = server_procedure,
.test_args_f = test_args,
},
{
.test_id = "client",
.test_pre_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = client_procedure,
.test_args_f = test_args,
},
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 };
void main(void)
{
bst_main();
}
void backchannel_init(void)
{
uint device_number = get_device_nbr();
uint channel_numbers[2] = { 0, 0, };
uint device_numbers[2];
uint num_ch;
uint *ch;
/* No backchannels to next/prev device if only device */
if (get_test_round() == 0 && is_final_round()) {
return;
}
/* Each `server` round/instance gets a connection to the previous and to
* the next instance in the chain. It waits until it is signalled by the
* previous instance, then runs its test procedure and finally signals
* the next instance in the chain.
*
* The two ends of the chain get only one channel, hence the difference
* in handling.
*/
if (get_test_round() == 0) {
/* send only */
device_numbers[0] = get_device_nbr() + 1;
num_ch = 1;
} else if (is_final_round()) {
/* receive only */
device_numbers[0] = get_device_nbr() - 1;
num_ch = 1;
} else {
/* send signal */
device_numbers[0] = get_device_nbr() + 1;
/* receive signal */
device_numbers[1] = get_device_nbr() - 1;
num_ch = 2;
}
printk("Opening backchannels\n");
ch = bs_open_back_channel(device_number, device_numbers,
channel_numbers, num_ch);
if (!ch) {
FAIL("Unable to open backchannel\n");
}
}
#define MSG_SIZE 1
void backchannel_sync_send(uint channel)
{
uint8_t sync_msg[MSG_SIZE] = { get_device_nbr() };
printk("Sending sync\n");
bs_bc_send_msg(channel, sync_msg, ARRAY_SIZE(sync_msg));
}
void backchannel_sync_wait(uint channel)
{
uint8_t sync_msg[MSG_SIZE];
while (true) {
if (bs_bc_is_msg_received(channel) > 0) {
bs_bc_receive_msg(channel, sync_msg,
ARRAY_SIZE(sync_msg));
if (sync_msg[0] != get_device_nbr()) {
/* Received a message from another device, exit */
break;
}
}
k_sleep(K_MSEC(1));
}
printk("Sync received\n");
}
/* We can't really kill the device/process without borking the bsim
* backchannels, so the next best thing is stopping all threads from processing,
* thus stopping the Bluetooth host from processing the disconnect event (or any
* event, really) coming from the link-layer.
*/
static void stop_all_threads(void)
{
/* promote to highest priority */
k_thread_priority_set(k_current_get(), K_HIGHEST_THREAD_PRIO);
/* busy-wait loop */
for (;;) {
k_busy_wait(1000);
k_yield();
}
}
void signal_next_test_round(void)
{
if (!is_final_round()) {
backchannel_sync_send(0);
}
PASS("round %d over\n", get_test_round());
stop_all_threads();
}
void wait_for_round_start(void)
{
backchannel_init();
if (is_final_round()) {
backchannel_sync_wait(0);
} else if (get_test_round() != 0) {
backchannel_sync_wait(1);
}
}

View file

@ -0,0 +1,14 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
char *get_settings_file(void);
int get_test_round(void);
bool is_final_round(void);
void signal_next_test_round(void);
void wait_for_round_start(void);

View file

@ -0,0 +1,208 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "utils.h"
#include "main.h"
#include "gatt_utils.h"
#include <zephyr/bluetooth/addr.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/settings/settings.h>
#include <zephyr/toolchain/gcc.h>
#include <stdint.h>
#include <string.h>
void set_public_addr(void)
{
bt_addr_le_t addr = {BT_ADDR_LE_RANDOM,
{{0x0A, 0x89, 0x67, 0x45, 0x23, 0xC1}}};
bt_id_create(&addr, NULL);
}
void server_round_0(void)
{
struct bt_conn *conn;
conn = connect_as_central();
wait_for_client_read();
printk("bonding\n");
bond(conn);
}
void server_round_1(void)
{
struct bt_conn *conn;
/* Wait for GATT DB hash to complete. */
k_sleep(K_SECONDS(2));
conn = connect_as_central();
printk("encrypting\n");
set_security(conn, BT_SECURITY_L2);
wait_for_client_read();
wait_disconnected();
printk("register second service, peer will be change-unaware\n");
gatt_register_service_2();
/* on-disk hash will be different when round 2 (and round 4)
* start, the peer will be marked as change-unaware
*/
k_sleep(K_MSEC(100));
}
void server_round_2(void)
{
struct bt_conn *conn;
conn = connect_as_central();
printk("encrypting\n");
set_security(conn, BT_SECURITY_L2);
wait_for_client_read();
/* Kill the power before the graceful disconnect, to make sure
* that the change-aware status has been written correctly to
* NVS. We still have to wait for the delayed work to be run.
*/
k_sleep(K_MSEC(CONFIG_BT_SETTINGS_DELAYED_STORE_MS));
}
void server_round_3(void)
{
struct bt_conn *conn;
conn = connect_as_central();
printk("encrypting\n");
set_security(conn, BT_SECURITY_L2);
wait_for_client_read();
wait_disconnected();
printk("register second service, peer will be change-unaware\n");
gatt_register_service_2();
/* on-disk hash will be different when round 2 (and round 4)
* start, the peer will be marked as change-unaware
*/
k_sleep(K_MSEC(100));
}
void server_round_4(void)
{
struct bt_conn *conn;
conn = connect_as_central();
printk("encrypting\n");
set_security(conn, BT_SECURITY_L2);
wait_for_client_read();
wait_disconnected();
}
void server_round_5(void)
{
gatt_register_service_2();
/* sleep long enough to ensure the DB hash is stored to disk, but short
* enough to make sure the delayed storage work item is not executed.
*/
k_sleep(K_MSEC(100));
}
void server_round_6(void)
{
struct bt_conn *conn;
gatt_register_service_2();
conn = connect_as_central();
printk("encrypting\n");
set_security(conn, BT_SECURITY_L2);
wait_for_client_read();
wait_disconnected();
}
/* What is being tested: since this deals with settings it's not the rounds
* themselves, but rather the transitions that test expected behavior.
*
* Round 0 -> 1: test CCC / CF values written before bonding are stored to NVS
* if the server reboots before disconnecting.
*
* Round 1 -> 2: test change-awareness is updated if GATT DB changes _after_ the
* peer has disconnected. In round 2 we also make sure we receive the Service
* Changed indication.
*
* Round 2 -> 3: tests `CONFIG_BT_SETTINGS_CF_STORE_ON_WRITE` does its job, and
* writes the change-awareness before we get disconnected. Basically, this
* transition simulates a user yanking the power of the device before it has the
* chance to disconnect.
*
* Round 3 -> 4: same as (1->2), except this time we won't get the SC indication
* (as we have unsubscribed from it). We should instead get the
* `BT_ATT_ERR_DB_OUT_OF_SYNC` error on the first attribute read. This also
* tests that robust GATT caching is enforced.
*
* Round 4 -> 5: tests change-awareness status is still written on disconnect.
* This is a non-regression test to make sure we didn't break the previous
* behavior.
*
* Round 5 -> 6: tests DFU corner case: in this case, we are on the first boot
* of an updated firmware, that will register new services. But for some unknown
* reason, we decide to reboot before the delayed store work item has had the
* time to execute and store that the peers are now change-unaware. Round 6 then
* makes sure that we are indeed change-unaware.
*/
void server_procedure(void)
{
uint8_t round = get_test_round();
wait_for_round_start();
printk("Start test round: %d\n", get_test_round());
/* Use the same public address for all instances of the central. If we
* don't do that, encryption (using the bond stored in NVS) will
* fail.
*/
set_public_addr();
gatt_register_service_1();
bt_enable(NULL);
settings_load();
switch (round) {
case 0:
server_round_0();
break;
case 1:
server_round_1();
break;
case 2:
server_round_2();
break;
case 3:
server_round_3();
break;
case 4:
server_round_4();
break;
case 5:
server_round_5();
break;
case 6:
server_round_6();
break;
default:
FAIL("Round %d doesn't exist\n", round);
break;
}
signal_next_test_round();
}

View file

@ -0,0 +1,233 @@
/*
* Copyright (c) 2023 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "main.h"
#include <stdio.h>
#include <stddef.h>
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include "zephyr/types.h"
#include "errno.h"
#include "argparse.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(settings_backend, 3);
#define SETTINGS_FILE setting_file
#define SETTINGS_FILE_TMP setting_file_tmp
#define ENTRY_LEN_SIZE (4)
#define ENTRY_NAME_MAX_LEN (SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN)
#define ENTRY_VAL_MAX_LEN (SETTINGS_MAX_VAL_LEN * 2)
#define READ_LEN_MAX (ENTRY_VAL_MAX_LEN + ENTRY_NAME_MAX_LEN + 2)
struct line_read_ctx {
int len;
const uint8_t *val;
};
static char setting_file[50];
static char setting_file_tmp[sizeof(setting_file) + 1];
static int entry_check_and_copy(FILE *fin, FILE *fout, const char *name)
{
char line[READ_LEN_MAX + 1];
char name_tmp[strlen(name) + 2];
snprintk(name_tmp, sizeof(name_tmp), "%s=", name);
while (fgets(line, sizeof(line), fin) == line) {
if (strstr(line, name_tmp) != NULL) {
return 0;
}
if (fputs(line, fout) < 0) {
return -1;
}
};
return 0;
}
static ssize_t settings_line_read_cb(void *cb_arg, void *data, size_t len)
{
struct line_read_ctx *valctx = (struct line_read_ctx *)cb_arg;
if ((valctx->len / 2) > len) {
return -ENOBUFS;
}
uint8_t *n = (uint8_t *) data;
len = valctx->len / 2;
for (int i = 0; i < len; i++, n++) {
if (sscanf(&valctx->val[i * 2], "%2hhx", n) != 1) {
return 0;
};
}
return len;
}
static int settings_custom_load(struct settings_store *cs, const struct settings_load_arg *arg)
{
FILE *fp = fopen(SETTINGS_FILE, "r+");
if (fp == NULL) {
LOG_INF("Settings file doesn't exist, will create it");
return -1;
}
if (fseek(fp, 0, SEEK_SET) < 0) {
return -1;
}
int vallen;
char line[READ_LEN_MAX + 1];
while (fgets(line, sizeof(line), fp) == line) {
/* check for matching subtree */
if (arg->subtree != NULL && !strstr(line, arg->subtree)) {
continue;
}
char *pos = strchr(line, '=');
if (pos <= line) {
return -1;
}
vallen = strlen(line) - (pos - line) - 2;
LOG_DBG("loading entry: %s", line);
struct line_read_ctx valctx;
valctx.len = vallen;
valctx.val = pos + 1;
int err = settings_call_set_handler(line, vallen / 2, settings_line_read_cb,
&valctx, arg);
if (err < 0) {
return err;
}
};
return fclose(fp);
}
/* Entries are saved to optimize readability of the settings file for test development and
* debugging purposes. Format:
* <entry-key>=<entry-value-hex-str>\n
*/
static int settings_custom_save(struct settings_store *cs, const char *name,
const char *value, size_t val_len)
{
FILE *fcur = fopen(SETTINGS_FILE, "r+");
FILE *fnew = NULL;
if (fcur == NULL) {
fcur = fopen(SETTINGS_FILE, "w");
} else {
fnew = fopen(SETTINGS_FILE_TMP, "w");
if (fnew == NULL) {
LOG_ERR("Failed to create temporary file %s", SETTINGS_FILE_TMP);
return -1;
}
}
if (fcur == NULL) {
LOG_ERR("Failed to create settings file: %s", SETTINGS_FILE);
return -1;
}
if (strlen(name) > ENTRY_NAME_MAX_LEN || val_len > SETTINGS_MAX_VAL_LEN) {
return -1;
}
if (fnew != NULL) {
if (entry_check_and_copy(fcur, fnew, name) < 0) {
return -1;
}
}
if (val_len) {
char bufvname[ENTRY_NAME_MAX_LEN + ENTRY_LEN_SIZE + 3];
snprintk(bufvname, sizeof(bufvname), "%s=", name);
if (fputs(bufvname, fnew != NULL ? fnew : fcur) < 0) {
return -1;
}
char bufval[ENTRY_VAL_MAX_LEN + 2] = {};
size_t valcnt = 0;
while (valcnt < (val_len * 2)) {
valcnt += snprintk(&bufval[valcnt], 3, "%02x",
(uint8_t)value[valcnt / 2]);
};
/* helps in making settings file readable */
bufval[valcnt++] = '\n';
bufval[valcnt] = 0;
LOG_DBG("writing to disk");
if (fputs(bufval, fnew != NULL ? fnew : fcur) < 0) {
return -1;
}
}
if (fnew != NULL) {
entry_check_and_copy(fcur, fnew, name);
}
fclose(fcur);
if (fnew != NULL) {
fclose(fnew);
remove(SETTINGS_FILE);
rename(SETTINGS_FILE_TMP, SETTINGS_FILE);
}
return 0;
}
/* custom backend interface */
static struct settings_store_itf settings_custom_itf = {
.csi_load = settings_custom_load,
.csi_save = settings_custom_save,
};
/* custom backend node */
static struct settings_store settings_custom_store = {
.cs_itf = &settings_custom_itf
};
int settings_backend_init(void)
{
snprintf(setting_file, sizeof(setting_file), "%s_%s.log", get_simid(), get_settings_file());
snprintf(setting_file_tmp, sizeof(setting_file_tmp), "~%s", setting_file);
LOG_INF("file path: %s", SETTINGS_FILE);
/* register custom backend */
settings_dst_register(&settings_custom_store);
settings_src_register(&settings_custom_store);
return 0;
}
void settings_test_backend_clear(void)
{
snprintf(setting_file, sizeof(setting_file), "%s_%s.log", get_simid(), get_settings_file());
if (remove(setting_file)) {
LOG_INF("error deleting file: %s", setting_file);
}
}

View file

@ -0,0 +1,223 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "utils.h"
#include "gatt_utils.h"
#include <zephyr/sys/__assert.h>
#include <zephyr/bluetooth/hci.h>
DEFINE_FLAG(flag_is_connected);
DEFINE_FLAG(flag_test_end);
void wait_connected(void)
{
UNSET_FLAG(flag_is_connected);
WAIT_FOR_FLAG(flag_is_connected);
printk("connected\n");
}
void wait_disconnected(void)
{
SET_FLAG(flag_is_connected);
WAIT_FOR_FLAG_UNSET(flag_is_connected);
printk("disconnected\n");
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
bt_conn_unref(conn);
UNSET_FLAG(flag_is_connected);
gatt_clear_flags();
}
static void connected(struct bt_conn *conn, uint8_t err)
{
if (err != 0) {
return;
}
bt_conn_ref(conn);
SET_FLAG(flag_is_connected);
}
DEFINE_FLAG(flag_encrypted);
void security_changed(struct bt_conn *conn, bt_security_t level,
enum bt_security_err err)
{
__ASSERT(err == 0, "Error setting security (err %u)\n", err);
printk("Encrypted\n");
SET_FLAG(flag_encrypted);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
.security_changed = security_changed,
};
static void scan_connect_to_first_result_device_found(const bt_addr_le_t *addr, int8_t rssi,
uint8_t type, struct net_buf_simple *ad)
{
struct bt_conn *conn;
char addr_str[BT_ADDR_LE_STR_LEN];
int err;
/* We're only interested in connectable events */
if (type != BT_HCI_ADV_IND && type != BT_HCI_ADV_DIRECT_IND) {
FAIL("Unexpected advertisement type.");
}
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();
__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,
&conn);
__ASSERT(!err, "Err bt_conn_le_create %d", err);
}
void scan_connect_to_first_result(void)
{
int err;
printk("start scanner\n");
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE,
scan_connect_to_first_result_device_found);
__ASSERT(!err, "Err bt_le_scan_start %d", err);
}
void advertise_connectable(void)
{
printk("start advertiser\n");
int err;
struct bt_le_adv_param param = {};
param.interval_min = 0x0020;
param.interval_max = 0x4000;
param.options |= BT_LE_ADV_OPT_ONE_TIME;
param.options |= BT_LE_ADV_OPT_CONNECTABLE;
err = bt_le_adv_start(&param, NULL, 0, NULL, 0);
__ASSERT(err == 0, "Advertising failed to start (err %d)\n", err);
}
void disconnect(struct bt_conn *conn)
{
int err;
err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
__ASSERT(!err, "Failed to initate disconnection (err %d)", err);
printk("Waiting for disconnection...\n");
WAIT_FOR_FLAG_UNSET(flag_is_connected);
}
static void get_active_conn_cb(struct bt_conn *src, void *dst)
{
*(struct bt_conn **)dst = src;
}
struct bt_conn *get_conn(void)
{
struct bt_conn *ret;
bt_conn_foreach(BT_CONN_TYPE_LE, get_active_conn_cb, &ret);
return ret;
}
DEFINE_FLAG(flag_pairing_complete);
static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
{
FAIL("Pairing failed (unexpected): reason %u", reason);
}
static void pairing_complete(struct bt_conn *conn, bool bonded)
{
__ASSERT(bonded, "Bonding failed\n");
printk("Paired\n");
SET_FLAG(flag_pairing_complete);
}
static struct bt_conn_auth_info_cb bt_conn_auth_info_cb = {
.pairing_failed = pairing_failed,
.pairing_complete = pairing_complete,
};
void set_security(struct bt_conn *conn, bt_security_t sec)
{
int err;
UNSET_FLAG(flag_encrypted);
err = bt_conn_set_security(conn, sec);
__ASSERT(!err, "Err bt_conn_set_security %d", err);
WAIT_FOR_FLAG(flag_encrypted);
}
void wait_secured(void)
{
UNSET_FLAG(flag_encrypted);
WAIT_FOR_FLAG(flag_encrypted);
}
void bond(struct bt_conn *conn)
{
UNSET_FLAG(flag_pairing_complete);
int err = bt_conn_auth_info_cb_register(&bt_conn_auth_info_cb);
__ASSERT(!err, "bt_conn_auth_info_cb_register failed.\n");
set_security(conn, BT_SECURITY_L2);
WAIT_FOR_FLAG(flag_pairing_complete);
}
void wait_bonded(void)
{
UNSET_FLAG(flag_encrypted);
UNSET_FLAG(flag_pairing_complete);
int err = bt_conn_auth_info_cb_register(&bt_conn_auth_info_cb);
__ASSERT(!err, "bt_conn_auth_info_cb_register failed.\n");
WAIT_FOR_FLAG(flag_encrypted);
WAIT_FOR_FLAG(flag_pairing_complete);
}
struct bt_conn *connect_as_central(void)
{
struct bt_conn *conn;
scan_connect_to_first_result();
wait_connected();
conn = get_conn();
return conn;
}
struct bt_conn *connect_as_peripheral(void)
{
struct bt_conn *conn;
advertise_connectable();
wait_connected();
conn = get_conn();
return conn;
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "bs_tracing.h"
#include "bs_types.h"
#include "bstests.h"
#include "time_machine.h"
#include <errno.h>
#include <zephyr/bluetooth/conn.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 ASSERT(expr, ...) \
do { \
if (!(expr)) { \
FAIL(__VA_ARGS__); \
} \
} while (0)
DECLARE_FLAG(flag_test_end);
#define FAIL(...) \
SET_FLAG(flag_test_end); \
do { \
bst_result = Failed; \
bs_trace_error_time_line(__VA_ARGS__); \
} while (0)
#define PASS(...) \
SET_FLAG(flag_test_end); \
do { \
bst_result = Passed; \
bs_trace_info_time(1, __VA_ARGS__); \
} while (0)
void disconnect(struct bt_conn *conn);
void wait_disconnected(void);
struct bt_conn *get_conn(void);
struct bt_conn *connect_as_central(void);
struct bt_conn *connect_as_peripheral(void);
void set_security(struct bt_conn *conn, bt_security_t sec);
void wait_secured(void);
void bond(struct bt_conn *conn);
void wait_bonded(void);

View file

@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Copyright 2023 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
set -eu
bash_source_dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
# Read variable definitions output by _env.sh
source "${bash_source_dir}/_env.sh"
# Place yourself in the test's root (i.e. ./../)
west build -b nrf52_bsim -d build_test && \
cp build_test/zephyr/zephyr.exe "${test_exe}"
west build -b nrf52_bsim -d build_test_2 -- -DCONF_FILE=prj_2.conf && \
cp build_test_2/zephyr/zephyr.exe "${test_2_exe}"

View file

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Copyright 2023 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
set -eu
bash_source_dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
: "${BSIM_OUT_PATH:?BSIM_OUT_PATH must be defined}"
test_name="$(basename "$(realpath "$bash_source_dir/..")")"
bsim_bin="${BSIM_OUT_PATH}/bin"
BOARD="${BOARD:-nrf52_bsim}"
test_exe="${bsim_bin}/bs_${BOARD}_tests_bsim_bluetooth_host_gatt_settings_prj_conf"
test_2_exe="${bsim_bin}/bs_${BOARD}_tests_bsim_bluetooth_host_gatt_settings_prj_2_conf"

View file

@ -0,0 +1,63 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
set -eu
bash_source_dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
source "${bash_source_dir}/_env.sh"
simulation_id="${test_name}"
verbosity_level=2
process_ids=""
exit_code=0
function Execute() {
if [ ! -f $1 ]; then
echo -e " \e[91m$(pwd)/$(basename $1) cannot be found (did you forget to\
compile it?)\e[39m"
exit 1
fi
$@ &
process_ids="$process_ids $!"
}
: "${BSIM_OUT_PATH:?BSIM_OUT_PATH must be defined}"
cd ${BSIM_OUT_PATH}/bin
# Remove the files used by the custom SETTINGS backend
TO_DELETE="${simulation_id}_server.log ${simulation_id}_client.log"
echo "remove settings files ${TO_DELETE}"
rm ${TO_DELETE} || true
Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s="${simulation_id}" -D=8 -sim_length=30e6 $@
# Only one `server` device is really running at a time. This is a necessary hack
# because bsim doesn't support plugging devices in and out of a running
# simulation, but this test needs a way to power-cycle the `server` device a few
# times.
#
# Each device will wait until the previous instance (called 'test round') has
# finished executing before starting up.
Execute "$test_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=0 -testid=server -RealEncryption=1 -argstest 0 6 "server"
Execute "$test_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=1 -testid=server -RealEncryption=1 -argstest 1 6 "server"
Execute "$test_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=2 -testid=server -RealEncryption=1 -argstest 2 6 "server"
Execute "$test_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=3 -testid=server -RealEncryption=1 -argstest 3 6 "server"
Execute "$test_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=4 -testid=server -RealEncryption=1 -argstest 4 6 "server"
Execute "$test_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=5 -testid=server -RealEncryption=1 -argstest 5 6 "server"
Execute "$test_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=6 -testid=server -RealEncryption=1 -argstest 6 6 "server"
Execute "$test_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=7 -testid=client -RealEncryption=1 -argstest 0 0 "client"
for process_id in $process_ids; do
wait $process_id || let "exit_code=$?"
done
exit $exit_code #the last exit code != 0

View file

@ -0,0 +1,63 @@
#!/usr/bin/env bash
# Copyright 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
set -eu
bash_source_dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
source "${bash_source_dir}/_env.sh"
simulation_id="${test_name}_2"
verbosity_level=2
process_ids=""
exit_code=0
function Execute() {
if [ ! -f $1 ]; then
echo -e " \e[91m$(pwd)/$(basename $1) cannot be found (did you forget to\
compile it?)\e[39m"
exit 1
fi
$@ &
process_ids="$process_ids $!"
}
: "${BSIM_OUT_PATH:?BSIM_OUT_PATH must be defined}"
cd ${BSIM_OUT_PATH}/bin
# Remove the files used by the custom SETTINGS backend
TO_DELETE="${simulation_id}_server_2.log ${simulation_id}_client_2.log"
echo "remove settings files ${TO_DELETE}"
rm ${TO_DELETE} || true
Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s="${simulation_id}" -D=8 -sim_length=30e6 $@
# Only one `server` device is really running at a time. This is a necessary hack
# because bsim doesn't support plugging devices in and out of a running
# simulation, but this test needs a way to power-cycle the `server` device a few
# times.
#
# Each device will wait until the previous instance (called 'test round') has
# finished executing before starting up.
Execute "$test_2_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=0 -testid=server -RealEncryption=1 -argstest 0 6 "server_2"
Execute "$test_2_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=1 -testid=server -RealEncryption=1 -argstest 1 6 "server_2"
Execute "$test_2_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=2 -testid=server -RealEncryption=1 -argstest 2 6 "server_2"
Execute "$test_2_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=3 -testid=server -RealEncryption=1 -argstest 3 6 "server_2"
Execute "$test_2_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=4 -testid=server -RealEncryption=1 -argstest 4 6 "server_2"
Execute "$test_2_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=5 -testid=server -RealEncryption=1 -argstest 5 6 "server_2"
Execute "$test_2_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=6 -testid=server -RealEncryption=1 -argstest 6 6 "server_2"
Execute "$test_2_exe" -v=${verbosity_level} \
-s="${simulation_id}" -d=7 -testid=client -RealEncryption=1 -argstest 0 0 "client_2"
for process_id in $process_ids; do
wait $process_id || let "exit_code=$?"
done
exit $exit_code #the last exit code != 0

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 REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(bsim_test_gatt_write)
target_sources(app PRIVATE
src/main.c
${ZEPHYR_BASE}/samples/bluetooth/central_gatt_write/src/gatt_write_common.c
${ZEPHYR_BASE}/samples/bluetooth/central_gatt_write/src/central_gatt_write.c
${ZEPHYR_BASE}/samples/bluetooth/peripheral_gatt_write/src/peripheral_gatt_write.c
)
zephyr_include_directories(
$ENV{BSIM_COMPONENTS_PATH}/libUtilv1/src/
$ENV{BSIM_COMPONENTS_PATH}/libPhyComv1/src/
)

View file

@ -0,0 +1,14 @@
CONFIG_BT=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_SMP=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_BT_BUF_ACL_RX_SIZE=255
CONFIG_BT_BUF_ACL_TX_SIZE=251
CONFIG_BT_BUF_CMD_TX_SIZE=255
CONFIG_BT_BUF_EVT_DISCARDABLE_SIZE=255
CONFIG_BT_L2CAP_TX_MTU=247
CONFIG_BT_CTLR_DATA_LENGTH_MAX=251

View file

@ -0,0 +1,131 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/util.h>
#include "bs_types.h"
#include "bs_tracing.h"
#include "time_machine.h"
#include "bstests.h"
#define COUNT 5000 /* Arbitrary GATT Write Cmd iterations used */
/* Write Throughput calculation:
* Measure interval = 1 s
* Connection interval = 50 ms
* No. of connection intervals = 20
* Single Tx time, 2M PHY = 1064 us
* tIFS = 150 us
* Single Tx duration = 1214 us
* Full duplex Tx-Rx duration = 2428 us
* Implementation dependent event overhead = 340 us
* Max. incomplete PDU time = 1064 us
* Max. radio idle time per 1 second = (1064 + 340) * 20 = 28080 us
* Packets per 1 second = (1000000 - 28080) / 2428 = 400.297
* GATT Write data length = 244 bytes
* Throughput = 400 * 244 * 8 = 780800 bps
*/
#define WRITE_RATE 780800 /* GATT Write bps recorded in this test */
extern uint32_t central_gatt_write(uint32_t count);
extern uint32_t peripheral_gatt_write(uint32_t count);
#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)
extern enum bst_result_t bst_result;
static void test_central_main(void)
{
uint32_t write_rate;
write_rate = central_gatt_write(COUNT);
printk("%s: Write Rate = %u bps\n", __func__, write_rate);
if (write_rate == WRITE_RATE) {
PASS("Central tests passed\n");
} else {
FAIL("Central tests failed\n");
}
/* Give extra time for peripheral side to finish its iterations */
k_sleep(K_SECONDS(1));
bs_trace_silent_exit(0);
}
static void test_peripheral_main(void)
{
uint32_t write_rate;
write_rate = peripheral_gatt_write(COUNT);
printk("%s: Write Rate = %u bps\n", __func__, write_rate);
if (write_rate == WRITE_RATE) {
PASS("Peripheral tests passed\n");
} else {
FAIL("Peripheral tests failed\n");
}
}
static void test_gatt_write_init(void)
{
bst_ticker_set_next_tick_absolute(60e6);
bst_result = In_progress;
}
static void test_gatt_write_tick(bs_time_t HW_device_time)
{
bst_result = Failed;
bs_trace_error_line("Test GATT Write finished.\n");
}
static const struct bst_test_instance test_def[] = {
{
.test_id = "central",
.test_descr = "Central GATT Write",
.test_post_init_f = test_gatt_write_init,
.test_tick_f = test_gatt_write_tick,
.test_main_f = test_central_main
},
{
.test_id = "peripheral",
.test_descr = "Peripheral GATT Write",
.test_post_init_f = test_gatt_write_init,
.test_tick_f = test_gatt_write_tick,
.test_main_f = test_peripheral_main
},
BSTEST_END_MARKER
};
struct bst_test_list *test_gatt_write_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_def);
}
bst_test_install_t test_installers[] = {
test_gatt_write_install,
NULL
};
void main(void)
{
bst_main();
}

View file

@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Copyright 2018 Oticon A/S
# SPDX-License-Identifier: Apache-2.0
# Multiple connection between two devices with multiple peripheral identity
simulation_id="gatt_write"
verbosity_level=2
process_ids=""; exit_code=0
function Execute(){
if [ ! -f $1 ]; then
echo -e " \e[91m`pwd`/`basename $1` cannot be found (did you forget to\
compile it?)\e[39m"
exit 1
fi
timeout 60 $@ & process_ids="$process_ids $!"
}
: "${BSIM_OUT_PATH:?BSIM_OUT_PATH must be defined}"
#Give a default value to BOARD if it does not have one yet:
BOARD="${BOARD:-nrf52_bsim}"
cd ${BSIM_OUT_PATH}/bin
Execute ./bs_${BOARD}_tests_bsim_bluetooth_host_gatt_write_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=0 -testid=central
Execute ./bs_${BOARD}_tests_bsim_bluetooth_host_gatt_write_prj_conf\
-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 $@
for process_id in $process_ids; do
wait $process_id || let "exit_code=$?"
done
exit $exit_code #the last exit code != 0