From 5b9328467c964378fc1057781456aa2d1d94de67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Koppel?= Date: Thu, 7 Aug 2025 17:55:52 +0200 Subject: [PATCH 1/6] Bluetooth: Host: Extract GAP service from GATT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove Generic Access Service implementation from gatt files and move it into separate file in services directory. The proposed name is just gap_default as it is default implementation that may be switched off by the user in purpose of providing the GAP implementation directly in the application. Signed-off-by: Radosław Koppel --- include/zephyr/bluetooth/gap.h | 9 + subsys/bluetooth/host/Kconfig.gatt | 56 ------- subsys/bluetooth/host/gatt.c | 158 ------------------ subsys/bluetooth/host/gatt_internal.h | 3 - subsys/bluetooth/services/CMakeLists.txt | 2 + subsys/bluetooth/services/Kconfig | 2 + subsys/bluetooth/services/Kconfig.gap_svc | 76 +++++++++ subsys/bluetooth/services/gap_svc_default.c | 175 ++++++++++++++++++++ 8 files changed, 264 insertions(+), 217 deletions(-) create mode 100644 subsys/bluetooth/services/Kconfig.gap_svc create mode 100644 subsys/bluetooth/services/gap_svc_default.c diff --git a/include/zephyr/bluetooth/gap.h b/include/zephyr/bluetooth/gap.h index b446ad521d4c9..5f1f3e72e65d2 100644 --- a/include/zephyr/bluetooth/gap.h +++ b/include/zephyr/bluetooth/gap.h @@ -26,6 +26,15 @@ extern "C" { * @{ */ +/** + * @brief Default GAP service name + * + * Use this name as a first argument for BT_GATT_SERVICE_DEFINE when creating + * GAP service implementation. + * This ensures that the GAP service would be placed just after BT_UUID_GATT. + */ +#define BT_GATT_GAP_SVC_DEFAULT_NAME _2_gap_svc + /** * @name Company Identifiers (see Bluetooth Assigned Numbers) * @{ diff --git a/subsys/bluetooth/host/Kconfig.gatt b/subsys/bluetooth/host/Kconfig.gatt index a04241a3e94f6..8c4debadfd2c1 100644 --- a/subsys/bluetooth/host/Kconfig.gatt +++ b/subsys/bluetooth/host/Kconfig.gatt @@ -259,62 +259,6 @@ config BT_PERIPHERAL_PREF_TIMEOUT Range 3200 to 65534 is invalid. 65535 represents no specific value. endif # BT_GAP_PERIPHERAL_PREF_PARAMS -config BT_DEVICE_NAME_GATT_WRITABLE - bool "Allow to write device name by remote GATT clients" - depends on BT_DEVICE_NAME_DYNAMIC - default y - help - Enabling this option allows remote GATT clients to write to device - name GAP characteristic. - -if BT_DEVICE_NAME_GATT_WRITABLE -choice BT_DEVICE_NAME_GATT_WRITABLE_SECURITY - prompt "Security requirements" - default DEVICE_NAME_GATT_WRITABLE_ENCRYPT - help - Select security requirements for writing device name by remote GATT - clients. - -config DEVICE_NAME_GATT_WRITABLE_NONE - bool "No requirements" - -config DEVICE_NAME_GATT_WRITABLE_ENCRYPT - bool "Encryption required" - -config DEVICE_NAME_GATT_WRITABLE_AUTHEN - bool "Encryption and authentication required" - -endchoice #BT_DEVICE_NAME_GATT_WRITABLE_SECURITY -endif #BT_DEVICE_NAME_GATT_WRITABLE - -config BT_DEVICE_APPEARANCE_GATT_WRITABLE - bool "Allow to write GAP Appearance by remote GATT clients" - depends on BT_DEVICE_APPEARANCE_DYNAMIC - default y - help - Enabling this option allows remote GATT clients to write to device - appearance GAP characteristic. - -if BT_DEVICE_APPEARANCE_GATT_WRITABLE -choice BT_DEVICE_APPEARANCE_GATT_WRITABLE - prompt "Security requirements" - default DEVICE_APPEARANCE_GATT_WRITABLE_AUTHEN - help - Select security requirements for writing device name by remote GATT - clients. - -config BT_DEVICE_APPEARANCE_GATT_WRITABLE_NONE - bool "No requirements" - -config BT_DEVICE_APPEARANCE_GATT_WRITABLE_ENCRYPT - bool "Encryption required" - -config DEVICE_APPEARANCE_GATT_WRITABLE_AUTHEN - bool "Encryption and authentication required" - -endchoice #BT_DEVICE_APPEARANCE_GATT_WRITABLE -endif #BT_DEVICE_APPEARANCE_GATT_WRITABLE - config BT_GATT_AUTHORIZATION_CUSTOM bool "Custom authorization of GATT operations" help diff --git a/subsys/bluetooth/host/gatt.c b/subsys/bluetooth/host/gatt.c index 4e8b7c78244c7..bbae64420adcb 100644 --- a/subsys/bluetooth/host/gatt.c +++ b/subsys/bluetooth/host/gatt.c @@ -104,99 +104,6 @@ enum gatt_global_flags { static ATOMIC_DEFINE(gatt_flags, GATT_NUM_FLAGS); -static ssize_t read_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, - void *buf, uint16_t len, uint16_t offset) -{ - const char *name = bt_get_name(); - - return bt_gatt_attr_read(conn, attr, buf, len, offset, name, - strlen(name)); -} - -#if defined(CONFIG_BT_DEVICE_NAME_GATT_WRITABLE) - -static ssize_t write_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, - uint16_t len, uint16_t offset, uint8_t flags) -{ - /* adding one to fit the terminating null character */ - char value[CONFIG_BT_DEVICE_NAME_MAX + 1] = {}; - - if (offset >= CONFIG_BT_DEVICE_NAME_MAX) { - return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); - } - - if (offset + len > CONFIG_BT_DEVICE_NAME_MAX) { - return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); - } - - memcpy(value, buf, len); - - value[len] = '\0'; - - bt_set_name(value); - - return len; -} - -#endif /* CONFIG_BT_DEVICE_NAME_GATT_WRITABLE */ - -static ssize_t read_appearance(struct bt_conn *conn, - const struct bt_gatt_attr *attr, void *buf, - uint16_t len, uint16_t offset) -{ - uint16_t appearance = sys_cpu_to_le16(bt_get_appearance()); - - return bt_gatt_attr_read(conn, attr, buf, len, offset, &appearance, - sizeof(appearance)); -} - -#if defined(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE) -static ssize_t write_appearance(struct bt_conn *conn, const struct bt_gatt_attr *attr, - const void *buf, uint16_t len, uint16_t offset, - uint8_t flags) -{ - uint16_t appearance_le = sys_cpu_to_le16(bt_get_appearance()); - char * const appearance_le_bytes = (char *)&appearance_le; - uint16_t appearance; - int err; - - if (offset >= sizeof(appearance_le)) { - return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); - } - - if ((offset + len) > sizeof(appearance_le)) { - return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); - } - - memcpy(&appearance_le_bytes[offset], buf, len); - appearance = sys_le16_to_cpu(appearance_le); - - err = bt_set_appearance(appearance); - - if (err) { - return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); - } - - return len; -} -#endif /* CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE */ - -#if defined(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE) - #define GAP_APPEARANCE_PROPS (BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE) -#if defined(CONFIG_DEVICE_APPEARANCE_GATT_WRITABLE_AUTHEN) - #define GAP_APPEARANCE_PERMS (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_AUTHEN) -#elif defined(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE_ENCRYPT) - #define GAP_APPEARANCE_PERMS (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) -#else - #define GAP_APPEARANCE_PERMS (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE) -#endif - #define GAP_APPEARANCE_WRITE_HANDLER write_appearance -#else - #define GAP_APPEARANCE_PROPS BT_GATT_CHRC_READ - #define GAP_APPEARANCE_PERMS BT_GATT_PERM_READ - #define GAP_APPEARANCE_WRITE_HANDLER NULL -#endif - #if defined (CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS) /* This checks if the range entered is valid */ BUILD_ASSERT(!(CONFIG_BT_PERIPHERAL_PREF_MIN_INT > 3200 && @@ -211,72 +118,7 @@ BUILD_ASSERT((CONFIG_BT_PERIPHERAL_PREF_MIN_INT == 0xffff) || BUILD_ASSERT((CONFIG_BT_PERIPHERAL_PREF_TIMEOUT * 4U) > ((1U + CONFIG_BT_PERIPHERAL_PREF_LATENCY) * CONFIG_BT_PERIPHERAL_PREF_MAX_INT)); - -static ssize_t read_ppcp(struct bt_conn *conn, const struct bt_gatt_attr *attr, - void *buf, uint16_t len, uint16_t offset) -{ - struct __packed { - uint16_t min_int; - uint16_t max_int; - uint16_t latency; - uint16_t timeout; - } ppcp; - - ppcp.min_int = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_MIN_INT); - ppcp.max_int = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_MAX_INT); - ppcp.latency = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_LATENCY); - ppcp.timeout = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_TIMEOUT); - - return bt_gatt_attr_read(conn, attr, buf, len, offset, &ppcp, - sizeof(ppcp)); -} #endif - -#if defined(CONFIG_BT_CENTRAL) && defined(CONFIG_BT_PRIVACY) -static ssize_t read_central_addr_res(struct bt_conn *conn, - const struct bt_gatt_attr *attr, void *buf, - uint16_t len, uint16_t offset) -{ - uint8_t central_addr_res = BT_GATT_CENTRAL_ADDR_RES_SUPP; - - return bt_gatt_attr_read(conn, attr, buf, len, offset, - ¢ral_addr_res, sizeof(central_addr_res)); -} -#endif /* CONFIG_BT_CENTRAL && CONFIG_BT_PRIVACY */ - -BT_GATT_SERVICE_DEFINE(_2_gap_svc, - BT_GATT_PRIMARY_SERVICE(BT_UUID_GAP), -#if defined(CONFIG_BT_DEVICE_NAME_GATT_WRITABLE) - /* Require pairing for writes to device name */ - BT_GATT_CHARACTERISTIC(BT_UUID_GAP_DEVICE_NAME, - BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, - BT_GATT_PERM_READ | -#if defined(CONFIG_DEVICE_NAME_GATT_WRITABLE_AUTHEN) - BT_GATT_PERM_WRITE_AUTHEN, -#elif defined(CONFIG_DEVICE_NAME_GATT_WRITABLE_ENCRYPT) - BT_GATT_PERM_WRITE_ENCRYPT, -#else - BT_GATT_PERM_WRITE, -#endif - read_name, write_name, bt_dev.name), -#else - BT_GATT_CHARACTERISTIC(BT_UUID_GAP_DEVICE_NAME, BT_GATT_CHRC_READ, - BT_GATT_PERM_READ, read_name, NULL, NULL), -#endif /* CONFIG_BT_DEVICE_NAME_GATT_WRITABLE */ - BT_GATT_CHARACTERISTIC(BT_UUID_GAP_APPEARANCE, GAP_APPEARANCE_PROPS, - GAP_APPEARANCE_PERMS, read_appearance, - GAP_APPEARANCE_WRITE_HANDLER, NULL), -#if defined(CONFIG_BT_CENTRAL) && defined(CONFIG_BT_PRIVACY) - BT_GATT_CHARACTERISTIC(BT_UUID_CENTRAL_ADDR_RES, - BT_GATT_CHRC_READ, BT_GATT_PERM_READ, - read_central_addr_res, NULL, NULL), -#endif /* CONFIG_BT_CENTRAL && CONFIG_BT_PRIVACY */ -#if defined(CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS) - BT_GATT_CHARACTERISTIC(BT_UUID_GAP_PPCP, BT_GATT_CHRC_READ, - BT_GATT_PERM_READ, read_ppcp, NULL, NULL), -#endif -); - struct sc_data { uint16_t start; uint16_t end; diff --git a/subsys/bluetooth/host/gatt_internal.h b/subsys/bluetooth/host/gatt_internal.h index dbe3736cb0fc8..67640b63f627e 100644 --- a/subsys/bluetooth/host/gatt_internal.h +++ b/subsys/bluetooth/host/gatt_internal.h @@ -14,9 +14,6 @@ #include #include -#define BT_GATT_CENTRAL_ADDR_RES_NOT_SUPP 0 -#define BT_GATT_CENTRAL_ADDR_RES_SUPP 1 - #define BT_GATT_PERM_READ_MASK (BT_GATT_PERM_READ | \ BT_GATT_PERM_READ_ENCRYPT | \ BT_GATT_PERM_READ_AUTHEN | \ diff --git a/subsys/bluetooth/services/CMakeLists.txt b/subsys/bluetooth/services/CMakeLists.txt index f17f683d7186c..75ad5fa3fad5d 100644 --- a/subsys/bluetooth/services/CMakeLists.txt +++ b/subsys/bluetooth/services/CMakeLists.txt @@ -4,6 +4,8 @@ zephyr_sources_ifdef(CONFIG_BT_ANS ans.c) zephyr_sources_ifdef(CONFIG_BT_DIS dis.c) +zephyr_sources_ifdef(CONFIG_BT_GAP_SVC_DEFAULT_IMPL gap_svc_default.c) + zephyr_sources_ifdef(CONFIG_BT_CTS cts.c) zephyr_sources_ifdef(CONFIG_BT_HRS hrs.c) diff --git a/subsys/bluetooth/services/Kconfig b/subsys/bluetooth/services/Kconfig index cad7dc5a72589..75547b8552847 100644 --- a/subsys/bluetooth/services/Kconfig +++ b/subsys/bluetooth/services/Kconfig @@ -10,6 +10,8 @@ rsource "Kconfig.ans" rsource "Kconfig.dis" +rsource "Kconfig.gap_svc" + rsource "Kconfig.cts" rsource "Kconfig.hrs" diff --git a/subsys/bluetooth/services/Kconfig.gap_svc b/subsys/bluetooth/services/Kconfig.gap_svc new file mode 100644 index 0000000000000..6451edf5e5d4d --- /dev/null +++ b/subsys/bluetooth/services/Kconfig.gap_svc @@ -0,0 +1,76 @@ +# GATT Generic Access Service - default implementation +# +# Copyright (c) 2019 Intel Corporation +# Copyright (c) 2025 Koppel Electronic +# +# SPDX-License-Identifier: Apache-2.0 + +menu "GATT Generic Access Service" + +config BT_GAP_SVC_DEFAULT_IMPL + bool "GATT Generic Access Service - default implementation" + default y + help + Enable the default GATT Generic Access Service. + Enabled by default provides basic GAP functionality. + If disabled, it is the application responsibility to provide its own + implementation of GAP service, otherwise the system is not Bluetooth + qualification compliant. + +config BT_DEVICE_NAME_GATT_WRITABLE + bool "Allow to write device name by remote GATT clients" + depends on BT_DEVICE_NAME_DYNAMIC + default y + help + Enabling this option allows remote GATT clients to write to device + name GAP characteristic. + +if BT_DEVICE_NAME_GATT_WRITABLE +choice BT_DEVICE_NAME_GATT_WRITABLE_SECURITY + prompt "Security requirements" + default DEVICE_NAME_GATT_WRITABLE_ENCRYPT + help + Select security requirements for writing device name by remote GATT + clients. + +config DEVICE_NAME_GATT_WRITABLE_NONE + bool "No requirements" + +config DEVICE_NAME_GATT_WRITABLE_ENCRYPT + bool "Encryption required" + +config DEVICE_NAME_GATT_WRITABLE_AUTHEN + bool "Encryption and authentication required" + +endchoice #BT_DEVICE_NAME_GATT_WRITABLE_SECURITY +endif #BT_DEVICE_NAME_GATT_WRITABLE + +config BT_DEVICE_APPEARANCE_GATT_WRITABLE + bool "Allow to write GAP Appearance by remote GATT clients" + depends on BT_DEVICE_APPEARANCE_DYNAMIC + default y + help + Enabling this option allows remote GATT clients to write to device + appearance GAP characteristic. + +if BT_DEVICE_APPEARANCE_GATT_WRITABLE +choice BT_DEVICE_APPEARANCE_GATT_WRITABLE + prompt "Security requirements" + default DEVICE_APPEARANCE_GATT_WRITABLE_AUTHEN + help + Select security requirements for writing device appearance by remote + GATT clients. + +config BT_DEVICE_APPEARANCE_GATT_WRITABLE_NONE + bool "No requirements" + +config BT_DEVICE_APPEARANCE_GATT_WRITABLE_ENCRYPT + bool "Encryption required" + +config DEVICE_APPEARANCE_GATT_WRITABLE_AUTHEN + bool "Encryption and authentication required" + +endchoice #BT_DEVICE_APPEARANCE_GATT_WRITABLE +endif #BT_DEVICE_APPEARANCE_GATT_WRITABLE + +endmenu diff --git a/subsys/bluetooth/services/gap_svc_default.c b/subsys/bluetooth/services/gap_svc_default.c new file mode 100644 index 0000000000000..ed9a366dbd671 --- /dev/null +++ b/subsys/bluetooth/services/gap_svc_default.c @@ -0,0 +1,175 @@ +/** @file + * @brief GATT Generic Access Service - default implementation + */ +/* + * Copyright (c) 2015-2016 Intel Corporation + * Copyright (c) 2023 Nordic Semiconductor ASA + * Copyright (c) 2025 Koppel Electronic + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define BT_GATT_CENTRAL_ADDR_RES_NOT_SUPP 0 +#define BT_GATT_CENTRAL_ADDR_RES_SUPP 1 + + +static ssize_t read_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + const char *name = bt_get_name(); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, name, + strlen(name)); +} + +#if defined(CONFIG_BT_DEVICE_NAME_GATT_WRITABLE) + +static ssize_t write_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + /* adding one to fit the terminating null character */ + char value[CONFIG_BT_DEVICE_NAME_MAX + 1] = {}; + + if (offset > 0) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (offset + len > CONFIG_BT_DEVICE_NAME_MAX) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + memcpy(value, buf, len); + + value[len] = '\0'; + + bt_set_name(value); + + return len; +} + +#endif /* CONFIG_BT_DEVICE_NAME_GATT_WRITABLE */ + +static ssize_t read_appearance(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint16_t appearance = sys_cpu_to_le16(bt_get_appearance()); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &appearance, + sizeof(appearance)); +} + +#if defined(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE) +static ssize_t write_appearance(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, uint16_t offset, + uint8_t flags) +{ + uint16_t appearance; + int err; + + if (offset > 0) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != sizeof(appearance)) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + appearance = sys_get_le16(buf); + + err = bt_set_appearance(appearance); + + if (err) { + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + return len; +} +#endif /* CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE */ + +#if defined(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE) + #define GAP_APPEARANCE_PROPS (BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE) +#if defined(CONFIG_DEVICE_APPEARANCE_GATT_WRITABLE_AUTHEN) + #define GAP_APPEARANCE_PERMS (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_AUTHEN) +#elif defined(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE_ENCRYPT) + #define GAP_APPEARANCE_PERMS (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) +#else + #define GAP_APPEARANCE_PERMS (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE) +#endif + #define GAP_APPEARANCE_WRITE_HANDLER write_appearance +#else + #define GAP_APPEARANCE_PROPS BT_GATT_CHRC_READ + #define GAP_APPEARANCE_PERMS BT_GATT_PERM_READ + #define GAP_APPEARANCE_WRITE_HANDLER NULL +#endif + +#if defined(CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS) +static ssize_t read_ppcp(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + struct __packed { + uint16_t min_int; + uint16_t max_int; + uint16_t latency; + uint16_t timeout; + } ppcp; + + ppcp.min_int = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_MIN_INT); + ppcp.max_int = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_MAX_INT); + ppcp.latency = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_LATENCY); + ppcp.timeout = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_TIMEOUT); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &ppcp, + sizeof(ppcp)); +} +#endif + +#if defined(CONFIG_BT_CENTRAL) && defined(CONFIG_BT_PRIVACY) +static ssize_t read_central_addr_res(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint8_t central_addr_res = BT_GATT_CENTRAL_ADDR_RES_SUPP; + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + ¢ral_addr_res, sizeof(central_addr_res)); +} +#endif /* CONFIG_BT_CENTRAL && CONFIG_BT_PRIVACY */ + +BT_GATT_SERVICE_DEFINE(BT_GATT_GAP_SVC_DEFAULT_NAME, + BT_GATT_PRIMARY_SERVICE(BT_UUID_GAP), +#if defined(CONFIG_BT_DEVICE_NAME_GATT_WRITABLE) + /* Require pairing for writes to device name */ + BT_GATT_CHARACTERISTIC(BT_UUID_GAP_DEVICE_NAME, + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, + BT_GATT_PERM_READ | +#if defined(CONFIG_DEVICE_NAME_GATT_WRITABLE_AUTHEN) + BT_GATT_PERM_WRITE_AUTHEN, +#elif defined(CONFIG_DEVICE_NAME_GATT_WRITABLE_ENCRYPT) + BT_GATT_PERM_WRITE_ENCRYPT, +#else + BT_GATT_PERM_WRITE, +#endif + read_name, write_name, bt_dev.name), +#else + BT_GATT_CHARACTERISTIC(BT_UUID_GAP_DEVICE_NAME, BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, read_name, NULL, NULL), +#endif /* CONFIG_BT_DEVICE_NAME_GATT_WRITABLE */ + BT_GATT_CHARACTERISTIC(BT_UUID_GAP_APPEARANCE, GAP_APPEARANCE_PROPS, + GAP_APPEARANCE_PERMS, read_appearance, + GAP_APPEARANCE_WRITE_HANDLER, NULL), +#if defined(CONFIG_BT_CENTRAL) && defined(CONFIG_BT_PRIVACY) + BT_GATT_CHARACTERISTIC(BT_UUID_CENTRAL_ADDR_RES, + BT_GATT_CHRC_READ, BT_GATT_PERM_READ, + read_central_addr_res, NULL, NULL), +#endif /* CONFIG_BT_CENTRAL && CONFIG_BT_PRIVACY */ +#if defined(CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS) + BT_GATT_CHARACTERISTIC(BT_UUID_GAP_PPCP, BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, read_ppcp, NULL, NULL), +#endif +); From d1a8d89ae2706fc1646270ee4c33eb3b6dea9b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Koppel?= Date: Wed, 1 Oct 2025 15:56:03 +0200 Subject: [PATCH 2/6] Bluetooth: gap_svc_default: Set user_data for name characteristic to NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set the unused argument of the name characteristic to NULL. This removes the reference from GAP service to the hci_core. Signed-off-by: Radosław Koppel --- subsys/bluetooth/services/gap_svc_default.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subsys/bluetooth/services/gap_svc_default.c b/subsys/bluetooth/services/gap_svc_default.c index ed9a366dbd671..8bef494d647bc 100644 --- a/subsys/bluetooth/services/gap_svc_default.c +++ b/subsys/bluetooth/services/gap_svc_default.c @@ -155,7 +155,7 @@ BT_GATT_SERVICE_DEFINE(BT_GATT_GAP_SVC_DEFAULT_NAME, #else BT_GATT_PERM_WRITE, #endif - read_name, write_name, bt_dev.name), + read_name, write_name, NULL), #else BT_GATT_CHARACTERISTIC(BT_UUID_GAP_DEVICE_NAME, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, read_name, NULL, NULL), From 5e32be057d61398ea78856aea1a7117a96d20dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Koppel?= Date: Thu, 2 Oct 2025 15:09:25 +0200 Subject: [PATCH 3/6] Bluetooth: Host: Conn: Add GAP service validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the validation if there is exactly one GAP service in GATT database. The validation is performed as soon as the GATT database is available and forbids to enable the Bluetooth if the configuration is not compatible with the Bluetooth Specification. The verification may be disabled by the user for better performance or deliberate implementation that is not compliant with the specification. Signed-off-by: Radosław Koppel --- subsys/bluetooth/host/CMakeLists.txt | 5 ++ subsys/bluetooth/host/Kconfig.gatt | 1 + .../host/Kconfig.gatt_gap_svc_validate | 21 +++++++ subsys/bluetooth/host/conn.c | 9 +++ subsys/bluetooth/host/gatt_gap_svc_validate.c | 55 +++++++++++++++++++ subsys/bluetooth/host/gatt_gap_svc_validate.h | 27 +++++++++ 6 files changed, 118 insertions(+) create mode 100644 subsys/bluetooth/host/Kconfig.gatt_gap_svc_validate create mode 100644 subsys/bluetooth/host/gatt_gap_svc_validate.c create mode 100644 subsys/bluetooth/host/gatt_gap_svc_validate.h diff --git a/subsys/bluetooth/host/CMakeLists.txt b/subsys/bluetooth/host/CMakeLists.txt index 0911c22c8f866..39ce620a53490 100644 --- a/subsys/bluetooth/host/CMakeLists.txt +++ b/subsys/bluetooth/host/CMakeLists.txt @@ -49,6 +49,11 @@ if(CONFIG_BT_HCI_HOST) gatt.c ) + zephyr_library_sources_ifdef( + CONFIG_GATT_GAP_SVC_VALIDATE + gatt_gap_svc_validate.c + ) + if(CONFIG_BT_SMP) zephyr_library_sources( smp.c diff --git a/subsys/bluetooth/host/Kconfig.gatt b/subsys/bluetooth/host/Kconfig.gatt index 8c4debadfd2c1..2d55220d17278 100644 --- a/subsys/bluetooth/host/Kconfig.gatt +++ b/subsys/bluetooth/host/Kconfig.gatt @@ -267,4 +267,5 @@ config BT_GATT_AUTHORIZATION_CUSTOM with the bt_gatt_authorization_cb_register API. See the API documentation for more details. +rsource 'Kconfig.gatt_gap_svc_validate' endmenu diff --git a/subsys/bluetooth/host/Kconfig.gatt_gap_svc_validate b/subsys/bluetooth/host/Kconfig.gatt_gap_svc_validate new file mode 100644 index 0000000000000..61bfd96477258 --- /dev/null +++ b/subsys/bluetooth/host/Kconfig.gatt_gap_svc_validate @@ -0,0 +1,21 @@ +# Bluetooth GATT GAP service validation + +# Copyright (c) 2025 Koppel Electronic +# SPDX-License-Identifier: Apache-2.0 + +menuconfig GATT_GAP_SVC_VALIDATE + bool "GATT GAP Service validation" + depends on BT_CONN + default y + help + Validate if in the GATT database exactly one GAP service is present. + This helps to debug any issues related to GAP service that may make + the Bluetooth implementation not compatible with the standard. + +if GATT_GAP_SVC_VALIDATE + +module = GATT_GAP_SVC_VALIDATE +module-str = gatt-gap-validate +source "subsys/logging/Kconfig.template.log_config" + +endif # GATT_GAP_SVC_VALIDATE diff --git a/subsys/bluetooth/host/conn.c b/subsys/bluetooth/host/conn.c index 3bb442e3f1859..0ba63dce9d20e 100644 --- a/subsys/bluetooth/host/conn.c +++ b/subsys/bluetooth/host/conn.c @@ -53,6 +53,7 @@ #include "common/bt_str.h" #include "conn_internal.h" #include "direction_internal.h" +#include "gatt_gap_svc_validate.h" #include "hci_core.h" #include "id.h" #include "iso_internal.h" @@ -4428,6 +4429,14 @@ int bt_conn_init(void) bt_att_init(); + if (IS_ENABLED(CONFIG_GATT_GAP_SVC_VALIDATE)) { + err = gatt_gap_svc_validate(); + if (err) { + LOG_ERR("GATT GAP service validation failed (err %d)", err); + return err; + } + } + err = bt_smp_init(); if (err) { return err; diff --git a/subsys/bluetooth/host/gatt_gap_svc_validate.c b/subsys/bluetooth/host/gatt_gap_svc_validate.c new file mode 100644 index 0000000000000..518cdd607f448 --- /dev/null +++ b/subsys/bluetooth/host/gatt_gap_svc_validate.c @@ -0,0 +1,55 @@ +/* gatt_gap_svc_validate.c - GAP service inside GATT validation functions */ + +/* + * Copyright (c) 2025 Koppel Electronic + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + + +#define MODULE gatt_gap_validate + +LOG_MODULE_REGISTER(MODULE, CONFIG_GATT_GAP_SVC_VALIDATE_LOG_LEVEL); + + +static uint8_t gap_service_check_cb(const struct bt_gatt_attr *attr, + uint16_t handle, + void *user_data) +{ + size_t *cnt = user_data; + const struct bt_uuid *svc_uuid = attr->user_data; + + if (svc_uuid && bt_uuid_cmp(svc_uuid, BT_UUID_GAP) == 0) { + (*cnt)++; + LOG_DBG("GAP service found at handle: %u", handle); + if (*cnt > 1) { + LOG_ERR("Multiple (%zu) GAP services found at handle: %u", + *cnt, handle); + } + } + + return BT_GATT_ITER_CONTINUE; +} + +int gatt_gap_svc_validate(void) +{ + size_t gap_svc_count = 0; + + bt_gatt_foreach_attr_type( + BT_ATT_FIRST_ATTRIBUTE_HANDLE, + BT_ATT_LAST_ATTRIBUTE_HANDLE, + BT_UUID_GATT_PRIMARY, + NULL, 0, + gap_service_check_cb, + &gap_svc_count); + + if (gap_svc_count != 1) { + LOG_ERR("GAP service count invalid: %zu", gap_svc_count); + return -EINVAL; + } + return 0; +} diff --git a/subsys/bluetooth/host/gatt_gap_svc_validate.h b/subsys/bluetooth/host/gatt_gap_svc_validate.h new file mode 100644 index 0000000000000..c6a07ce617199 --- /dev/null +++ b/subsys/bluetooth/host/gatt_gap_svc_validate.h @@ -0,0 +1,27 @@ +/** @file + * @brief Internal API GATT GAP service validation. + */ + +/* + * Copyright (c) 2025 Koppel Electronic + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __BT_GATT_GAP_SVC_VALIDATE_H +#define __BT_GATT_GAP_SVC_VALIDATE_H + + +/** @brief Validate GATT database contains exactly one GAP service. + * + * This function iterates through the GATT attribute database and verifies + * that exactly one Generic Access Profile (GAP) service is present. The + * Bluetooth specification requires that a GATT server expose exactly one + * GAP service. + * + * @return 0 on success (exactly one GAP service found). + * @return -EINVAL if zero or multiple GAP services are found. + */ +int gatt_gap_svc_validate(void); + +#endif /* __BT_GATT_GAP_SVC_VALIDATE_H */ From 9e6c0c5bfb9c0b130871f4db11990184e97ffc44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Koppel?= Date: Fri, 3 Oct 2025 07:56:02 +0200 Subject: [PATCH 4/6] tests: bluetooth: host: conn: Disable GATT GAP svc validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Disable the GATT GAP service validation for the test. Signed-off-by: Radosław Koppel --- tests/bluetooth/host/conn/prj.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/bluetooth/host/conn/prj.conf b/tests/bluetooth/host/conn/prj.conf index eb8d5a0fdbf79..c199e15baf832 100644 --- a/tests/bluetooth/host/conn/prj.conf +++ b/tests/bluetooth/host/conn/prj.conf @@ -9,6 +9,7 @@ CONFIG_BT_CENTRAL=y CONFIG_BT_EXT_ADV=y CONFIG_BT_PER_ADV=y CONFIG_BT_PER_ADV_SYNC=y +CONFIG_GATT_GAP_SVC_VALIDATE=n CONFIG_BT_MAX_CONN=1 CONFIG_BT_L2CAP_TX_MTU=23 From 7852f3c5f84d6c31dfa382872a55e4223b9d3fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Koppel?= Date: Sat, 29 Nov 2025 00:32:07 +0100 Subject: [PATCH 5/6] Tests: Bluetooth: Add 'gap_svc' test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The goal of this test is to ensure the default GAP Service can be disabled and the application level GAP service implementation can be provided. Signed-off-by: Radosław Koppel --- .../host/gatt/gap_svc/CMakeLists.txt | 24 ++ .../bsim/bluetooth/host/gatt/gap_svc/prj.conf | 42 ++ .../bluetooth/host/gatt/gap_svc/src/central.c | 359 ++++++++++++++++++ .../bluetooth/host/gatt/gap_svc/src/main.c | 22 ++ .../host/gatt/gap_svc/src/peripheral.c | 256 +++++++++++++ .../gatt/gap_svc/test_scripts/run_gap_svc.sh | 28 ++ .../bluetooth/host/gatt/gap_svc/testcase.yaml | 10 + 7 files changed, 741 insertions(+) create mode 100644 tests/bsim/bluetooth/host/gatt/gap_svc/CMakeLists.txt create mode 100644 tests/bsim/bluetooth/host/gatt/gap_svc/prj.conf create mode 100644 tests/bsim/bluetooth/host/gatt/gap_svc/src/central.c create mode 100644 tests/bsim/bluetooth/host/gatt/gap_svc/src/main.c create mode 100644 tests/bsim/bluetooth/host/gatt/gap_svc/src/peripheral.c create mode 100755 tests/bsim/bluetooth/host/gatt/gap_svc/test_scripts/run_gap_svc.sh create mode 100644 tests/bsim/bluetooth/host/gatt/gap_svc/testcase.yaml diff --git a/tests/bsim/bluetooth/host/gatt/gap_svc/CMakeLists.txt b/tests/bsim/bluetooth/host/gatt/gap_svc/CMakeLists.txt new file mode 100644 index 0000000000000..9f183ca632c95 --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/gap_svc/CMakeLists.txt @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(gap_svc) + +add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/common/testlib testlib) +target_link_libraries(app PRIVATE testlib) + +add_subdirectory(${ZEPHYR_BASE}/tests/bsim/babblekit babblekit) +target_link_libraries(app PRIVATE babblekit) + +zephyr_include_directories( + ${BSIM_COMPONENTS_PATH}/libUtilv1/src/ + ${BSIM_COMPONENTS_PATH}/libPhyComv1/src/ +) + +target_sources(app PRIVATE + src/main.c + src/central.c + src/peripheral.c +) diff --git a/tests/bsim/bluetooth/host/gatt/gap_svc/prj.conf b/tests/bsim/bluetooth/host/gatt/gap_svc/prj.conf new file mode 100644 index 0000000000000..6bbd0817d1a2a --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/gap_svc/prj.conf @@ -0,0 +1,42 @@ +CONFIG_BT_TESTING=y + +CONFIG_BT=y +CONFIG_BT_DEVICE_NAME="GapSvc" +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_HRS=y +# Dependency of testlib/adv and testlib/scan. +CONFIG_BT_EXT_ADV=y + +CONFIG_BT_AUTO_PHY_UPDATE=n +CONFIG_BT_GATT_AUTO_UPDATE_MTU=n +CONFIG_BT_AUTO_DATA_LEN_UPDATE=n +CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n + +CONFIG_BT_GATT_AUTO_DISCOVER_CCC=y +CONFIG_BT_GATT_AUTO_RESUBSCRIBE=n + +CONFIG_BT_SMP=y +CONFIG_BT_GATT_CLIENT=y + + +# Testing locally redefined SVC implementation +CONFIG_BT_GAP_SVC_DEFAULT_IMPL=n + +CONFIG_BT_DEVICE_NAME_DYNAMIC=y +CONFIG_BT_DEVICE_NAME_GATT_WRITABLE=y + +CONFIG_BT_DEVICE_APPEARANCE_DYNAMIC=y +CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE=y +# Do not care about paramsters below - simpler GAP implementation +CONFIG_BT_PRIVACY=n +CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS=n + +# Other libraries +CONFIG_LOG=y +CONFIG_ASSERT=y + +CONFIG_THREAD_NAME=y +CONFIG_LOG_THREAD_ID_PREFIX=y + +CONFIG_ARCH_POSIX_TRAP_ON_FATAL=y diff --git a/tests/bsim/bluetooth/host/gatt/gap_svc/src/central.c b/tests/bsim/bluetooth/host/gatt/gap_svc/src/central.c new file mode 100644 index 0000000000000..91187ff9af996 --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/gap_svc/src/central.c @@ -0,0 +1,359 @@ +/** @file + * @brief Test local GATT Generic Access Service - central role + * + * @note Most of the original code from "../device_name/client.c" used here. + */ +/* + * Copyright (c) 2025 Koppel Electronic + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "testlib/att.h" +#include "testlib/att_read.h" +#include "testlib/att_write.h" +#include "testlib/conn.h" + +#include "babblekit/testcase.h" + +LOG_MODULE_REGISTER(central, LOG_LEVEL_DBG); + +/* Wait time in microseconds for the test to be finished */ +#define WAIT_TIME 10e6 + +static struct bt_conn *default_conn; +static struct bt_conn *connected_conn; +struct k_sem connected_sem; + +static void start_scan(void); + + +static bool eir_found(struct bt_data *data, void *user_data) +{ + bt_addr_le_t *addr = user_data; + + printk("[AD]: %u data_len %u\n", data->type, data->data_len); + + switch (data->type) { + case BT_DATA_UUID16_SOME: + case BT_DATA_UUID16_ALL: + if (data->data_len % sizeof(uint16_t) != 0U) { + printk("AD malformed\n"); + return true; + } + + for (int i = 0; i < data->data_len; i += sizeof(uint16_t)) { + struct bt_conn_le_create_param *create_param; + struct bt_le_conn_param *param; + const struct bt_uuid *uuid; + uint16_t u16; + int err; + + memcpy(&u16, &data->data[i], sizeof(u16)); + uuid = BT_UUID_DECLARE_16(sys_le16_to_cpu(u16)); + if (bt_uuid_cmp(uuid, BT_UUID_HRS)) { + continue; + } + + err = bt_le_scan_stop(); + if (err) { + printk("Stop LE scan failed (err %d)\n", err); + continue; + } + + printk("Creating connection with Coded PHY support\n"); + param = BT_LE_CONN_PARAM_DEFAULT; + create_param = BT_CONN_LE_CREATE_CONN; + create_param->options |= BT_CONN_LE_OPT_CODED; + err = bt_conn_le_create(addr, create_param, param, + &default_conn); + if (err) { + printk("Create connection with Coded PHY support failed (err %d)\n", + err); + + printk("Creating non-Coded PHY connection\n"); + create_param->options &= ~BT_CONN_LE_OPT_CODED; + err = bt_conn_le_create(addr, create_param, + param, &default_conn); + if (err) { + printk("Create connection failed (err %d)\n", err); + start_scan(); + } + } + + return false; + } + default: + break; + } + + return true; +} + +static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, + struct net_buf_simple *ad) +{ + char dev[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(addr, dev, sizeof(dev)); + printk("[DEVICE]: %s, AD evt type %u, AD data len %u, RSSI %i\n", + dev, type, ad->len, rssi); + + /* We're only interested in legacy connectable events or + * possible extended advertising that are connectable. + */ + if (type == BT_GAP_ADV_TYPE_ADV_IND || + type == BT_GAP_ADV_TYPE_ADV_DIRECT_IND || + type == BT_GAP_ADV_TYPE_EXT_ADV) { + bt_data_parse(ad, eir_found, (void *)addr); + } +} + +static void start_scan(void) +{ + int err; + + /* Use active scanning and disable duplicate filtering to handle any + * devices that might update their advertising data at runtime. + */ + struct bt_le_scan_param scan_param = { + .type = BT_LE_SCAN_TYPE_ACTIVE, + .options = BT_LE_SCAN_OPT_CODED, + .interval = BT_GAP_SCAN_FAST_INTERVAL, + .window = BT_GAP_SCAN_FAST_WINDOW, + }; + + err = bt_le_scan_start(&scan_param, device_found); + if (err) { + printk("Scanning with Coded PHY support failed (err %d)\n", err); + + printk("Scanning without Coded PHY\n"); + scan_param.options &= ~BT_LE_SCAN_OPT_CODED; + err = bt_le_scan_start(&scan_param, device_found); + if (err) { + printk("Scanning failed to start (err %d)\n", err); + return; + } + } + + printk("Scanning successfully started\n"); +} + + +static void connected(struct bt_conn *conn, uint8_t conn_err) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + if (conn_err) { + printk("Failed to connect to %s (%u)\n", addr, conn_err); + + bt_conn_unref(default_conn); + default_conn = NULL; + + start_scan(); + return; + } + + printk("Connected: %s\n", addr); + + if (conn == default_conn) { + connected_conn = bt_conn_ref(conn); + k_sem_give(&connected_sem); + } +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + printk("Disconnected: %s, reason 0x%02x %s\n", addr, reason, bt_hci_err_to_str(reason)); + + if (default_conn != conn) { + return; + } + + struct bt_conn *conn_to_unref; + + conn_to_unref = connected_conn; + connected_conn = NULL; + bt_conn_unref(conn_to_unref); + + conn_to_unref = default_conn; + default_conn = NULL; + bt_conn_unref(conn_to_unref); + + start_scan(); +} + +static struct bt_conn_cb conn_callbacks = { + .connected = connected, + .disconnected = disconnected, +}; + +static void test_gap_name(struct bt_conn *conn) +{ + int err; + char server_new_name[CONFIG_BT_DEVICE_NAME_MAX] = CONFIG_BT_DEVICE_NAME"-up"; + uint16_t chrc_handle; + + NET_BUF_SIMPLE_DEFINE(attr_value_buf, BT_ATT_MAX_ATTRIBUTE_LEN); + + err = bt_testlib_gatt_discover_characteristic(&chrc_handle, + NULL, NULL, conn, + BT_UUID_GAP_DEVICE_NAME, + BT_ATT_FIRST_ATTRIBUTE_HANDLE, + BT_ATT_LAST_ATTRIBUTE_HANDLE); + TEST_ASSERT(err == 0, "Device Name characteristic not found (err %d)", err); + + LOG_DBG("Device Name characteristic found at handle %u", chrc_handle); + + /* Read Device name */ + err = bt_testlib_att_read_by_handle_sync(&attr_value_buf, NULL, NULL, conn, + BT_ATT_CHAN_OPT_UNENHANCED_ONLY, chrc_handle, 0); + TEST_ASSERT(err == 0, "Failed to read characteristic (err %d)", err); + + LOG_DBG("Device Name of the server: %.*s", attr_value_buf.len, attr_value_buf.data); + + net_buf_simple_reset(&attr_value_buf); + + /* Write new Device name */ + err = bt_testlib_att_write(conn, + BT_ATT_CHAN_OPT_UNENHANCED_ONLY, + chrc_handle, + server_new_name, + sizeof(server_new_name)); + TEST_ASSERT(err == BT_ATT_ERR_SUCCESS, "Got ATT error: %d", err); + + /* Verify new Device name */ + err = bt_testlib_att_read_by_handle_sync(&attr_value_buf, NULL, NULL, conn, + BT_ATT_CHAN_OPT_UNENHANCED_ONLY, chrc_handle, 0); + TEST_ASSERT(err == 0, "Failed to read characteristic (err %d)", err); + + TEST_ASSERT(attr_value_buf.len == strlen(server_new_name), + "Unexpected Device Name length: %u (!=%u)", + attr_value_buf.len, sizeof(server_new_name)); + TEST_ASSERT(memcmp(attr_value_buf.data, server_new_name, attr_value_buf.len) == 0, + "Unexpected Device Name value: %.*s", + attr_value_buf.len, attr_value_buf.data); + + net_buf_simple_reset(&attr_value_buf); +} + +static void test_gap_appearance(struct bt_conn *conn) +{ + int err; + uint16_t chrc_handle; + uint16_t appearance; + + NET_BUF_SIMPLE_DEFINE(attr_value_buf, BT_ATT_MAX_ATTRIBUTE_LEN); + + err = bt_testlib_gatt_discover_characteristic(&chrc_handle, + NULL, NULL, conn, + BT_UUID_GAP_APPEARANCE, + BT_ATT_FIRST_ATTRIBUTE_HANDLE, + BT_ATT_LAST_ATTRIBUTE_HANDLE); + TEST_ASSERT(err == 0, "Device Appearance characteristic not found (err %d)", err); + + LOG_DBG("Device Appearance characteristic found at handle %u", chrc_handle); + + /* Read Device appearance */ + err = bt_testlib_att_read_by_handle_sync(&attr_value_buf, NULL, NULL, conn, + BT_ATT_CHAN_OPT_UNENHANCED_ONLY, chrc_handle, 0); + TEST_ASSERT(err == 0, "Failed to read characteristic (err %d)", err); + TEST_ASSERT(attr_value_buf.len == sizeof(appearance), + "Unexpected Appearance length: %u (!=%u)", + attr_value_buf.len, sizeof(uint16_t)); + appearance = sys_le16_to_cpu(*(uint16_t *)attr_value_buf.data); + LOG_DBG("Device Appearance of the server: %.4x", appearance); + net_buf_simple_reset(&attr_value_buf); + + /* Write new Device appearance */ + appearance += 0x100; + err = bt_testlib_att_write(conn, BT_ATT_CHAN_OPT_UNENHANCED_ONLY, chrc_handle, + (char *)&appearance, sizeof(appearance)); + TEST_ASSERT(err == BT_ATT_ERR_SUCCESS, "Got ATT error: %d", err); +} + +static void test_local_gap_svc_central_main(void) +{ + int err; + struct bt_conn *conn; + + k_sem_init(&connected_sem, 0, 1); + bt_conn_cb_register(&conn_callbacks); + + err = bt_enable(NULL); + TEST_ASSERT(err == 0, "Cannot enable Bluetooth (err %d)", err); + + LOG_INF("Bluetooth initialized"); + + start_scan(); + + /* Wait for connection */ + k_sem_take(&connected_sem, K_FOREVER); + + conn = bt_conn_ref(connected_conn); + + err = bt_testlib_att_exchange_mtu(conn); + TEST_ASSERT(err == 0, "Failed to update MTU (err %d)", err); + + test_gap_name(conn); + test_gap_appearance(conn); + + bt_conn_unref(conn); + + TEST_PASS("client"); +} + +static void test_local_gap_svc_central_init(void) +{ + bst_ticker_set_next_tick_absolute(WAIT_TIME); + TEST_START("test_local_gap_svc_central"); +} + +static void test_local_gap_svc_central_tick(bs_time_t HW_device_time) +{ + /* + * If in WAIT_TIME seconds the testcase did not already pass + * (and finish) we consider it failed + */ + if (bst_result != Passed) { + TEST_FAIL("test_local_gap_svc_central failed (not passed after %d seconds)", + (int)(WAIT_TIME / 1e6)); + } +} + + + +static const struct bst_test_instance test_central[] = { + { + .test_id = "central", + .test_descr = "GAP service local reimplementation - central role.", + .test_main_f = test_local_gap_svc_central_main, + .test_pre_init_f = test_local_gap_svc_central_init, + .test_tick_f = test_local_gap_svc_central_tick, + }, + BSTEST_END_MARKER +}; + +struct bst_test_list *test_local_gap_svc_central_install(struct bst_test_list *tests) +{ + tests = bst_add_tests(tests, test_central); + return tests; +} diff --git a/tests/bsim/bluetooth/host/gatt/gap_svc/src/main.c b/tests/bsim/bluetooth/host/gatt/gap_svc/src/main.c new file mode 100644 index 0000000000000..61ca999b2cf2b --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/gap_svc/src/main.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Koppel Electronic + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "bstests.h" + +extern struct bst_test_list *test_local_gap_svc_central_install(struct bst_test_list *tests); +extern struct bst_test_list *test_local_gap_svc_peripheral_install(struct bst_test_list *tests); + +bst_test_install_t test_installers[] = { + test_local_gap_svc_central_install, + test_local_gap_svc_peripheral_install, + NULL +}; + +int main(void) +{ + bst_main(); + return 0; +} diff --git a/tests/bsim/bluetooth/host/gatt/gap_svc/src/peripheral.c b/tests/bsim/bluetooth/host/gatt/gap_svc/src/peripheral.c new file mode 100644 index 0000000000000..9c4c37d54298b --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/gap_svc/src/peripheral.c @@ -0,0 +1,256 @@ +/** @file + * @brief Test local GATT Generic Access Service - peripheral role + */ +/* + * Copyright (c) 2025 Koppel Electronic + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include + +#include "babblekit/testcase.h" + + +LOG_MODULE_REGISTER(peripheral, LOG_LEVEL_DBG); + +BUILD_ASSERT(IS_ENABLED(CONFIG_BT_DEVICE_NAME_GATT_WRITABLE), + "This test requires BT_DEVICE_NAME_GATT_WRITABLE to be enabled"); +BUILD_ASSERT(IS_ENABLED(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE), + "This test requires BT_DEVICE_APPEARANCE_GATT_WRITABLE to be enabled"); +BUILD_ASSERT(!IS_ENABLED(CONFIG_BT_GAP_SVC_DEFAULT_IMPL), + "This test requires BT_GAP_SVC_DEFAULT_IMPL to be disabled"); +BUILD_ASSERT(!IS_ENABLED(CONFIG_BT_PRIVACY), + "Simplified GAP implementation - BT_PRIVACY not implemented"); +BUILD_ASSERT(!IS_ENABLED(CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS), + "Simplified GAP implementation - BT_GAP_PERIPHERAL_PREF_PARAMS not implemented"); + +/* Wait time in microseconds for the name and appearance to be changed */ +#define WAIT_TIME 10e6 +/* Name changed called in the test */ +bool gap_svc_name_changed; +/* Appearance changed called in the test */ +bool gap_svc_appearance_changed; + +/* Advertising data */ +static const struct bt_data ad[] = { + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA_BYTES(BT_DATA_UUID16_ALL, + BT_UUID_16_ENCODE(BT_UUID_HRS_VAL)), +}; + +/* ----------------------------------------------------------------------------- + * Local implementation of GAP service + */ + +static ssize_t read_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + LOG_DBG("Name read called"); + + const char *name = bt_get_name(); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, name, + strlen(name)); +} + +static ssize_t write_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + LOG_DBG("Name changed called"); + /* adding one to fit the terminating null character */ + char value[CONFIG_BT_DEVICE_NAME_MAX + 1] = {}; + + if (offset != 0) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (offset + len > CONFIG_BT_DEVICE_NAME_MAX) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + memcpy(value, buf, len); + + value[len] = '\0'; + + bt_set_name(value); + + LOG_INF("Name changed to %s", value); + gap_svc_name_changed = true; + if (gap_svc_appearance_changed) { + TEST_PASS("GAP service name and appearance changed successfully"); + } + + return len; +} + +static ssize_t read_appearance(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint16_t appearance = sys_cpu_to_le16(bt_get_appearance()); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &appearance, sizeof(appearance)); +} + +static ssize_t write_appearance(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, uint16_t offset, + uint8_t flags) +{ + LOG_DBG("Appearance write called"); + + uint16_t appearance; + int err; + + if (offset > 0) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != sizeof(appearance)) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + appearance = sys_get_le16(buf); + + err = bt_set_appearance(appearance); + + if (err) { + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + LOG_INF("Appearance changed to 0x%04x", appearance); + gap_svc_appearance_changed = true; + if (gap_svc_name_changed) { + TEST_PASS("GAP service name and appearance changed successfully"); + } + + return len; +} + +BT_GATT_SERVICE_DEFINE(BT_GATT_GAP_SVC_DEFAULT_NAME, + BT_GATT_PRIMARY_SERVICE(BT_UUID_GAP), + /* Require pairing for writes to device name */ + BT_GATT_CHARACTERISTIC(BT_UUID_GAP_DEVICE_NAME, + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, + read_name, write_name, NULL), + BT_GATT_CHARACTERISTIC(BT_UUID_GAP_APPEARANCE, + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, + read_appearance, write_appearance, NULL), +); + +/* End of local implementation of GAP service + * --------------------------------------------------------------------------- + */ + +static void connected(struct bt_conn *conn, uint8_t conn_err) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + if (conn_err) { + TEST_FAIL("Failed to connect to %s (%u)", addr, conn_err); + return; + } + + gap_svc_name_changed = false; + gap_svc_appearance_changed = false; + + LOG_INF("Connected: %s", addr); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + LOG_INF("Disconnected: %s (reason 0x%02x)", addr, reason); +} + +static int start_advertising(void) +{ + int err; + + err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), NULL, 0); + if (err) { + TEST_FAIL("Advertising failed to start (err %d)", err); + } + + return err; +} + +static void recycled(void) +{ + start_advertising(); +} + +static struct bt_conn_cb conn_callbacks = { + .connected = connected, + .disconnected = disconnected, + .recycled = recycled, +}; + +static void test_local_gap_svc_peripheral_main(void) +{ + int err; + + bt_conn_cb_register(&conn_callbacks); + + err = bt_enable(NULL); + + if (err) { + TEST_FAIL("Bluetooth init failed (err %d)", err); + return; + } + + LOG_INF("Peripheral Bluetooth initialized"); + err = start_advertising(); + if (err) { + return; + } + LOG_INF("Advertising successfully started"); +} + +static void test_local_gap_svc_peripheral_init(void) +{ + bst_ticker_set_next_tick_absolute(WAIT_TIME); + TEST_START("test_local_gap_svc_peripheral"); +} + +static void test_local_gap_svc_peripheral_tick(bs_time_t HW_device_time) + +{ + /* + * If in WAIT_TIME seconds the testcase did not already pass + * (and finish) we consider it failed + */ + if (bst_result != Passed) { + TEST_FAIL("test_local_gap_svc_peripheral failed (not passed after %d seconds)", + (int)(WAIT_TIME / 1e6)); + } +} + +static const struct bst_test_instance test_peripheral[] = { + { + .test_id = "peripheral", + .test_descr = "GAP service local reimplementation - peripheral role.", + .test_main_f = test_local_gap_svc_peripheral_main, + .test_pre_init_f = test_local_gap_svc_peripheral_init, + .test_tick_f = test_local_gap_svc_peripheral_tick, + }, + BSTEST_END_MARKER +}; + +struct bst_test_list *test_local_gap_svc_peripheral_install(struct bst_test_list *tests) +{ + tests = bst_add_tests(tests, test_peripheral); + return tests; +} diff --git a/tests/bsim/bluetooth/host/gatt/gap_svc/test_scripts/run_gap_svc.sh b/tests/bsim/bluetooth/host/gatt/gap_svc/test_scripts/run_gap_svc.sh new file mode 100755 index 0000000000000..0ba0bb939601f --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/gap_svc/test_scripts/run_gap_svc.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Copyright 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 +set -eu + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +test_name="$(guess_test_long_name)" +simulation_id=${test_name} + +verbosity_level=2 + +EXECUTE_TIMEOUT=120 + +SIM_LEN_US=$((10 * 1000 * 1000)) + +test_exe="${BSIM_OUT_PATH}/bin/bs_${BOARD_TS}_${test_name}_prj_conf" + +cd ${BSIM_OUT_PATH}/bin + +Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} -D=2 -sim_length=${SIM_LEN_US} $@ + +Execute "${test_exe}" -v=${verbosity_level} -s=${simulation_id} -d=0 -rs=420 -testid=peripheral \ + -RealEncryption=1 +Execute "${test_exe}" -v=${verbosity_level} -s=${simulation_id} -d=1 -rs=69 -testid=central \ + -RealEncryption=1 + +wait_for_background_jobs diff --git a/tests/bsim/bluetooth/host/gatt/gap_svc/testcase.yaml b/tests/bsim/bluetooth/host/gatt/gap_svc/testcase.yaml new file mode 100644 index 0000000000000..d48960efda12e --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/gap_svc/testcase.yaml @@ -0,0 +1,10 @@ +tests: + bluetooth.host.gatt.gap_svc: + build_only: true + tags: + - bluetooth + platform_allow: + - nrf52_bsim/native + harness: bsim + harness_config: + bsim_exe_name: tests_bsim_bluetooth_host_gatt_gap_svc_prj_conf From ebf2cad77fe74e3317d5e8aba0b66e4e020c0e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Koppel?= Date: Mon, 1 Dec 2025 23:59:16 +0100 Subject: [PATCH 6/6] samples: bluetooth: peripheral_gap_svc: Adding sample MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding a sample that presents how to implement non default GAP service inside the application. Signed-off-by: Radosław Koppel --- .../peripheral_gap_svc/CMakeLists.txt | 7 + .../bluetooth/peripheral_gap_svc/README.rst | 29 ++++ samples/bluetooth/peripheral_gap_svc/prj.conf | 14 ++ .../bluetooth/peripheral_gap_svc/sample.yaml | 14 ++ .../bluetooth/peripheral_gap_svc/src/main.c | 157 ++++++++++++++++++ 5 files changed, 221 insertions(+) create mode 100644 samples/bluetooth/peripheral_gap_svc/CMakeLists.txt create mode 100644 samples/bluetooth/peripheral_gap_svc/README.rst create mode 100644 samples/bluetooth/peripheral_gap_svc/prj.conf create mode 100644 samples/bluetooth/peripheral_gap_svc/sample.yaml create mode 100644 samples/bluetooth/peripheral_gap_svc/src/main.c diff --git a/samples/bluetooth/peripheral_gap_svc/CMakeLists.txt b/samples/bluetooth/peripheral_gap_svc/CMakeLists.txt new file mode 100644 index 0000000000000..831b26a3a756d --- /dev/null +++ b/samples/bluetooth/peripheral_gap_svc/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(peripheral_gap_svc) + +target_sources(app PRIVATE + src/main.c +) diff --git a/samples/bluetooth/peripheral_gap_svc/README.rst b/samples/bluetooth/peripheral_gap_svc/README.rst new file mode 100644 index 0000000000000..ea6439831a26d --- /dev/null +++ b/samples/bluetooth/peripheral_gap_svc/README.rst @@ -0,0 +1,29 @@ +.. zephyr:code-sample:: ble_peripheral_gap_svc + :name: Peripheral GAP Server non default implementation + :relevant-api: bluetooth + + Implement a dummy peripheral with GAP Server that limits the accepted names + +Overview +******** + +This sample demonstrates the implementation of the GAP service + + + + +This sample demonstrates the usage of the NUS service (Nordic UART Service) as a serial +endpoint to exchange data. In this case, the sample assumes the data is UTF-8 encoded, +but it may be binary data. Once the user connects to the device and subscribes to the TX +characteristic, it will start receiving periodic notifications with "Hello World!\n". + +Requirements +************ + +* BlueZ running on the host, or +* A board with Bluetooth LE support + +Building and Running +******************** + +See :zephyr:code-sample-category:`bluetooth` samples for details. diff --git a/samples/bluetooth/peripheral_gap_svc/prj.conf b/samples/bluetooth/peripheral_gap_svc/prj.conf new file mode 100644 index 0000000000000..4f9beebd4c063 --- /dev/null +++ b/samples/bluetooth/peripheral_gap_svc/prj.conf @@ -0,0 +1,14 @@ +CONFIG_LOG=y +CONFIG_BT=y +CONFIG_BT_SMP=y +CONFIG_BT_PERIPHERAL=y + +# Testing locally redefined SVC implementation +CONFIG_BT_GAP_SVC_DEFAULT_IMPL=n + +CONFIG_BT_DEVICE_NAME="Zephyr GAP service" +CONFIG_BT_DEVICE_NAME_DYNAMIC=y +CONFIG_BT_DEVICE_NAME_GATT_WRITABLE=y + +CONFIG_BT_DEVICE_APPEARANCE_DYNAMIC=n +CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE=n diff --git a/samples/bluetooth/peripheral_gap_svc/sample.yaml b/samples/bluetooth/peripheral_gap_svc/sample.yaml new file mode 100644 index 0000000000000..e0a6f36fd0088 --- /dev/null +++ b/samples/bluetooth/peripheral_gap_svc/sample.yaml @@ -0,0 +1,14 @@ +sample: + name: Bluetooth Peripheral GAP service non default implementation + description: Demonstrates the GAP service implementation on application side. +tests: + sample.bluetooth.peripheral_gap_svc: + harness: bluetooth + platform_allow: + - qemu_cortex_m3 + - qemu_x86 + - nrf52840dk/nrf52840 + - ophelia4ev/nrf54l15/cpuapp + integration_platforms: + - qemu_cortex_m3 + tags: bluetooth diff --git a/samples/bluetooth/peripheral_gap_svc/src/main.c b/samples/bluetooth/peripheral_gap_svc/src/main.c new file mode 100644 index 0000000000000..9482fe94bd9d7 --- /dev/null +++ b/samples/bluetooth/peripheral_gap_svc/src/main.c @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2025 Koppel Electronic + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + + +#define DEVICE_NAME CONFIG_BT_DEVICE_NAME +#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1) + +static const struct bt_data ad[] = { + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN), +}; + +/* ----------------------------------------------------------------------------- + * Local implementation of GAP service + */ + +static ssize_t read_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + const char *name = bt_get_name(); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, name, + strlen(name)); +} + +static ssize_t write_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + /* adding one to fit the terminating null character */ + char value[CONFIG_BT_DEVICE_NAME_MAX + 1] = {}; + + if (offset != 0) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (offset + len > CONFIG_BT_DEVICE_NAME_MAX) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + memcpy(value, buf, len); + + value[len] = '\0'; + + /* Check if the name starts with capital letter */ + if (value[0] < 'A' || value[0] > 'Z') { + printk("Rejected name change to \"%s\": must start with capital letter\n", value); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + bt_set_name(value); + + printk("Name changed to \"%s\"\n", value); + + return len; +} + +static ssize_t read_appearance(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint16_t appearance = sys_cpu_to_le16(bt_get_appearance()); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &appearance, sizeof(appearance)); +} + +BT_GATT_SERVICE_DEFINE(BT_GATT_GAP_SVC_DEFAULT_NAME, + BT_GATT_PRIMARY_SERVICE(BT_UUID_GAP), + /* Require pairing for writes to device name */ + BT_GATT_CHARACTERISTIC(BT_UUID_GAP_DEVICE_NAME, + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, + read_name, write_name, NULL), + BT_GATT_CHARACTERISTIC(BT_UUID_GAP_APPEARANCE, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + read_appearance, NULL, NULL), +); + +/* End of local implementation of GAP service + * --------------------------------------------------------------------------- + */ + +static void connected(struct bt_conn *conn, uint8_t conn_err) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + if (conn_err) { + printk("Failed to connect to %s (%u)\n", addr, conn_err); + return; + } + + printk("Connected: %s\n", addr); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + printk("Disconnected: %s (reason 0x%02x)\n", addr, reason); +} + +static int start_advertising(void) +{ + int err; + + err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), NULL, 0); + if (err) { + printk("Advertising failed to start (err %d)\n", err); + } + + return err; +} + +static void recycled(void) +{ + start_advertising(); +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected, + .disconnected = disconnected, + .recycled = recycled, +}; + +int main(void) +{ + int err; + + printk("Sample - Bluetooth Peripheral GAP service\n"); + + err = bt_enable(NULL); + if (err) { + printk("Failed to enable bluetooth: %d\n", err); + return err; + } + + err = start_advertising(); + if (err) { + return err; + } + + printk("Initialization complete\n"); + + return 0; +}