ToDo: Consider moving dbus_client instance into separate singleton, so that no destruction/construction is necessary when switching from GATT provisioning service to GATT proxy service (see next commit). --- Makefile.mesh | 4 +- mesh/gatt-service.c | 1178 ++++++++++++++++++++++++++++++++++++++++++ mesh/gatt-service.h | 53 ++ mesh/pb-gatt.c | 173 +++++++ mesh/pb-gatt.h | 20 + mesh/prov-acceptor.c | 6 + 6 files changed, 1433 insertions(+), 1 deletion(-) create mode 100644 mesh/gatt-service.c create mode 100644 mesh/gatt-service.h create mode 100644 mesh/pb-gatt.c create mode 100644 mesh/pb-gatt.h diff --git a/Makefile.mesh b/Makefile.mesh index e4c9fa6a32e6..700d64fe9293 100644 --- a/Makefile.mesh +++ b/Makefile.mesh @@ -35,10 +35,12 @@ mesh_sources = mesh/mesh.h mesh/mesh.c \ mesh/prov-acceptor.c mesh/prov-initiator.c \ mesh/manager.h mesh/manager.c \ mesh/pb-adv.h mesh/pb-adv.c \ + mesh/pb-gatt.h mesh/pb-gatt.c \ mesh/keyring.h mesh/keyring.c \ mesh/rpl.h mesh/rpl.c \ mesh/prv-beacon.h mesh/prvbeac-server.c \ - mesh/mesh-defs.h + mesh/mesh-defs.h \ + mesh/gatt-service.h mesh/gatt-service.c pkglibexec_PROGRAMS += mesh/bluetooth-meshd mesh/mesh.$(OBJEXT): ell/internal diff --git a/mesh/gatt-service.c b/mesh/gatt-service.c new file mode 100644 index 000000000000..8f3ace178f9d --- /dev/null +++ b/mesh/gatt-service.c @@ -0,0 +1,1178 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> // memcpy(), strerror() +#include <sys/socket.h> // SOCK_SEQPACKET, SOCK_NONBLOCK, + // AF_UNIX, SOCK_CLOEXEC, MSG_NOSIGNAL, + // struct msghdr, + // socketpair(), sendmsg() +#include <sys/types.h> // struct iovec +#include <unistd.h> // close() + +#include <ell/dbus.h> +#include <ell/dbus-client.h> +#include <ell/dbus-service.h> +#include <ell/idle.h> +#include <ell/io.h> +#include <ell/log.h> +#include <ell/util.h> // L_ARRAY_SIZE(), + // l_new(), l_free() + +#include "mesh/dbus.h" // dbus_get_bus(), + // dbus_append_byte_array(), + // dbus_error() +#include "mesh/error.h" // MESH_ERROR_INVALID_ARGS +#include "mesh/util.h" // print_packet() +#include "mesh/gatt-service.h" + +#define GATT_SERVICE_IFACE "org.bluez.GattService1" +#define BLUEZ_MESH_GATT_PATH BLUEZ_MESH_PATH "/gatt" +#define BLUEZ_MESH_SERVICE_PATH BLUEZ_MESH_GATT_PATH "/service" +#define BLUEZ_MESH_CHRC_DATA_IN_PATH BLUEZ_MESH_SERVICE_PATH "/data_in" +#define BLUEZ_MESH_CHRC_DATA_OUT_PATH BLUEZ_MESH_SERVICE_PATH "/data_out" +/* + * Advertising should NOT be handled by provisioning's object manager, so + * we cannot use a child element of BLUEZ_MESH_GATT_PATH. + */ +#define BLUEZ_MESH_GATT_ADV_PATH BLUEZ_MESH_PATH "/gatt_adv" + +#define GATT_MGR_IFACE "org.bluez.GattManager1" +#define GATT_SERVICE_IFACE "org.bluez.GattService1" +#define GATT_CHRC_IFACE "org.bluez.GattCharacteristic1" + +#define LE_ADVERTISING_MGR_IFACE "org.bluez.LEAdvertisingManager1" +#define LE_ADVERTISEMENT_IFACE "org.bluez.LEAdvertisement1" + +#define GATT_MTU 23 + +struct gatt_service; +struct characterstic +{ + const char *uuid; + const char * const *flags; + struct gatt_service *service; +}; + +enum write_value_type { + WRITE_VALUE_TYPE_COMMAND, + WRITE_VALUE_TYPE_REQUEST, + WRITE_VALUE_TYPE_RELIABLE +}; + +enum link_type { + LINK_TYPE_BR_EDR, + LINK_TYPE_LE +}; + +struct write_value_options { + const char *device; + enum link_type link; + enum write_value_type type; + uint16_t offset; + uint16_t mtu; + bool prepare_authorize; +}; + +struct acquire_notify_options { + const char *device; + enum link_type link; + uint16_t mtu; +}; + +/* MshPRT_v1.1, section 6.3.1, SAR field */ +enum proxy_pdu_sar { + PROXY_PDU_SAR_CMPLT_MSG = 0x00, + PROXY_PDU_SAR_1ST_SEG = 0x01, + PROXY_PDU_SAR_CONT_SEG = 0x02, + PROXY_PDU_SAR_LAST_SEG = 0x03, +}; + +struct gatt_service { + const char *svc_uuid; + uint8_t max_pdu_len; + + gatt_service_notify_acquired_cb notify_acquired_cb; + gatt_service_notify_stopped_cb notify_stopped_cb; + gatt_service_rx_cb rx_cb; + gatt_service_tx_cmplt_cb tx_cmplt_cb; + gatt_service_fill_adv_service_data_cb fill_adv_service_data_cb; + struct characterstic chrc_data_in; + struct characterstic chrc_data_out; + + struct l_dbus_client *dbus_client; + struct l_dbus_proxy *dbus_proxy_gatt_mgr; + struct l_dbus_proxy *dbus_proxy_le_adv_mgr; + + /* + * ToDo: Check whether acceptors timeout complies with MshPRT_v1.1, + * section 5.2.2 + */ + struct l_io *notify_io; + uint16_t mtu; + uint8_t *sar; + uint8_t *sar_out; + uint8_t msg_type; + uint8_t sar_len; + void *user_data; + + gatt_destroy_cb svc_deinit_cb; + gatt_destroy_cb adv_deinit_cb; + + gatt_destroy_cb destroy_cb; + void *destroy_data; +}; + +static struct gatt_service *gatt_service = NULL; + +static bool notify_write(struct l_io *io, void *user_data) +{ + struct gatt_service *service = user_data; + unsigned int remaining = (service->sar + service->sar_len) + - service->sar_out; + unsigned max_size = service->mtu - 5; + struct iovec iov[2]; + struct msghdr msg; + bool more = false; + uint8_t sar_type; + int i, count; + + /* Note: One extra byte is required for sar_type */ + if (service->sar_len < max_size) { + sar_type = PROXY_PDU_SAR_CMPLT_MSG; + count = service->sar_len; + } + else if (service->sar_out == service->sar) { + sar_type = PROXY_PDU_SAR_1ST_SEG; + count = max_size - 1; + more = true; + } + else if (remaining < max_size) { + sar_type = PROXY_PDU_SAR_LAST_SEG; + count = remaining; + } + else { + sar_type = PROXY_PDU_SAR_CONT_SEG; + count = max_size - 1; + more = true; + } + + sar_type <<= 6; + sar_type |= service->msg_type; + + l_info("remaining=%u, count=%u, sar_type=0x%02x", remaining, count, sar_type); + print_packet("notify_write", service->sar_out, count); + + iov[0].iov_base = &sar_type; + iov[0].iov_len = sizeof(sar_type); + iov[1].iov_base = service->sar_out; + iov[1].iov_len = count; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = L_ARRAY_SIZE(iov); + + if (sendmsg(l_io_get_fd(service->notify_io), &msg, MSG_NOSIGNAL) < 0) + l_error("Cannot write notification data: %s", strerror(errno)); + + service->sar_out += count; + + if (!more) + more = service->tx_cmplt_cb(service->user_data); + + return more; +} + +void gatt_service_tx(struct gatt_service *service, uint8_t msg_type, + const void *data, uint16_t len) +{ + if (!service || gatt_service != service) + return; + + if (len > service->max_pdu_len) { + l_error("Frame too long"); + return; + } + + memcpy(service->sar, data, len); + service->sar_len = len; + service->sar_out = service->sar; + service->msg_type = msg_type; + print_packet("TX", service->sar, service->sar_len); + l_io_set_write_handler(service->notify_io, notify_write, service, NULL); +} + +static bool svc_uuid_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + struct gatt_service *service = user_data; + +// l_info("svc_uuid_getter"); + return l_dbus_message_builder_append_basic(builder, 's', + service->svc_uuid); +} + +static bool svc_primary_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + bool primary = true; + +// l_info("svc_primary_getter"); + return l_dbus_message_builder_append_basic(builder, 'b', &primary); +} + +static void setup_gatt_svc_interface(struct l_dbus_interface *iface) +{ + l_dbus_interface_property(iface, "UUID", 0, "s", svc_uuid_getter, NULL); + l_dbus_interface_property(iface, "Primary", 0, "b", svc_primary_getter, + NULL); +} + +static bool parse_write_value_options(struct l_dbus_message_iter *itr, + struct write_value_options *opts) +{ + const char *key; + struct l_dbus_message_iter var; + + opts->device = NULL; + opts->link = LINK_TYPE_BR_EDR; + opts->type = WRITE_VALUE_TYPE_COMMAND; + opts->offset = 0; + opts->mtu = 0; + opts->prepare_authorize = false; + + while (l_dbus_message_iter_next_entry(itr, &key, &var)) { + if (!strcmp(key, "device")) { + if (!l_dbus_message_iter_get_variant(&var, "o", + &opts->device)) + return false; + } else if (!strcmp(key, "link")) { + const char *link; + + if (!l_dbus_message_iter_get_variant(&var, "s", &link)) + return false; + + if (!strcmp(link, "BR/EDR")) + opts->link = LINK_TYPE_BR_EDR; + else if (!strcmp(link, "LE")) + opts->link = LINK_TYPE_LE; + else + return false; + } else if (!strcmp(key, "type")) { + const char *type; + + if (!l_dbus_message_iter_get_variant(&var, "s", &type)) + return false; + + if (!strcmp(type, "command")) + opts->type = WRITE_VALUE_TYPE_COMMAND; + else if (!strcmp(type, "request")) + opts->type = WRITE_VALUE_TYPE_REQUEST; + else if (!strcmp(type, "reliable")) + opts->type = WRITE_VALUE_TYPE_RELIABLE; + else + return false; + } else if (!strcmp(key, "offset")) { + if (!l_dbus_message_iter_get_variant(&var, "q", + &opts->offset)) + return false; + } else if (!strcmp(key, "mtu")) { + if (!l_dbus_message_iter_get_variant(&var, "q", + &opts->mtu)) + return false; + } else if (!strcmp(key, "prepare-authorize")) { + if (!l_dbus_message_iter_get_variant(&var, "b", + &opts->prepare_authorize)) + return false; + } + } + + return true; +} + +static struct l_dbus_message *chrc_write_value_call(struct l_dbus *, + struct l_dbus_message *msg, + void *user_data) +{ + struct characterstic *chr = user_data; + struct gatt_service *service = chr->service; + struct l_dbus_message_iter iter_data, dict; + struct write_value_options opts; + enum proxy_pdu_sar sar; + uint8_t msg_type; + uint8_t *data; + uint32_t len; + int i; + + if (!l_dbus_message_get_arguments(msg, "aya{sv}", &iter_data, &dict)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!parse_write_value_options(&dict, &opts)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!l_dbus_message_iter_get_fixed_array(&iter_data, &data, &len) || + !len || len > service->max_pdu_len) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Incorrect data"); + + l_info("chrc_write_value_call(type=%u, offset=%u, mtu=%u)", opts.type, opts.offset, opts.mtu); + print_packet("WriteValue", data, len); + + if (len < 1) + return l_dbus_message_new_method_return(msg); + + sar = (data[0] >> 6) & 0x03; + msg_type = data[0] & 0x3F; + + switch (sar) { + case PROXY_PDU_SAR_CMPLT_MSG: + service->rx_cb(service->user_data, msg_type, data, len); + break; + + case PROXY_PDU_SAR_1ST_SEG: + if (len > service->max_pdu_len) { + l_debug("Length exceeded: %d", len); + break; + } + + memcpy(service->sar, data, len); + service->sar_len = len; + break; + + case PROXY_PDU_SAR_CONT_SEG: + case PROXY_PDU_SAR_LAST_SEG: { + if (len - 1 > service->max_pdu_len - service->sar_len) { + l_debug("Length exceeded: %d", len); + break; + } + + memcpy(service->sar + service->sar_len, + data + 1, len - 1); + service->sar_len += len - 1; + + if (sar == PROXY_PDU_SAR_LAST_SEG) { + uint8_t sar_len = service->sar_len; + + /* reused by gatt_service_tx */ + service->sar_len = 0; + service->rx_cb(service->user_data, msg_type, + service->sar, sar_len); + } + + break; + } + } + + return l_dbus_message_new_method_return(msg); +} + +static bool parse_acquire_notify_options(struct l_dbus_message_iter *itr, + struct acquire_notify_options *opts) +{ + const char *key; + struct l_dbus_message_iter var; + + opts->device = NULL; + opts->link = LINK_TYPE_BR_EDR; + opts->mtu = 0; + + while (l_dbus_message_iter_next_entry(itr, &key, &var)) { + if (!strcmp(key, "device")) { + if (!l_dbus_message_iter_get_variant(&var, "o", + &opts->device)) + return false; + } else if (!strcmp(key, "link")) { + const char *link; + + if (!l_dbus_message_iter_get_variant(&var, "s", &link)) + return false; + + if (!strcmp(link, "BR/EDR")) + opts->link = LINK_TYPE_BR_EDR; + else if (!strcmp(link, "LE")) + opts->link = LINK_TYPE_LE; + else + return false; + } else if (!strcmp(key, "mtu")) { + if (!l_dbus_message_iter_get_variant(&var, "q", + &opts->mtu)) + return false; + } + } + + return true; +} + +static void notify_disconnected(struct l_io *io, void *user_data) +{ + struct gatt_service *service = user_data; + + if (service != gatt_service) + return; + + l_info("notify_disconnected"); + + if (!service->notify_io) + return; + + if (service->notify_stopped_cb) + service->notify_stopped_cb(service->user_data); +} + +static struct l_dbus_message *chrc_acquire_notify_call(struct l_dbus *, + struct l_dbus_message *msg, + void *user_data) +{ + struct characterstic *chr = user_data; + struct gatt_service *service = chr->service; + struct l_dbus_message_iter dict; + struct acquire_notify_options opts; + struct l_dbus_message *reply; + int fds[2]; + + l_info("AcquireNotify"); + + if (!l_dbus_message_get_arguments(msg, "a{sv}", &dict)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!parse_acquire_notify_options(&dict, &opts)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (socketpair(AF_UNIX, + SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, + 0, fds) == -1) + return dbus_error(msg, MESH_ERROR_FAILED, + "Cannot create socket"); + + service->notify_io = l_io_new(fds[0]); + l_io_set_close_on_destroy(service->notify_io, true); + l_io_set_disconnect_handler(service->notify_io, notify_disconnected, + service, NULL); + service->mtu = opts.mtu; + l_info("AcquireNotify: mtu=%u", opts.mtu); + + if (service->notify_acquired_cb) + service->notify_acquired_cb(service->user_data); + + reply = l_dbus_message_new_method_return(msg); + + /* l_dbus_message_builder_append_basic() cannot append UNIX FDs */ + l_dbus_message_set_arguments(reply, "hq", fds[1], service->mtu); + /* + * file descriptor for bluetoothd has just been dup'ed and must be + * closed here in order to get disconnect event after GATT notifications + * notifications have been disabled. + */ + close(fds[1]); + + return reply; +} + +static bool chrc_uuid_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct characterstic *chr = user_data; + + const char *path = l_dbus_message_get_path(msg); + const char *interface = l_dbus_message_get_interface(msg); + const char *member = l_dbus_message_get_member(msg); + +// l_info("chrc_uuid_getter(path=%s, interface=%s, member=%s)", path, interface, member); + return l_dbus_message_builder_append_basic(builder, 's', chr->uuid); +} + +static bool chrc_service_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ +// l_info("chrc_service_getter"); + return l_dbus_message_builder_append_basic(builder, 'o', + BLUEZ_MESH_SERVICE_PATH); +} + +static bool chrc_notify_acquired_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct characterstic *chr = user_data; + const struct gatt_service *service = chr->service; + bool notifying = !!service->notify_io; + +// l_info("chrc_notify_acquired_getter"); + + return l_dbus_message_builder_append_basic(builder, 'b', ¬ifying); +} + +static bool chrc_flags_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct characterstic *chr = user_data; + const char * const *flag = chr->flags; + +// l_info("chrc_flags_getter"); + + l_dbus_message_builder_enter_array(builder, "s"); + + while (*flag) + l_dbus_message_builder_append_basic(builder, 's', *flag++); + + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static void setup_gatt_chrc_interface(struct l_dbus_interface *iface) +{ + /* Note: "ReadValue" method is not supported. */ + l_dbus_interface_method(iface, "WriteValue" , 0, chrc_write_value_call, + "", "aya{sv}", + "value", "options"); + l_dbus_interface_method(iface, "AcquireNotify", 0, + chrc_acquire_notify_call, + "hq", "a{sv}", + "fd", "mtu", + "options"); + l_dbus_interface_property(iface, "UUID" , 0, "s", chrc_uuid_getter, + NULL); + l_dbus_interface_property(iface, "Service", 0, "o", chrc_service_getter, + NULL); + l_dbus_interface_property(iface, "NotifyAcquired", 0, "b", + chrc_notify_acquired_getter, NULL); + l_dbus_interface_property(iface, "Flags" , 0, "as", chrc_flags_getter, + NULL); +} + +static void register_app_setup(struct l_dbus_message *msg, void *user_data) +{ + struct l_dbus_message_builder *builder; + +// l_info("register_app_setup"); + + builder = l_dbus_message_builder_new(msg); + + /* Object path */ + l_dbus_message_builder_append_basic(builder, 'o', BLUEZ_MESH_GATT_PATH); + + /* Options (empty) */ + l_dbus_message_builder_enter_array(builder, "{sv}"); + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_leave_dict(builder); + l_dbus_message_builder_leave_array(builder); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void register_app_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ +// l_info("register_app_reply"); + + if (l_dbus_message_is_error(result)) { + const char *error; + + l_dbus_message_get_error(result, &error, NULL); + + l_error("Registration of GATT application failed: %s", error); + } +} + +static bool gatt_svc_init(struct l_dbus *dbus, struct l_dbus_proxy *dbus_proxy, + struct gatt_service *service) +{ + if (!l_dbus_register_interface(dbus, GATT_SERVICE_IFACE, + setup_gatt_svc_interface, + NULL, false)) { + l_error("Cannot register " GATT_SERVICE_IFACE " interface"); + goto error_return; + } + + if (!l_dbus_register_interface(dbus, GATT_CHRC_IFACE, + setup_gatt_chrc_interface, + NULL, false)) { + l_error("Cannot register " GATT_CHRC_IFACE " interface"); + goto error_unregister_svc_iface; + } + + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_SERVICE_PATH, + GATT_SERVICE_IFACE, service)) { + l_error("Cannot add GATT service"); + goto error_unregister_chrc_iface; + } + + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_CHRC_DATA_IN_PATH, + GATT_CHRC_IFACE, + &service->chrc_data_in)) { + l_error("Cannot add GATT Data In characteristic"); + goto error_remove_svc; + } + + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_CHRC_DATA_OUT_PATH, + GATT_CHRC_IFACE, + &service->chrc_data_out)) { + l_error("Cannot add GATT Data Out characteristic"); + goto error_remove_data_in_chrc; + } + + if (!l_dbus_object_manager_enable(dbus, BLUEZ_MESH_GATT_PATH)) { + l_error("Cannot enable object manager"); + goto error_remove_data_out_chrc; + } + + if (!l_dbus_proxy_method_call(dbus_proxy, "RegisterApplication", + register_app_setup, + register_app_reply, + NULL, NULL)) { + l_error("Cannot register GATT application"); + goto error_disable_object_manager; + } + + return true; + +error_disable_object_manager: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_PATH, + L_DBUS_INTERFACE_OBJECT_MANAGER); + +error_remove_data_out_chrc: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_CHRC_DATA_OUT_PATH, + GATT_CHRC_IFACE); + +error_remove_data_in_chrc: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_CHRC_DATA_IN_PATH, + GATT_CHRC_IFACE); + +error_remove_svc: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_SERVICE_PATH, + GATT_SERVICE_IFACE); + +error_unregister_chrc_iface: + l_dbus_unregister_interface(dbus, GATT_CHRC_IFACE); + +error_unregister_svc_iface: + l_dbus_unregister_interface(dbus, GATT_SERVICE_IFACE); + +error_return: + return false; +} + +static void unregister_app_setup(struct l_dbus_message *msg, void *user_data) +{ + struct l_dbus_message_builder *builder; + +// l_info("unregister_app_setup"); + + builder = l_dbus_message_builder_new(msg); + + /* Object path */ + l_dbus_message_builder_append_basic(builder, 'o', BLUEZ_MESH_GATT_PATH); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void unregister_app_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct gatt_service *service = user_data; + struct l_dbus *dbus = dbus_get_bus(); +// l_info("unregister_app_reply"); + + if (l_dbus_message_is_error(result)) { + const char *error; + + l_dbus_message_get_error(result, &error, NULL); + + l_error("Unregistration of GATT application failed: %s", error); + } + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_PATH, + L_DBUS_INTERFACE_OBJECT_MANAGER); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_CHRC_DATA_OUT_PATH, + GATT_CHRC_IFACE); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_CHRC_DATA_IN_PATH, + GATT_CHRC_IFACE); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_SERVICE_PATH, + GATT_SERVICE_IFACE); + + l_dbus_unregister_interface(dbus, GATT_CHRC_IFACE); + l_dbus_unregister_interface(dbus, GATT_SERVICE_IFACE); + + if (service->svc_deinit_cb) + service->svc_deinit_cb(service); +} + +static void gatt_svc_deinit(struct gatt_service *service, gatt_destroy_cb cb) +{ + service->svc_deinit_cb = cb; + + if (!l_dbus_proxy_method_call(service->dbus_proxy_gatt_mgr, + "UnregisterApplication", + unregister_app_setup, + unregister_app_reply, + service, NULL)) { + l_error("Cannot unregister GATT application"); + } +} + +static struct l_dbus_message *adv_release_call(struct l_dbus *, + struct l_dbus_message *msg, + void *user_data) +{ + l_info("ADV Release"); + + return NULL; +} + +static bool adv_type_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ +// l_info("adv_type_getter"); + + return l_dbus_message_builder_append_basic(builder, 's', "peripheral"); +} + +static bool adv_svc_uuids_getter(struct l_dbus *dbus, + struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct gatt_service *service = user_data; + +// l_info("adv_svc_uuids_getter"); + l_dbus_message_builder_enter_array(builder, "s"); + l_dbus_message_builder_append_basic(builder, 's', service->svc_uuid); + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static bool adv_svc_data_getter(struct l_dbus *dbus, + struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct gatt_service *service = user_data; + +// l_info("adv_svc_data_getter"); + l_dbus_message_builder_enter_array(builder, "{sv}"); + + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_append_basic(builder, 's', service->svc_uuid); + l_dbus_message_builder_enter_variant(builder, "ay"); + + if (!service->fill_adv_service_data_cb(service->user_data, builder)) + return false; + + l_dbus_message_builder_leave_variant(builder); + l_dbus_message_builder_leave_dict(builder); + + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static bool adv_local_name_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint16_t max_interval_ms = 1000; + uint16_t duration = 1 * max_interval_ms; + +// l_info("adv_local_name_getter"); + + return l_dbus_message_builder_append_basic(builder, 's', "Test"); +} + +static bool adv_duration_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint16_t max_interval_ms = 1000; + uint16_t duration = 1 * max_interval_ms; + +// l_info("adv_duration_getter"); + + return l_dbus_message_builder_append_basic(builder, 'q', &duration); +} + +static bool adv_timeout_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint16_t timeout = 1000; + +// l_info("adv_timeout_getter"); + + return l_dbus_message_builder_append_basic(builder, 'q', &timeout); +} + +static bool adv_min_interval_getter(struct l_dbus *dbus, + struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint32_t min_interval_ms = 1000; + +// l_info("adv_min_interval_getter"); + + return l_dbus_message_builder_append_basic(builder, 'u', + &min_interval_ms); +} + +static bool adv_max_interval_getter(struct l_dbus *dbus, + struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint32_t max_interval_ms = 1000; + +// l_info("adv_max_interval_getter"); + + return l_dbus_message_builder_append_basic(builder, 'u', + &max_interval_ms); +} + +static void setup_le_adv_interface(struct l_dbus_interface *iface) +{ + l_dbus_interface_method(iface, "Release", 0, adv_release_call, + "", ""); + l_dbus_interface_property(iface, "Type", 0, "s", + adv_type_getter, NULL); + l_dbus_interface_property(iface, "ServiceUUIDs", 0, "as", + adv_svc_uuids_getter, NULL); + l_dbus_interface_property(iface, "ServiceData", 0, "a{sv}", + adv_svc_data_getter, NULL); + l_dbus_interface_property(iface, "LocalName", 0, "s", + adv_local_name_getter, NULL); + l_dbus_interface_property(iface, "Duration", 0, "q", + adv_duration_getter, NULL); + l_dbus_interface_property(iface, "Timeout", 0, "q", + adv_timeout_getter, NULL); + l_dbus_interface_property(iface, "MinInterval", 0, "u", + adv_min_interval_getter, NULL); + l_dbus_interface_property(iface, "MaxInterval", 0, "u", + adv_max_interval_getter, NULL); +} + +static void register_adv_setup(struct l_dbus_message *msg, void *user_data) +{ + struct l_dbus_message_builder *builder; + +// l_info("register_adv_setup"); + builder = l_dbus_message_builder_new(msg); + + /* Object path */ + l_dbus_message_builder_append_basic(builder, 'o', + BLUEZ_MESH_GATT_ADV_PATH); + + /* Options (empty) */ + l_dbus_message_builder_enter_array(builder, "{sv}"); + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_leave_dict(builder); + l_dbus_message_builder_leave_array(builder); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void register_adv_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ +// l_info("register_adv_reply"); + if (l_dbus_message_is_error(result)) { + const char *error; + + l_dbus_message_get_error(result, &error, NULL); + + l_error("Registration of LE advertising failed: %s", error); + } +} + +static bool gatt_adv_init(struct l_dbus *dbus, struct l_dbus_proxy *dbus_proxy, + struct gatt_service *service) +{ + if (!l_dbus_register_interface(dbus, LE_ADVERTISEMENT_IFACE, + setup_le_adv_interface, + NULL, false)) { + l_error("Cannot register " LE_ADVERTISEMENT_IFACE " interface"); + goto error_return; + } + + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + LE_ADVERTISEMENT_IFACE, + service)) { + l_error("Cannot add provisioner LE advertising service"); + goto error_unregister_le_adv_iface; + } + + if (!l_dbus_object_manager_enable(dbus, BLUEZ_MESH_GATT_ADV_PATH)) { + l_error("Cannot enable object manager"); + goto error_remove_le_adv; + } + + /* + * org.freedesktop.DBus.Properties is required for building + * propertiesChanged signals + */ + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_PROPERTIES, NULL)) { + l_error("Cannot add LE advertising properties"); + goto error_disable_object_manager; + } + + if (!l_dbus_proxy_method_call(dbus_proxy, "RegisterAdvertisement", + register_adv_setup, + register_adv_reply, + NULL, NULL)) { + l_error("Cannot register LE advertisement"); + goto error_remove_properties_iface; + } + + return true; + +error_remove_properties_iface: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_PROPERTIES); + +error_disable_object_manager: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_OBJECT_MANAGER); + +error_remove_le_adv: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + LE_ADVERTISEMENT_IFACE); + +error_unregister_le_adv_iface: + l_dbus_unregister_interface(dbus, LE_ADVERTISEMENT_IFACE); + +error_return: + return false; +} + +static void unregister_adv_setup(struct l_dbus_message *msg, void *user_data) +{ + struct l_dbus_message_builder *builder; + +// l_info("unregister_adv_setup"); + + builder = l_dbus_message_builder_new(msg); + + /* Object path */ + l_dbus_message_builder_append_basic(builder, 'o', BLUEZ_MESH_GATT_ADV_PATH); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void unregister_adv_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct gatt_service *service = user_data; + struct l_dbus *dbus = dbus_get_bus(); +// l_info("unregister_adv_reply"); + + if (l_dbus_message_is_error(result)) { + const char *error; + + l_dbus_message_get_error(result, &error, NULL); + + l_error("Unregistration of LE advertisement failed: %s", error); + } + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_PROPERTIES); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_OBJECT_MANAGER); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + LE_ADVERTISEMENT_IFACE); + + l_dbus_unregister_interface(dbus, LE_ADVERTISEMENT_IFACE); + + if (service->adv_deinit_cb) + service->adv_deinit_cb(service); +} + +static void gatt_adv_deinit(struct gatt_service *service, gatt_destroy_cb cb) +{ + service->adv_deinit_cb = cb; + + if (!l_dbus_proxy_method_call(service->dbus_proxy_le_adv_mgr, + "UnregisterAdvertisement", + unregister_adv_setup, + unregister_adv_reply, + service, NULL)) { + l_error("Cannot unregister LE advertisement"); + } +} + +static void dbus_proxy_added(struct l_dbus_proxy *dbus_proxy, void *user_data) +{ + const char *interface = l_dbus_proxy_get_interface(dbus_proxy); + const char *path = l_dbus_proxy_get_path(dbus_proxy); + struct gatt_service *service = user_data; + + l_info("Proxy added: %s (%s)", interface, path); + + if (!strcmp(interface, GATT_MGR_IFACE)) { + service->dbus_proxy_gatt_mgr = dbus_proxy; + gatt_svc_init(dbus_get_bus(), dbus_proxy, service); + } else if (!strcmp(interface, LE_ADVERTISING_MGR_IFACE)) { + service->dbus_proxy_le_adv_mgr = dbus_proxy; + gatt_adv_init(dbus_get_bus(), dbus_proxy, service); + } +} + +static void dbus_proxy_removed(struct l_dbus_proxy *proxy, void *user_data) +{ + const char *interface = l_dbus_proxy_get_interface(proxy); + const char *path = l_dbus_proxy_get_path(proxy); + struct gatt_service *service = user_data; + + l_info("Proxy removed: %s (%s)", interface, path); + + if (!strcmp(interface, GATT_MGR_IFACE)) + service->dbus_proxy_gatt_mgr = NULL; + else if (!strcmp(interface, LE_ADVERTISING_MGR_IFACE)) + service->dbus_proxy_le_adv_mgr = NULL; +} + +struct gatt_service * +gatt_service_create( + const char *svc_uuid, + const char *chrc_data_in_uuid, + const char *chrc_data_out_uuid, + uint8_t max_pdu_len, + gatt_service_notify_acquired_cb notify_acquired_cb, + gatt_service_notify_stopped_cb notify_stopped_cb, + gatt_service_rx_cb rx_cb, + gatt_service_tx_cmplt_cb tx_cmplt_cb, + gatt_service_fill_adv_service_data_cb fill_adv_service_data_cb, + void *user_data) +{ + static const char *flags_data_in[] = {"write-without-response", NULL}; + static const char *flags_data_out[] = {"notify", NULL}; + + /* Only one GATT service may exist at a time (MshPRT_v1.1, chapter 7) */ + if (gatt_service) + return NULL; + + gatt_service = l_new(struct gatt_service, 1); + gatt_service->svc_uuid = svc_uuid; + gatt_service->max_pdu_len = max_pdu_len; + gatt_service->sar = l_malloc(max_pdu_len); + + gatt_service->notify_acquired_cb = notify_acquired_cb; + gatt_service->notify_stopped_cb = notify_stopped_cb; + gatt_service->rx_cb = rx_cb; + gatt_service->tx_cmplt_cb = tx_cmplt_cb; + gatt_service->fill_adv_service_data_cb = fill_adv_service_data_cb; + gatt_service->user_data = user_data; + gatt_service->mtu = GATT_MTU; + + gatt_service->chrc_data_in.uuid = chrc_data_in_uuid; + gatt_service->chrc_data_in.flags = flags_data_in; + gatt_service->chrc_data_in.service = gatt_service; + + gatt_service->chrc_data_out.uuid = chrc_data_out_uuid; + gatt_service->chrc_data_out.flags = flags_data_out; + gatt_service->chrc_data_out.service = gatt_service; + + gatt_service->dbus_client = l_dbus_client_new(dbus_get_bus(), + "org.bluez", "/org/bluez"); + + l_dbus_client_set_proxy_handlers(gatt_service->dbus_client, + dbus_proxy_added, + dbus_proxy_removed, + NULL, + gatt_service, NULL); + + return gatt_service; +} + +static void gatt_svc_destroy(void *user_data) +{ + struct gatt_service *service = user_data; + gatt_destroy_cb destroy_cb; + void *destroy_data; + + if (!gatt_service || gatt_service != service) + return; + + destroy_cb = service->destroy_cb; + destroy_data = service->destroy_data; + + l_dbus_client_destroy(service->dbus_client); + l_io_destroy(service->notify_io); + l_free(service->sar); + l_free(service); + gatt_service = NULL; + + if (destroy_cb) + destroy_cb(destroy_data); +} + +static void gatt_svc_deinit_finished(void *user_data) +{ + struct gatt_service *service = user_data; + + if (!gatt_service || gatt_service != service) + return; + + /* l_dbus_client_destroy() must not be called from dbus context */ + l_idle_oneshot(gatt_svc_destroy, service, NULL); +} + +static void gatt_adv_deinit_finished(void *user_data) +{ + struct gatt_service *service = user_data; + + if (!gatt_service || gatt_service != service) + return; + + gatt_svc_deinit(service, gatt_svc_deinit_finished); +} + +void gatt_service_destroy(struct gatt_service *service, + gatt_destroy_cb destroy_cb, void *user_data) +{ + if (!gatt_service || gatt_service != service) + return; + + /* avoid recursion */ + l_io_set_disconnect_handler(service->notify_io, NULL, NULL, NULL); + + service->destroy_cb = destroy_cb; + service->destroy_data = user_data; + gatt_adv_deinit(service, gatt_adv_deinit_finished); +} + +void gatt_service_adv_updated(struct gatt_service *service) +{ + if (!gatt_service || gatt_service != service) + return; + + l_dbus_property_changed(dbus_get_bus(), BLUEZ_MESH_GATT_ADV_PATH, + LE_ADVERTISEMENT_IFACE, "ServiceData"); +} diff --git a/mesh/gatt-service.h b/mesh/gatt-service.h new file mode 100644 index 000000000000..a37966ec15f6 --- /dev/null +++ b/mesh/gatt-service.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <stdbool.h> +#include <stdint.h> + +/* MshPRT_v1.1, section 6.3.1 */ +enum proxy_msg_type { + PROXY_MSG_TYPE_NETWORK_PDU = 0x00, + PROXY_MSG_TYPE_MESH_BEACON = 0x01, + PROXY_MSG_TYPE_PROXY_CFG = 0x02, + PROXY_MSG_TYPE_PROV_PDU = 0x03 +}; + +typedef void (*gatt_service_notify_acquired_cb)(void *user_data); +typedef void (*gatt_service_notify_stopped_cb)(void *user_data); +typedef void (*gatt_service_rx_cb)(void *user_data, + enum proxy_msg_type messageType, + const void *data, uint16_t len); +typedef bool (*gatt_service_tx_cmplt_cb)(void *user_data); +typedef bool (*gatt_service_fill_adv_service_data_cb)(void *user_data, + struct l_dbus_message_builder *builder); + +typedef void (*gatt_destroy_cb)(void *user_data); + +struct gatt_service; + +struct gatt_service * +gatt_service_create( + const char *svc_uuid, + const char *chrc_data_in_uuid, + const char *chrc_data_out_uuid, + uint8_t max_pdu_len, + gatt_service_notify_acquired_cb notify_acquired_cb, + gatt_service_notify_stopped_cb notify_stopped_cb, + gatt_service_rx_cb rx_cb, + gatt_service_tx_cmplt_cb tx_cmplt_cb, + gatt_service_fill_adv_service_data_cb fill_adv_service_data_cb, + void *user_data); + +void gatt_service_destroy(struct gatt_service *service, + gatt_destroy_cb destroy_cb, void *user_data); + +void gatt_service_tx(struct gatt_service *service, uint8_t msg_type, + const void *data, uint16_t len); +void gatt_service_adv_updated(struct gatt_service *service); diff --git a/mesh/pb-gatt.c b/mesh/pb-gatt.c new file mode 100644 index 000000000000..797dd7361ee1 --- /dev/null +++ b/mesh/pb-gatt.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <string.h> // memcpy() +#include <sys/types.h> // struct timeval [required by prov.h] + +#include <ell/dbus.h> +//#include <ell/log.h> +//#include <ell/timeout.h> +#include <ell/util.h> // l_new(), l_free() + +#include "mesh/gatt-service.h" +#include "mesh/net.h" // mesh_net_prov_caps, required by prov.h +#include "mesh/prov.h" // mesh_prov_open_func_t, + // mesh_prov_close_func_t, + // mesh_prov_receive_func_t +#include "mesh/provision.h" // PB_GATT +#include "mesh/pb-gatt.h" + +#define MESH_GATT_PROV_SVC_UUID "0x1827" +#define MESH_GATT_PROV_CHRC_DATA_IN "0x2ADB" +#define MESH_GATT_PROV_CHRC_DATA_OUT "0x2ADC" +#define MAX_PROXY_PROV_PDU_LEN 66 /* MshPRT_v1.1, section 7.1.3.1 / 7.1.3.2 */ + +struct pb_gatt_session { + mesh_prov_open_func_t open_cb; + mesh_prov_close_func_t close_cb; + mesh_prov_receive_func_t rx_cb; + mesh_prov_ack_func_t ack_cb; +// struct l_timeout *tx_timeout; + uint8_t uuid[16]; + uint16_t oob_info; + + struct gatt_service *gatt_service; + void *user_data; + + pb_gatt_destroy_cb destroy_cb; + void *destroy_data; +}; + +static struct pb_gatt_session *pb_session = NULL; + +static void pb_gatt_tx(void *user_data, const void *data, uint16_t len) +{ + struct pb_gatt_session *session = user_data; + + gatt_service_tx(session->gatt_service, PROXY_MSG_TYPE_PROV_PDU, data, len); +} + +static void gatt_notify_acquired_cb(void *user_data) +{ + struct pb_gatt_session *session = user_data; + + /* + * MshPRT_v1.1, section 5.2.2: The link is opened on a PB-GATT + * bearer when the PB-GATT Client enables notifications. + */ + session->open_cb(session->user_data, pb_gatt_tx, session, PB_GATT); +} + +static void gatt_notify_stopped_cb(void *user_data) +{ + struct pb_gatt_session *session = user_data; + + session->close_cb(session->user_data, PROV_ERR_UNEXPECTED_ERR); +} + +static void gatt_rx_cb(void *user_data, enum proxy_msg_type msg_type, + const void *data, uint16_t len) +{ + struct pb_gatt_session *session = user_data; + + if (msg_type == PROXY_MSG_TYPE_PROV_PDU) + session->rx_cb(session->user_data, data + 1, len - 1); +} + +static bool gatt_tx_cmplt_cb(void *user_data) +{ + struct pb_gatt_session *session = user_data; + + session->ack_cb(session->user_data, 0 /* don't care */); + return false; +} + +static bool gatt_fill_adv_service_data_cb(void *user_data, + struct l_dbus_message_builder *builder) +{ + struct pb_gatt_session *session = user_data; + uint8_t oob_info[2]; + int i; + + l_dbus_message_builder_enter_array(builder, "y"); + + for (i = 0; i < sizeof(session->uuid); i++) + l_dbus_message_builder_append_basic(builder, 'y', + &(session->uuid[i])); + l_put_be16(session->oob_info, oob_info); + for (i = 0; i < sizeof(oob_info); i++) + l_dbus_message_builder_append_basic(builder, 'y', + &(oob_info[i])); + l_dbus_message_builder_leave_array(builder); + + return true; +} + +bool pb_gatt_reg(mesh_prov_open_func_t open_cb, mesh_prov_close_func_t close_cb, + mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb, + const uint8_t *uuid, uint16_t oob_info, void *user_data) +{ + pb_session = l_new(struct pb_gatt_session, 1); + + pb_session->open_cb = open_cb; + pb_session->close_cb = close_cb; + pb_session->rx_cb = rx_cb; + pb_session->ack_cb = ack_cb; + + memcpy(pb_session->uuid, uuid, 16); + pb_session->user_data = user_data; + + pb_session->gatt_service = gatt_service_create(MESH_GATT_PROV_SVC_UUID, + MESH_GATT_PROV_CHRC_DATA_IN, + MESH_GATT_PROV_CHRC_DATA_OUT, + MAX_PROXY_PROV_PDU_LEN, + gatt_notify_acquired_cb, + gatt_notify_stopped_cb, + gatt_rx_cb, gatt_tx_cmplt_cb, + gatt_fill_adv_service_data_cb, + pb_session); + if (!pb_session->gatt_service) { + l_free(pb_session); + pb_session = NULL; + return false; + } + + return true; +} + +static void gatt_destroy_finished(void *user_data) +{ + pb_gatt_destroy_cb destroy_cb; + void *destroy_data; + + if (!pb_session || pb_session != user_data) + return; + + destroy_cb = pb_session->destroy_cb; + destroy_data = pb_session->destroy_data; + + l_free(pb_session); + pb_session = NULL; + + if (destroy_cb) + destroy_cb(destroy_data); +} + +void pb_gatt_unreg(void *user_data, pb_gatt_destroy_cb destroy_cb, + void *destroy_data) +{ + if (!pb_session || pb_session->user_data != user_data) + return; + + pb_session->destroy_cb = destroy_cb; + pb_session->destroy_data = destroy_data; + gatt_service_destroy(pb_session->gatt_service, gatt_destroy_finished, + pb_session); +} diff --git a/mesh/pb-gatt.h b/mesh/pb-gatt.h new file mode 100644 index 000000000000..49d95974c4ae --- /dev/null +++ b/mesh/pb-gatt.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <stdbool.h> +#include <stdint.h> + +typedef void (*pb_gatt_destroy_cb)(void *user_data); + +bool pb_gatt_reg(mesh_prov_open_func_t open_cb, mesh_prov_close_func_t close_cb, + mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb, + const uint8_t *uuid, uint16_t oob_info, void *user_data); +void pb_gatt_unreg(void *user_data, pb_gatt_destroy_cb destroy_cb, + void *destroy_data); diff --git a/mesh/prov-acceptor.c b/mesh/prov-acceptor.c index 78304515ed16..f483c330953b 100644 --- a/mesh/prov-acceptor.c +++ b/mesh/prov-acceptor.c @@ -26,6 +26,7 @@ #include "mesh/provision.h" #include "mesh/remprv.h" #include "mesh/pb-adv.h" +#include "mesh/pb-gatt.h" #include "mesh/mesh.h" #include "mesh/agent.h" @@ -99,6 +100,7 @@ static void acceptor_free(void) mesh_send_cancel(&pkt_filter, sizeof(pkt_filter)); pb_adv_unreg(prov); + pb_gatt_unreg(prov, NULL, NULL); l_free(prov); prov = NULL; @@ -801,6 +803,10 @@ bool acceptor_start(uint8_t num_ele, uint8_t *uuid, /* Always register for PB-ADV */ result = pb_adv_reg(false, acp_prov_open, acp_prov_close, acp_prov_rx, acp_prov_ack, uuid, prov); + + result = pb_gatt_reg(acp_prov_open, acp_prov_close, + acp_prov_rx, acp_prov_ack, uuid, + caps->oob_info, prov); } else { /* Run Device Key Refresh Procedure */ result = register_nppi_acceptor(acp_prov_open, acp_prov_close, -- 2.43.0