blob: d17e8e8b7934515ddddcba7484d6716fdcd7d771 [file] [log] [blame]
/* main.c - Application main entry point */
/*
* Copyright (c) 2019 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <stddef.h>
#include <zephyr/ztest.h>
#include <zephyr/bluetooth/buf.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gatt.h>
/* Custom Service Variables */
static const struct bt_uuid_128 test_uuid = BT_UUID_INIT_128(
0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12,
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12);
static const 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' };
static const struct bt_uuid_128 test1_uuid = BT_UUID_INIT_128(
0xf4, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12,
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12);
static const struct bt_uuid_128 test1_nfy_uuid = BT_UUID_INIT_128(
0xf5, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12,
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12);
static uint8_t nfy_enabled;
static void test1_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
nfy_enabled = (value == BT_GATT_CCC_NOTIFY) ? 1 : 0;
}
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;
return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
strlen(value));
}
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;
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_uuid),
BT_GATT_CHARACTERISTIC(&test_chrc_uuid.uuid,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ_AUTHEN |
BT_GATT_PERM_WRITE_AUTHEN,
read_test, write_test, test_value),
};
static struct bt_gatt_service test_svc = BT_GATT_SERVICE(test_attrs);
static struct bt_gatt_attr test1_attrs[] = {
/* Vendor Primary Service Declaration */
BT_GATT_PRIMARY_SERVICE(&test1_uuid),
BT_GATT_CHARACTERISTIC(&test1_nfy_uuid.uuid,
BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_NONE,
NULL, NULL, &nfy_enabled),
BT_GATT_CCC(test1_ccc_cfg_changed,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
};
static struct bt_gatt_service test1_svc = BT_GATT_SERVICE(test1_attrs);
ZTEST_SUITE(test_gatt, NULL, NULL, NULL, NULL, NULL);
ZTEST(test_gatt, test_gatt_register)
{
/* Ensure our test services are not already registered */
bt_gatt_service_unregister(&test_svc);
bt_gatt_service_unregister(&test1_svc);
/* Attempt to register services */
zassert_false(bt_gatt_service_register(&test_svc),
"Test service registration failed");
zassert_false(bt_gatt_service_register(&test1_svc),
"Test service1 registration failed");
/* Attempt to register already registered services */
zassert_true(bt_gatt_service_register(&test_svc),
"Test service duplicate succeeded");
zassert_true(bt_gatt_service_register(&test1_svc),
"Test service1 duplicate succeeded");
}
ZTEST(test_gatt, test_gatt_unregister)
{
/* Attempt to unregister last */
zassert_false(bt_gatt_service_unregister(&test1_svc),
"Test service1 unregister failed");
zassert_false(bt_gatt_service_register(&test1_svc),
"Test service1 re-registration failed");
/* Attempt to unregister first/middle */
zassert_false(bt_gatt_service_unregister(&test_svc),
"Test service unregister failed");
zassert_false(bt_gatt_service_register(&test_svc),
"Test service re-registration failed");
/* Attempt to unregister all reverse order */
zassert_false(bt_gatt_service_unregister(&test1_svc),
"Test service1 unregister failed");
zassert_false(bt_gatt_service_unregister(&test_svc),
"Test service unregister failed");
zassert_false(bt_gatt_service_register(&test_svc),
"Test service registration failed");
zassert_false(bt_gatt_service_register(&test1_svc),
"Test service1 registration failed");
/* Attempt to unregister all same order */
zassert_false(bt_gatt_service_unregister(&test_svc),
"Test service1 unregister failed");
zassert_false(bt_gatt_service_unregister(&test1_svc),
"Test service unregister failed");
}
/* Test that a service A can be re-registered after registering it once, unregistering it, and then
* registering another service B.
* No pre-allocated handles. Repeat the process multiple times.
*/
ZTEST(test_gatt, test_gatt_reregister)
{
struct bt_gatt_attr local_test_attrs[] = {
/* Vendor Primary Service Declaration */
BT_GATT_PRIMARY_SERVICE(&test_uuid),
BT_GATT_CHARACTERISTIC(&test_chrc_uuid.uuid, BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN,
read_test, write_test, test_value),
};
struct bt_gatt_attr local_test1_attrs[] = {
/* Vendor Primary Service Declaration */
BT_GATT_PRIMARY_SERVICE(&test1_uuid),
BT_GATT_CHARACTERISTIC(&test1_nfy_uuid.uuid, BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_NONE,
NULL, NULL, &nfy_enabled),
BT_GATT_CCC(test1_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
};
struct bt_gatt_service local_test_svc = BT_GATT_SERVICE(local_test_attrs);
struct bt_gatt_service local_test1_svc = BT_GATT_SERVICE(local_test1_attrs);
/* Check that the procedure is successful for a few iterations to verify stability and
* detect residual state or memory issues.
*/
for (int i = 0; i < 10; i++) {
/* Check that the handles are initially 0x0000 before registering the service */
for (int j = 0; j < local_test_svc.attr_count; j++) {
zassert_equal(local_test_svc.attrs[j].handle, 0x0000,
"Test service A handle not initially reset");
}
zassert_false(bt_gatt_service_register(&local_test_svc),
"Test service A registration failed");
zassert_false(bt_gatt_service_unregister(&local_test_svc),
"Test service A unregister failed");
/* Check that the handles are the same as before registering the service */
for (int j = 0; j < local_test_svc.attr_count; j++) {
zassert_equal(local_test_svc.attrs[j].handle, 0x0000,
"Test service A handle not reset");
}
zassert_false(bt_gatt_service_register(&local_test1_svc),
"Test service B registration failed");
zassert_false(bt_gatt_service_register(&local_test_svc),
"Test service A re-registering failed...");
/* Clean up */
zassert_false(bt_gatt_service_unregister(&local_test_svc),
"Test service A unregister failed");
zassert_false(bt_gatt_service_unregister(&local_test1_svc),
"Test service B unregister failed");
}
}
/* Test that a service A can be re-registered after registering it once, unregistering it, and then
* registering another service B.
* Service A and B both have pre-allocated handles for their attributes.
* Check that pre-allocated handles are the same after unregistering as they were before
* registering the service.
*/
ZTEST(test_gatt, test_gatt_reregister_pre_allocated_handles)
{
struct bt_gatt_attr local_test_attrs[] = {
/* Vendor Primary Service Declaration */
BT_GATT_PRIMARY_SERVICE(&test_uuid),
BT_GATT_CHARACTERISTIC(&test_chrc_uuid.uuid, BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN,
read_test, write_test, test_value),
};
struct bt_gatt_attr local_test1_attrs[] = {
/* Vendor Primary Service Declaration */
BT_GATT_PRIMARY_SERVICE(&test1_uuid),
BT_GATT_CHARACTERISTIC(&test1_nfy_uuid.uuid, BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_NONE,
NULL, NULL, &nfy_enabled),
BT_GATT_CCC(test1_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
};
struct bt_gatt_service prealloc_test_svc = BT_GATT_SERVICE(local_test_attrs);
struct bt_gatt_service prealloc_test1_svc = BT_GATT_SERVICE(local_test1_attrs);
/* Pre-allocate handles for both services */
for (int i = 0; i < prealloc_test_svc.attr_count; i++) {
prealloc_test_svc.attrs[i].handle = 0x0100 + i;
}
for (int i = 0; i < prealloc_test1_svc.attr_count; i++) {
prealloc_test1_svc.attrs[i].handle = 0x0200 + i;
}
zassert_false(bt_gatt_service_register(&prealloc_test_svc),
"Test service A registration failed");
zassert_false(bt_gatt_service_unregister(&prealloc_test_svc),
"Test service A unregister failed");
/* Check that the handles are the same as before registering the service */
for (int i = 0; i < prealloc_test_svc.attr_count; i++) {
zassert_equal(prealloc_test_svc.attrs[i].handle, 0x0100 + i,
"Test service A handle not reset");
}
zassert_false(bt_gatt_service_register(&prealloc_test1_svc),
"Test service B registration failed");
zassert_false(bt_gatt_service_register(&prealloc_test_svc),
"Test service A re-registering failed...");
/* Clean up */
zassert_false(bt_gatt_service_unregister(&prealloc_test_svc),
"Test service A unregister failed");
zassert_false(bt_gatt_service_unregister(&prealloc_test1_svc),
"Test service B unregister failed");
}
/* Test that a service A can be re-registered after registering it once, unregistering it, and then
* registering another service B.
* Service A has pre-allocated handles for its attributes, while Service B has handles assigned by
* the stack when registered.
* Check that pre-allocated handles are the same after unregistering as they were before
* registering the service.
*/
ZTEST(test_gatt, test_gatt_reregister_pre_allocated_handle_single)
{
struct bt_gatt_attr local_test_attrs[] = {
/* Vendor Primary Service Declaration */
BT_GATT_PRIMARY_SERVICE(&test_uuid),
BT_GATT_CHARACTERISTIC(&test_chrc_uuid.uuid, BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN,
read_test, write_test, test_value),
};
struct bt_gatt_attr local_test1_attrs[] = {
/* Vendor Primary Service Declaration */
BT_GATT_PRIMARY_SERVICE(&test1_uuid),
BT_GATT_CHARACTERISTIC(&test1_nfy_uuid.uuid, BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_NONE,
NULL, NULL, &nfy_enabled),
BT_GATT_CCC(test1_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
};
struct bt_gatt_service prealloc_test_svc = BT_GATT_SERVICE(local_test_attrs);
struct bt_gatt_service auto_test_svc = BT_GATT_SERVICE(local_test1_attrs);
/* Pre-allocate handles for one service only */
for (int j = 0; j < prealloc_test_svc.attr_count; j++) {
prealloc_test_svc.attrs[j].handle = 0x0100 + j;
}
zassert_false(bt_gatt_service_register(&prealloc_test_svc),
"Test service A registration failed");
zassert_false(bt_gatt_service_unregister(&prealloc_test_svc),
"Test service A unregister failed");
/* Check that the handles are the same as before registering the service */
for (int i = 0; i < prealloc_test_svc.attr_count; i++) {
zassert_equal(prealloc_test_svc.attrs[i].handle, 0x0100 + i,
"Test service A handle not reset");
}
zassert_false(bt_gatt_service_register(&auto_test_svc),
"Test service B registration failed");
zassert_false(bt_gatt_service_register(&prealloc_test_svc),
"Test service A re-registering failed...");
/* Clean up */
zassert_false(bt_gatt_service_unregister(&prealloc_test_svc),
"Test service A unregister failed");
zassert_false(bt_gatt_service_unregister(&auto_test_svc),
"Test service B unregister failed");
}
static uint8_t count_attr(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
uint16_t *count = user_data;
(*count)++;
return BT_GATT_ITER_CONTINUE;
}
static uint8_t find_attr(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
const struct bt_gatt_attr **tmp = user_data;
*tmp = attr;
return BT_GATT_ITER_CONTINUE;
}
ZTEST(test_gatt, test_gatt_foreach)
{
const struct bt_gatt_attr *attr;
uint16_t num = 0;
/* Attempt to register services */
zassert_false(bt_gatt_service_register(&test_svc),
"Test service registration failed");
zassert_false(bt_gatt_service_register(&test1_svc),
"Test service1 registration failed");
/* Iterate attributes */
bt_gatt_foreach_attr(test_attrs[0].handle, 0xffff, count_attr, &num);
zassert_equal(num, 7, "Number of attributes don't match");
/* Iterate 1 attribute */
num = 0;
bt_gatt_foreach_attr_type(test_attrs[0].handle, 0xffff, NULL, NULL, 1,
count_attr, &num);
zassert_equal(num, 1, "Number of attributes don't match");
/* Find attribute by UUID */
attr = NULL;
bt_gatt_foreach_attr_type(test_attrs[0].handle, 0xffff,
&test_chrc_uuid.uuid, NULL, 0, find_attr,
&attr);
zassert_not_null(attr, "Attribute don't match");
if (attr) {
zassert_equal(attr->uuid, &test_chrc_uuid.uuid,
"Attribute UUID don't match");
}
/* Find attribute by DATA */
attr = NULL;
bt_gatt_foreach_attr_type(test_attrs[0].handle, 0xffff, NULL,
test_value, 0, find_attr, &attr);
zassert_not_null(attr, "Attribute don't match");
if (attr) {
zassert_equal(attr->user_data, test_value,
"Attribute value don't match");
}
/* Find all characteristics */
num = 0;
bt_gatt_foreach_attr_type(test_attrs[0].handle, 0xffff,
BT_UUID_GATT_CHRC, NULL, 0, count_attr, &num);
zassert_equal(num, 2, "Number of attributes don't match");
/* Find 1 characteristic */
attr = NULL;
bt_gatt_foreach_attr_type(test_attrs[0].handle, 0xffff,
BT_UUID_GATT_CHRC, NULL, 1, find_attr, &attr);
zassert_not_null(attr, "Attribute don't match");
/* Find attribute by UUID and DATA */
attr = NULL;
bt_gatt_foreach_attr_type(test_attrs[0].handle, 0xffff,
&test1_nfy_uuid.uuid, &nfy_enabled, 1,
find_attr, &attr);
zassert_not_null(attr, "Attribute don't match");
if (attr) {
zassert_equal(attr->uuid, &test1_nfy_uuid.uuid,
"Attribute UUID don't match");
zassert_equal(attr->user_data, &nfy_enabled,
"Attribute value don't match");
}
}
ZTEST(test_gatt, test_gatt_read)
{
const struct bt_gatt_attr *attr;
uint8_t buf[256];
ssize_t ret;
/* Find attribute by UUID */
attr = NULL;
bt_gatt_foreach_attr_type(test_attrs[0].handle, 0xffff,
&test_chrc_uuid.uuid, NULL, 0, find_attr,
&attr);
zassert_not_null(attr, "Attribute don't match");
zassert_equal(attr->uuid, &test_chrc_uuid.uuid,
"Attribute UUID don't match");
ret = attr->read(NULL, attr, (void *)buf, sizeof(buf), 0);
zassert_equal(ret, strlen(test_value),
"Attribute read unexpected return");
zassert_mem_equal(buf, test_value, ret,
"Attribute read value don't match");
}
ZTEST(test_gatt, test_gatt_write)
{
const struct bt_gatt_attr *attr;
char *value = " ";
ssize_t ret;
/* Need our service to be registered */
zassert_false(bt_gatt_service_register(&test_svc),
"Test service registration failed");
/* Find attribute by UUID */
attr = NULL;
bt_gatt_foreach_attr_type(test_attrs[0].handle, 0xffff,
&test_chrc_uuid.uuid, NULL, 0, find_attr,
&attr);
zassert_not_null(attr, "Attribute don't match");
ret = attr->write(NULL, attr, (void *)value, strlen(value), 0, 0);
zassert_equal(ret, strlen(value), "Attribute write unexpected return");
zassert_mem_equal(value, test_value, ret,
"Attribute write value don't match");
}
ZTEST(test_gatt, test_bt_att_err_to_str)
{
/* Test a couple of entries */
zassert_str_equal(bt_att_err_to_str(BT_ATT_ERR_SUCCESS),
"BT_ATT_ERR_SUCCESS");
zassert_str_equal(bt_att_err_to_str(BT_ATT_ERR_INSUFFICIENT_ENCRYPTION),
"BT_ATT_ERR_INSUFFICIENT_ENCRYPTION");
zassert_str_equal(bt_att_err_to_str(BT_ATT_ERR_OUT_OF_RANGE),
"BT_ATT_ERR_OUT_OF_RANGE");
/* Test a entries that is not used */
zassert_mem_equal(bt_att_err_to_str(0x14),
"(unknown)", strlen("(unknown)"));
zassert_mem_equal(bt_att_err_to_str(0xFB),
"(unknown)", strlen("(unknown)"));
for (uint16_t i = 0; i <= UINT8_MAX; i++) {
zassert_not_null(bt_att_err_to_str(i), ": %d", i);
}
}
ZTEST(test_gatt, test_bt_gatt_err_to_str)
{
/* Test a couple of entries */
zassert_str_equal(bt_gatt_err_to_str(BT_GATT_ERR(BT_ATT_ERR_SUCCESS)),
"BT_ATT_ERR_SUCCESS");
zassert_str_equal(bt_gatt_err_to_str(BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_ENCRYPTION)),
"BT_ATT_ERR_INSUFFICIENT_ENCRYPTION");
zassert_str_equal(bt_gatt_err_to_str(BT_GATT_ERR(BT_ATT_ERR_OUT_OF_RANGE)),
"BT_ATT_ERR_OUT_OF_RANGE");
/* Test entries that are not used */
zassert_mem_equal(bt_gatt_err_to_str(BT_GATT_ERR(0x14)),
"(unknown)", strlen("(unknown)"));
zassert_mem_equal(bt_gatt_err_to_str(BT_GATT_ERR(0xFB)),
"(unknown)", strlen("(unknown)"));
/* Test positive values */
for (uint16_t i = 0; i <= UINT8_MAX; i++) {
zassert_not_null(bt_gatt_err_to_str(i), ": %d", i);
}
/* Test negative values */
for (uint16_t i = 0; i <= UINT8_MAX; i++) {
zassert_not_null(bt_gatt_err_to_str(-i), ": %d", i);
}
}