/* * Copyright (c) 2022 Codecoup * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../bluetooth/host/hci_core.h" #include "../bluetooth/host/settings.h" #include "audio_internal.h" #include "common/bt_str.h" #include "has_internal.h" LOG_MODULE_REGISTER(bt_has, CONFIG_BT_HAS_LOG_LEVEL); /* The service allows operations with paired devices only. * The number of clients is set to maximum number of simultaneous connections to paired devices. */ #define MAX_INSTS MIN(CONFIG_BT_MAX_CONN, CONFIG_BT_MAX_PAIRED) #define BITS_CHANGED(_new_value, _old_value) ((_new_value) ^ (_old_value)) #define FEATURE_DEVICE_TYPE_UNCHANGED(_new_value) \ !BITS_CHANGED(_new_value, (has.features & BT_HAS_FEAT_HEARING_AID_TYPE_MASK)) #define FEATURE_SYNC_SUPPORT_UNCHANGED(_new_value) \ !BITS_CHANGED(_new_value, ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0 ? 1 : 0)) #define FEATURE_IND_PRESETS_UNCHANGED(_new_value) \ !BITS_CHANGED(_new_value, ((has.features & BT_HAS_FEAT_INDEPENDENT_PRESETS) != 0 ? 1 : 0)) #define BONDED_CLIENT_INIT_FLAGS \ (BIT(FLAG_ACTIVE_INDEX_CHANGED) | BIT(FLAG_NOTIFY_PRESET_LIST) | BIT(FLAG_FEATURES_CHANGED)) static struct bt_has has; #if defined(CONFIG_BT_HAS_ACTIVE_PRESET_INDEX) static void active_preset_index_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("attr %p value 0x%04x", attr, value); } #endif /* CONFIG_BT_HAS_ACTIVE_PRESET_INDEX */ #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) struct has_client; static int read_preset_response(struct has_client *client); static int preset_list_changed(struct has_client *client); static int preset_list_changed_generic_update_tail(struct has_client *client); static int preset_list_changed_record_deleted_last(struct has_client *client); static ssize_t write_control_point(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *data, uint16_t len, uint16_t offset, uint8_t flags); static void preset_cp_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("attr %p value 0x%04x", attr, value); } static ssize_t read_active_preset_index(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { uint8_t active_index; LOG_DBG("conn %p attr %p offset %d", (void *)conn, attr, offset); if (offset > sizeof(active_index)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } active_index = bt_has_preset_active_get(); return bt_gatt_attr_read(conn, attr, buf, len, offset, &active_index, sizeof(active_index)); } #endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ #if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) static void features_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("attr %p value 0x%04x", attr, value); } #endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ static ssize_t read_features(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { LOG_DBG("conn %p attr %p offset %d", (void *)conn, attr, offset); if (offset > sizeof(has.features)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } return bt_gatt_attr_read(conn, attr, buf, len, offset, &has.features, sizeof(has.features)); } #if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) #define BT_HAS_CHR_FEATURES \ BT_AUDIO_CHRC(BT_UUID_HAS_HEARING_AID_FEATURES, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, \ read_features, NULL, NULL), \ BT_AUDIO_CCC(features_cfg_changed), #else #define BT_HAS_CHR_FEATURES \ BT_AUDIO_CHRC(BT_UUID_HAS_HEARING_AID_FEATURES, \ BT_GATT_CHRC_READ, \ BT_GATT_PERM_READ_ENCRYPT, \ read_features, NULL, NULL), #endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) #if defined(CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE) #define BT_HAS_CHR_PRESET_CONTROL_POINT \ BT_AUDIO_CHRC(BT_UUID_HAS_PRESET_CONTROL_POINT, \ BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_WRITE_ENCRYPT, \ NULL, write_control_point, NULL), \ BT_AUDIO_CCC(preset_cp_cfg_changed), #else #define BT_HAS_CHR_PRESET_CONTROL_POINT \ BT_AUDIO_CHRC(BT_UUID_HAS_PRESET_CONTROL_POINT, \ BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE, \ BT_GATT_PERM_WRITE_ENCRYPT, \ NULL, write_control_point, NULL), \ BT_AUDIO_CCC(preset_cp_cfg_changed), #endif /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */ #else #define BT_HAS_CHR_PRESET_CONTROL_POINT #endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ #if defined(CONFIG_BT_HAS_ACTIVE_PRESET_INDEX) #define BT_HAS_CHR_ACTIVE_PRESET_INDEX \ BT_AUDIO_CHRC(BT_UUID_HAS_ACTIVE_PRESET_INDEX, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, \ read_active_preset_index, NULL, NULL), \ BT_AUDIO_CCC(active_preset_index_cfg_changed) #else #define BT_HAS_CHR_ACTIVE_PRESET_INDEX #endif /* CONFIG_BT_HAS_ACTIVE_PRESET_INDEX */ /* Hearing Access Service GATT Attributes */ static struct bt_gatt_attr has_attrs[] = { BT_GATT_PRIMARY_SERVICE(BT_UUID_HAS), BT_HAS_CHR_FEATURES BT_HAS_CHR_PRESET_CONTROL_POINT BT_HAS_CHR_ACTIVE_PRESET_INDEX }; static struct bt_gatt_service has_svc; static struct bt_gatt_attr *hearing_aid_features_attr; static struct bt_gatt_attr *preset_control_point_attr; static struct bt_gatt_attr *active_preset_index_attr; #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) || defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) static void notify_work_handler(struct k_work *work); enum flag_internal { FLAG_ACTIVE_INDEX_CHANGED, FLAG_PENDING_READ_PRESET_RESPONSE, FLAG_NOTIFY_PRESET_LIST, FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL, FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST, FLAG_FEATURES_CHANGED, FLAG_NUM, }; /* Stored client context */ static struct client_context { bt_addr_le_t addr; /* Pending notification flags */ ATOMIC_DEFINE(flags, FLAG_NUM); /* Last notified preset index */ uint8_t last_preset_index_known; } contexts[CONFIG_BT_MAX_PAIRED]; /* Connected client instance */ static struct has_client { struct bt_conn *conn; #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) union { struct bt_gatt_indicate_params ind; #if defined(CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE) struct bt_gatt_notify_params ntf; #endif /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */ } params; uint8_t preset_changed_index_next; struct bt_has_cp_read_presets_req read_presets_req; #endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ struct k_work_delayable notify_work; struct client_context *context; } has_client_list[MAX_INSTS]; static struct client_context *context_find(const bt_addr_le_t *addr) { __ASSERT_NO_MSG(addr != NULL); for (size_t i = 0; i < ARRAY_SIZE(contexts); i++) { if (bt_addr_le_eq(&contexts[i].addr, addr)) { return &contexts[i]; } } return NULL; } static struct client_context *context_alloc(const bt_addr_le_t *addr) { struct client_context *context; __ASSERT_NO_MSG(addr != NULL); /* Free contexts has BT_ADDR_LE_ANY as the address */ context = context_find(BT_ADDR_LE_ANY); if (context == NULL) { return NULL; } memset(context, 0, sizeof(*context)); bt_addr_le_copy(&context->addr, addr); return context; } static void context_free(struct client_context *context) { bt_addr_le_copy(&context->addr, BT_ADDR_LE_ANY); } static void client_free(struct has_client *client) { struct bt_conn_info info = { 0 }; int err; #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) || defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) (void)k_work_cancel_delayable(&client->notify_work); #endif /* CONFIG_BT_HAS_PRESET_SUPPORT || CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ err = bt_conn_get_info(client->conn, &info); __ASSERT_NO_MSG(err == 0); if (client->context != NULL && !bt_addr_le_is_bonded(info.id, info.le.dst)) { /* Free stored context of non-bonded client */ context_free(client->context); client->context = NULL; } bt_conn_unref(client->conn); client->conn = NULL; } static struct has_client *client_alloc(struct bt_conn *conn) { struct bt_conn_info info = { 0 }; struct has_client *client = NULL; int err; for (size_t i = 0; i < ARRAY_SIZE(has_client_list); i++) { if (conn == has_client_list[i].conn) { return &has_client_list[i]; } /* first free slot */ if (!client && has_client_list[i].conn == NULL) { client = &has_client_list[i]; } } __ASSERT(client, "failed to get client for conn %p", (void *)conn); memset(client, 0, sizeof(*client)); client->conn = bt_conn_ref(conn); #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) || defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) k_work_init_delayable(&client->notify_work, notify_work_handler); #endif /* CONFIG_BT_HAS_PRESET_SUPPORT || CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ err = bt_conn_get_info(conn, &info); if (err != 0) { LOG_DBG("Could not get conn info: %d", err); return NULL; } client->context = context_find(info.le.dst); if (client->context == NULL) { client->context = context_alloc(info.le.dst); if (client->context == NULL) { LOG_ERR("Failed to allocate client_context for %s", bt_addr_le_str(info.le.dst)); client_free(client); return NULL; } LOG_DBG("New client_context for %s", bt_addr_le_str(info.le.dst)); } return client; } static struct has_client *client_find_by_conn(struct bt_conn *conn) { for (size_t i = 0; i < ARRAY_SIZE(has_client_list); i++) { if (conn == has_client_list[i].conn) { return &has_client_list[i]; } } return NULL; } static void notify_work_reschedule(struct has_client *client, k_timeout_t delay) { int err; __ASSERT(client->conn, "Not connected"); if (k_work_delayable_remaining_get(&client->notify_work) > 0) { return; } err = k_work_reschedule(&client->notify_work, delay); if (err < 0) { LOG_ERR("Failed to reschedule notification work err %d", err); } } static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) { struct has_client *client; struct bt_conn_info info; int ret; LOG_DBG("conn %p level %d err %d", (void *)conn, level, err); if (err != BT_SECURITY_ERR_SUCCESS) { return; } client = client_alloc(conn); if (unlikely(!client)) { LOG_ERR("Failed to allocate client"); return; } ret = bt_conn_get_info(client->conn, &info); if (ret < 0) { LOG_ERR("bt_conn_get_info err %d", ret); return; } if (!bt_addr_le_is_bonded(info.id, info.le.dst)) { return; } if (atomic_get(client->context->flags) != 0) { notify_work_reschedule(client, K_NO_WAIT); } } static void disconnected(struct bt_conn *conn, uint8_t reason) { struct has_client *client; LOG_DBG("conn %p reason %d", (void *)conn, reason); client = client_find_by_conn(conn); if (client) { client_free(client); } } static void identity_resolved(struct bt_conn *conn, const bt_addr_le_t *rpa, const bt_addr_le_t *identity) { struct has_client *client; LOG_DBG("conn %p %s -> %s", (void *)conn, bt_addr_le_str(rpa), bt_addr_le_str(identity)); client = client_find_by_conn(conn); if (client == NULL) { return; } bt_addr_le_copy(&client->context->addr, identity); } BT_CONN_CB_DEFINE(conn_cb) = { .disconnected = disconnected, .security_changed = security_changed, .identity_resolved = identity_resolved, }; static void notify_work_handler(struct k_work *work) { struct k_work_delayable *dwork = k_work_delayable_from_work(work); struct has_client *client = CONTAINER_OF(dwork, struct has_client, notify_work); int err; if (IS_ENABLED(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) && atomic_test_and_clear_bit(client->context->flags, FLAG_FEATURES_CHANGED) && bt_gatt_is_subscribed(client->conn, hearing_aid_features_attr, BT_GATT_CCC_NOTIFY)) { err = bt_gatt_notify(client->conn, hearing_aid_features_attr, &has.features, sizeof(has.features)); if (err == -ENOMEM) { atomic_set_bit(client->context->flags, FLAG_FEATURES_CHANGED); notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); } else if (err < 0) { LOG_ERR("Notify features err %d", err); } } #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) if (atomic_test_and_clear_bit(client->context->flags, FLAG_PENDING_READ_PRESET_RESPONSE)) { err = read_preset_response(client); if (err == -ENOMEM) { atomic_set_bit(client->context->flags, FLAG_PENDING_READ_PRESET_RESPONSE); notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); } else if (err < 0) { LOG_ERR("Notify read preset response err %d", err); } } else if (atomic_test_and_clear_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST)) { err = preset_list_changed(client); if (err == -ENOMEM) { atomic_set_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST); notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); } else if (err < 0) { LOG_ERR("Notify preset list changed err %d", err); } } else if (atomic_test_and_clear_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL)) { err = preset_list_changed_generic_update_tail(client); if (err == -ENOMEM) { atomic_set_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL); notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); } else if (err < 0) { LOG_ERR("Notify preset list changed generic update tail err %d", err); } } else if (atomic_test_and_clear_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST)) { err = preset_list_changed_record_deleted_last(client); if (err == -ENOMEM) { atomic_set_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST); notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); } else if (err < 0) { LOG_ERR("Notify preset list changed recoed deleted last err %d", err); } } #endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ if (IS_ENABLED(CONFIG_BT_HAS_PRESET_SUPPORT) && atomic_test_and_clear_bit(client->context->flags, FLAG_ACTIVE_INDEX_CHANGED) && bt_gatt_is_subscribed(client->conn, active_preset_index_attr, BT_GATT_CCC_NOTIFY)) { uint8_t active_index; active_index = bt_has_preset_active_get(); err = bt_gatt_notify(client->conn, active_preset_index_attr, &active_index, sizeof(active_index)); if (err == -ENOMEM) { atomic_set_bit(client->context->flags, FLAG_ACTIVE_INDEX_CHANGED); notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); } else if (err < 0) { LOG_ERR("Notify active index err %d", err); } } } static void notify(struct has_client *client, enum flag_internal flag) { if (client != NULL) { atomic_set_bit(client->context->flags, flag); notify_work_reschedule(client, K_NO_WAIT); return; } /* Mark notification to be sent to all clients */ for (size_t i = 0U; i < ARRAY_SIZE(contexts); i++) { atomic_set_bit(contexts[i].flags, flag); } for (size_t i = 0U; i < ARRAY_SIZE(has_client_list); i++) { client = &has_client_list[i]; if (client->conn == NULL) { continue; } notify_work_reschedule(client, K_NO_WAIT); } } static void bond_deleted_cb(uint8_t id, const bt_addr_le_t *addr) { struct client_context *context; context = context_find(addr); if (context != NULL) { context_free(context); } if (IS_ENABLED(CONFIG_BT_SETTINGS)) { bt_settings_delete("has", 0, addr); } } static struct bt_conn_auth_info_cb auth_info_cb = { .bond_deleted = bond_deleted_cb, }; #endif /* CONFIG_BT_HAS_PRESET_SUPPORT || CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) static struct has_preset *active_preset; /* HAS internal preset representation */ static struct has_preset { uint8_t index; enum bt_has_properties properties; #if defined(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC) char name[BT_HAS_PRESET_NAME_MAX + 1]; /* +1 byte for NULL-terminator */ #else const char *name; #endif /* CONFIG_BT_HAS_PRESET_NAME_DYNAMIC */ const struct bt_has_preset_ops *ops; sys_snode_t node; } preset_pool[CONFIG_BT_HAS_PRESET_COUNT]; static sys_slist_t preset_list = SYS_SLIST_STATIC_INIT(&preset_list); static sys_slist_t preset_free_list = SYS_SLIST_STATIC_INIT(&preset_free_list); typedef uint8_t (*preset_func_t)(const struct has_preset *preset, void *user_data); static void preset_foreach(uint8_t start_index, uint8_t end_index, preset_func_t func, void *user_data) { struct has_preset *preset, *tmp; SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&preset_list, preset, tmp, node) { if (preset->index < start_index) { continue; } if (preset->index > end_index) { return; } if (func(preset, user_data) == BT_HAS_PRESET_ITER_STOP) { return; } } } static uint8_t preset_found(const struct has_preset *preset, void *user_data) { const struct has_preset **found = user_data; *found = preset; return BT_HAS_PRESET_ITER_STOP; } static void preset_insert(struct has_preset *preset) { struct has_preset *tmp, *prev = NULL; SYS_SLIST_FOR_EACH_CONTAINER(&preset_list, tmp, node) { if (tmp->index > preset->index) { if (prev) { sys_slist_insert(&preset_list, &prev->node, &preset->node); } else { sys_slist_prepend(&preset_list, &preset->node); } return; } prev = tmp; } sys_slist_append(&preset_list, &preset->node); } static struct has_preset *preset_alloc(uint8_t index, enum bt_has_properties properties, const char *name, const struct bt_has_preset_ops *ops) { struct has_preset *preset; sys_snode_t *node; node = sys_slist_get(&preset_free_list); if (node == NULL) { return NULL; } preset = CONTAINER_OF(node, struct has_preset, node); preset->index = index; preset->properties = properties; #if defined(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC) utf8_lcpy(preset->name, name, ARRAY_SIZE(preset->name)); #else preset->name = name; #endif /* CONFIG_BT_HAS_PRESET_NAME_DYNAMIC */ preset->ops = ops; preset_insert(preset); return preset; } static void preset_free(struct has_preset *preset) { bool removed; removed = sys_slist_find_and_remove(&preset_list, &preset->node); if (removed) { sys_slist_append(&preset_free_list, &preset->node); } } static struct has_preset *preset_get_head(void) { struct has_preset *next; return SYS_SLIST_PEEK_HEAD_CONTAINER(&preset_list, next, node); } static struct has_preset *preset_get_tail(void) { struct has_preset *prev; return SYS_SLIST_PEEK_TAIL_CONTAINER(&preset_list, prev, node); } static struct has_preset *preset_get_prev(const struct has_preset *preset) { struct has_preset *prev; SYS_SLIST_FOR_EACH_CONTAINER(&preset_list, prev, node) { if (SYS_SLIST_PEEK_NEXT_CONTAINER(prev, node) == preset) { return prev; } } prev = preset_get_tail(); if (prev == preset) { return NULL; } return prev; } static struct has_preset *preset_lookup_index(uint8_t index) { struct has_preset *preset; SYS_SLIST_FOR_EACH_CONTAINER(&preset_list, preset, node) { if (preset->index == index) { return preset; } } return NULL; } static struct has_preset *preset_get_next(struct has_preset *preset) { struct has_preset *next; next = SYS_SLIST_PEEK_NEXT_CONTAINER(preset, node); if (next == NULL) { next = preset_get_head(); if (next == preset) { return NULL; } } return next; } static uint8_t preset_get_prev_index(const struct has_preset *preset) { const struct has_preset *prev; prev = preset_get_prev(preset); if (prev == NULL || prev->index >= preset->index) { return BT_HAS_PRESET_INDEX_NONE; } return prev->index; } static void control_point_ntf_complete(struct bt_conn *conn, void *user_data) { struct has_client *client = client_find_by_conn(conn); LOG_DBG("conn %p", (void *)conn); /* Resubmit if needed */ if (client != NULL && atomic_get(client->context->flags) != 0) { notify_work_reschedule(client, K_NO_WAIT); } } static void control_point_ind_complete(struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err) { if (err) { /* TODO: Handle error somehow */ LOG_ERR("conn %p err 0x%02x", (void *)conn, err); } control_point_ntf_complete(conn, NULL); } static int control_point_send(struct has_client *client, struct net_buf_simple *buf) { const uint16_t mtu_size = bt_gatt_get_mtu(client->conn); /* PDU structure is [Opcode (1)] [Handle (2)] [...] */ const uint16_t pdu_size = 3 + buf->len; if (mtu_size < pdu_size) { LOG_WRN("Sending truncated control point PDU %d < %d", mtu_size, pdu_size); buf->len -= (pdu_size - mtu_size); } #if defined(CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE) if (bt_eatt_count(client->conn) > 0 && bt_gatt_is_subscribed(client->conn, preset_control_point_attr, BT_GATT_CCC_NOTIFY)) { memset(&client->params.ntf, 0, sizeof(client->params.ntf)); client->params.ntf.attr = preset_control_point_attr; client->params.ntf.func = control_point_ntf_complete; client->params.ntf.data = buf->data; client->params.ntf.len = buf->len; return bt_gatt_notify_cb(client->conn, &client->params.ntf); } #endif /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */ if (bt_gatt_is_subscribed(client->conn, preset_control_point_attr, BT_GATT_CCC_INDICATE)) { memset(&client->params.ind, 0, sizeof(client->params.ind)); client->params.ind.attr = preset_control_point_attr; client->params.ind.func = control_point_ind_complete; client->params.ind.destroy = NULL; client->params.ind.data = buf->data; client->params.ind.len = buf->len; return bt_gatt_indicate(client->conn, &client->params.ind); } return -ECANCELED; } static int control_point_send_all(struct net_buf_simple *buf) { int result = 0; for (size_t i = 0U; i < ARRAY_SIZE(contexts); i++) { struct client_context *context = &contexts[i]; struct has_client *client = NULL; int err; for (size_t j = 0U; j < ARRAY_SIZE(has_client_list); j++) { if (has_client_list[j].context == context) { client = &has_client_list[j]; break; } } if (client == NULL || client->conn == NULL) { /* Mark preset changed operation as pending */ atomic_set_bit(context->flags, FLAG_NOTIFY_PRESET_LIST); continue; } if (!bt_gatt_is_subscribed(client->conn, preset_control_point_attr, BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE)) { continue; } err = control_point_send(client, buf); if (err) { result = err; /* continue anyway */ } } return result; } static int bt_has_cp_read_preset_rsp(struct has_client *client, const struct has_preset *preset, bool is_last) { struct bt_has_cp_hdr *hdr; struct bt_has_cp_read_preset_rsp *rsp; NET_BUF_SIMPLE_DEFINE(buf, sizeof(*hdr) + sizeof(*rsp) + BT_HAS_PRESET_NAME_MAX); LOG_DBG("conn %p index 0x%02x prop 0x%02x %s is_last 0x%02x", (void *)client->conn, preset->index, preset->properties, preset->name, is_last); hdr = net_buf_simple_add(&buf, sizeof(*hdr)); hdr->opcode = BT_HAS_OP_READ_PRESET_RSP; rsp = net_buf_simple_add(&buf, sizeof(*rsp)); rsp->is_last = is_last ? 0x01 : 0x00; rsp->index = preset->index; rsp->properties = preset->properties; net_buf_simple_add_mem(&buf, preset->name, strlen(preset->name)); return control_point_send(client, &buf); } static void preset_changed_prepare(struct net_buf_simple *buf, uint8_t change_id, uint8_t is_last) { struct bt_has_cp_hdr *hdr; struct bt_has_cp_preset_changed *preset_changed; hdr = net_buf_simple_add(buf, sizeof(*hdr)); hdr->opcode = BT_HAS_OP_PRESET_CHANGED; preset_changed = net_buf_simple_add(buf, sizeof(*preset_changed)); preset_changed->change_id = change_id; preset_changed->is_last = is_last; } static int bt_has_cp_generic_update(struct has_client *client, uint8_t prev_index, uint8_t index, uint8_t properties, const char *name, uint8_t is_last) { struct bt_has_cp_generic_update *generic_update; NET_BUF_SIMPLE_DEFINE(buf, sizeof(struct bt_has_cp_hdr) + sizeof(struct bt_has_cp_preset_changed) + sizeof(struct bt_has_cp_generic_update) + BT_HAS_PRESET_NAME_MAX); LOG_DBG("client %p prev_index 0x%02x index 0x%02x prop 0x%02x %s is_last %d", client, prev_index, index, properties, name, is_last); preset_changed_prepare(&buf, BT_HAS_CHANGE_ID_GENERIC_UPDATE, is_last); generic_update = net_buf_simple_add(&buf, sizeof(*generic_update)); generic_update->prev_index = prev_index; generic_update->index = index; generic_update->properties = properties; net_buf_simple_add_mem(&buf, name, strlen(name)); if (client) { return control_point_send(client, &buf); } else { return control_point_send_all(&buf); } } #if defined(CONFIG_BT_SETTINGS) struct client_context_store { /* Last notified preset index */ uint8_t last_preset_index_known; } __packed; static int settings_set_cb(const char *name, size_t len_rd, settings_read_cb read_cb, void *cb_arg) { struct client_context_store store; struct client_context *context; bt_addr_le_t addr; ssize_t len; int err; if (!name) { LOG_ERR("Insufficient number of arguments"); return -EINVAL; } err = bt_settings_decode_key(name, &addr); if (err) { LOG_ERR("Unable to decode address %s", name); return -EINVAL; } context = context_find(&addr); if (context == NULL) { /* Find and initialize a free entry */ context = context_alloc(&addr); if (context == NULL) { LOG_ERR("Failed to allocate client_context for %s", bt_addr_le_str(&addr)); return -ENOMEM; } } if (len_rd) { len = read_cb(cb_arg, &store, sizeof(store)); if (len < 0) { LOG_ERR("Failed to decode value (err %zd)", len); return len; } context->last_preset_index_known = store.last_preset_index_known; } else { context->last_preset_index_known = 0x00; } /* Notify all the characteristics values after reboot */ atomic_set(context->flags, BONDED_CLIENT_INIT_FLAGS); return 0; } static BT_SETTINGS_DEFINE(has, "has", settings_set_cb, NULL); static void store_client_context(struct client_context *context) { struct client_context_store store = { .last_preset_index_known = context->last_preset_index_known, }; int err; LOG_DBG("%s last_preset_index_known 0x%02x", bt_addr_le_str(&context->addr), store.last_preset_index_known); err = bt_settings_store("has", 0, &context->addr, &store, sizeof(store)); if (err != 0) { LOG_ERR("Failed to store err %d", err); } } #else #define store_client_context(...) #endif /* CONFIG_BT_SETTINGS */ static void update_last_preset_index_known(struct has_client *client, uint8_t index) { if (client != NULL && client->context != NULL && client->context->last_preset_index_known != index) { client->context->last_preset_index_known = index; store_client_context(client->context); return; } for (size_t i = 0; i < ARRAY_SIZE(has_client_list); i++) { client = &has_client_list[i]; /* For each connected client */ if (client->conn != NULL && client->context != NULL && client->context->last_preset_index_known != index) { client->context->last_preset_index_known = index; store_client_context(client->context); } } } static int read_preset_response(struct has_client *client) { const struct has_preset *preset = NULL; bool is_last = true; int err; __ASSERT_NO_MSG(client != NULL); preset_foreach(client->read_presets_req.start_index, BT_HAS_PRESET_INDEX_LAST, preset_found, &preset); if (unlikely(preset == NULL)) { return bt_has_cp_read_preset_rsp(client, NULL, BT_HAS_IS_LAST); } if (client->read_presets_req.num_presets > 1) { const struct has_preset *next = NULL; preset_foreach(preset->index + 1, BT_HAS_PRESET_INDEX_LAST, preset_found, &next); is_last = next == NULL; } err = bt_has_cp_read_preset_rsp(client, preset, is_last); if (err != 0) { return err; } if (preset->index > client->context->last_preset_index_known) { update_last_preset_index_known(client, preset->index); } if (!is_last) { client->read_presets_req.start_index = preset->index + 1; client->read_presets_req.num_presets--; atomic_set_bit(client->context->flags, FLAG_PENDING_READ_PRESET_RESPONSE); notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); } return 0; } static int bt_has_cp_preset_record_deleted(struct has_client *client, uint8_t index) { NET_BUF_SIMPLE_DEFINE(buf, sizeof(struct bt_has_cp_hdr) + sizeof(struct bt_has_cp_preset_changed) + sizeof(uint8_t)); LOG_DBG("client %p index 0x%02x", client, index); preset_changed_prepare(&buf, BT_HAS_CHANGE_ID_PRESET_DELETED, BT_HAS_IS_LAST); net_buf_simple_add_u8(&buf, index); if (client != NULL) { return control_point_send(client, &buf); } else { return control_point_send_all(&buf); } } /* Generic Update the last (already deleted) preset */ static int preset_list_changed_generic_update_tail(struct has_client *client) { const struct has_preset *prev; struct has_preset last = { /* The index value of the last preset the client knew about. */ .index = client->context->last_preset_index_known, /* As the properties of deleted preset is not available anymore, we set this value * to 0x00 meaning the preset is unavailable and non-writable which is actually true */ .properties = BT_HAS_PROP_NONE, /* As the name of deleted preset are not available anymore, we set this value * to the value what is compliant with specification. * As per HAS_v1.0 the Name is 1-40 octet value. */ .name = "N/A", }; int err; prev = preset_get_tail(); err = bt_has_cp_generic_update(client, prev ? prev->index : BT_HAS_PRESET_INDEX_NONE, last.index, last.properties, last.name, false); if (err != 0) { return err; } return 0; } static int preset_list_changed_record_deleted_last(struct has_client *client) { const struct has_preset *last; int err; err = bt_has_cp_preset_record_deleted(client, client->context->last_preset_index_known); if (err != 0) { return err; } last = preset_get_tail(); update_last_preset_index_known(client, last ? last->index : BT_HAS_PRESET_INDEX_NONE); return 0; } static int preset_list_changed(struct has_client *client) { const struct has_preset *preset = NULL; const struct has_preset *next = NULL; bool is_last = true; int err; if (sys_slist_is_empty(&preset_list)) { /* The preset list is empty. We need to indicate deletion of all presets */ atomic_set_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL); atomic_set_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST); notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); return 0; } preset_foreach(client->preset_changed_index_next, BT_HAS_PRESET_INDEX_LAST, preset_found, &preset); if (preset == NULL) { return 0; } preset_foreach(preset->index + 1, BT_HAS_PRESET_INDEX_LAST, preset_found, &next); /* It is last Preset Changed notification if there are no presets left to notify and the * currently notified preset have the highest index known to the client. */ is_last = next == NULL && preset->index >= client->context->last_preset_index_known; err = bt_has_cp_generic_update(client, preset_get_prev_index(preset), preset->index, preset->properties, preset->name, is_last); if (err != 0) { return err; } if (is_last) { client->preset_changed_index_next = 0; /* It's the last preset notified, so update the highest index known to the client */ update_last_preset_index_known(client, preset->index); return 0; } if (next == NULL) { /* If we end up here, the last preset known to the client has been removed. * As we do not hold the information about the deleted presets, we need to use * Generic Update procedure to: * 1. Notify the presets that have been removed in range * (PrevIndex = current_preset_last, Index=previous_preset_last) * 2. Notify deletion of preset Index=previous_preset_last. */ atomic_set_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL); atomic_set_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST); } else { client->preset_changed_index_next = preset->index + 1; atomic_set_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST); } notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); return 0; } static uint8_t handle_read_preset_req(struct bt_conn *conn, struct net_buf_simple *buf) { const struct bt_has_cp_read_presets_req *req; const struct has_preset *preset = NULL; struct has_client *client; if (buf->len < sizeof(*req)) { return BT_HAS_ERR_INVALID_PARAM_LEN; } /* As per HAS_d1.0r00 Client Characteristic Configuration Descriptor Improperly Configured * shall be returned if client writes Read Presets Request but is not registered for * indications. */ if (!bt_gatt_is_subscribed(conn, preset_control_point_attr, BT_GATT_CCC_INDICATE)) { return BT_ATT_ERR_CCC_IMPROPER_CONF; } client = client_find_by_conn(conn); if (client == NULL) { return BT_ATT_ERR_UNLIKELY; } req = net_buf_simple_pull_mem(buf, sizeof(*req)); LOG_DBG("start_index %d num_presets %d", req->start_index, req->num_presets); /* Abort if there is no preset in requested index range */ preset_foreach(req->start_index, BT_HAS_PRESET_INDEX_LAST, preset_found, &preset); if (preset == NULL) { return BT_ATT_ERR_OUT_OF_RANGE; } /* Reject if already in progress */ if (atomic_test_bit(client->context->flags, FLAG_PENDING_READ_PRESET_RESPONSE)) { return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; } /* Store the request */ client->read_presets_req.start_index = req->start_index; client->read_presets_req.num_presets = req->num_presets; notify(client, FLAG_PENDING_READ_PRESET_RESPONSE); return 0; } static int set_preset_name(uint8_t index, const char *name, size_t len) { struct has_preset *preset = NULL; LOG_DBG("index %d name_len %zu", index, len); if (len < BT_HAS_PRESET_NAME_MIN || len > BT_HAS_PRESET_NAME_MAX) { return -EINVAL; } /* Abort if there is no preset in requested index range */ preset_foreach(index, BT_HAS_PRESET_INDEX_LAST, preset_found, &preset); if (preset == NULL) { return -ENOENT; } if (!(preset->properties & BT_HAS_PROP_WRITABLE)) { return -EPERM; } IF_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC, ( __ASSERT(len < ARRAY_SIZE(preset->name), "No space for name"); (void)memcpy(preset->name, name, len); /* NULL-terminate string */ preset->name[len] = '\0'; /* Properly truncate a NULL-terminated UTF-8 string */ utf8_trunc(preset->name); )); if (preset->ops->name_changed) { preset->ops->name_changed(index, preset->name); } return bt_has_cp_generic_update(NULL, preset_get_prev_index(preset), preset->index, preset->properties, preset->name, BT_HAS_IS_LAST); } static uint8_t handle_write_preset_name(struct bt_conn *conn, struct net_buf_simple *buf) { const struct bt_has_cp_write_preset_name *req; struct has_client *client; int err; if (buf->len < sizeof(*req)) { return BT_HAS_ERR_INVALID_PARAM_LEN; } /* As per HAS_v1.0 Client Characteristic Configuration Descriptor Improperly Configured * shall be returned if client writes Write Preset Name opcode but is not registered for * indications. */ if (!bt_gatt_is_subscribed(conn, preset_control_point_attr, BT_GATT_CCC_INDICATE)) { return BT_ATT_ERR_CCC_IMPROPER_CONF; } client = client_find_by_conn(conn); if (!client) { return BT_ATT_ERR_UNLIKELY; } req = net_buf_simple_pull_mem(buf, sizeof(*req)); err = set_preset_name(req->index, req->name, buf->len); if (err == -EINVAL) { return BT_HAS_ERR_INVALID_PARAM_LEN; } else if (err == -ENOENT) { return BT_ATT_ERR_OUT_OF_RANGE; } else if (err == -EPERM) { return BT_HAS_ERR_WRITE_NAME_NOT_ALLOWED; } else if (err) { return BT_ATT_ERR_UNLIKELY; } return BT_ATT_ERR_SUCCESS; } static void preset_set_active(struct has_preset *preset) { if (active_preset != preset) { active_preset = preset; notify(NULL, FLAG_ACTIVE_INDEX_CHANGED); } } static uint8_t preset_select(struct has_preset *preset, bool sync) { const int err = preset->ops->select(preset->index, sync); if (err == -EINPROGRESS) { /* User has to confirm once the requested preset becomes active by * calling bt_has_preset_active_set. */ return 0; } if (err == -EBUSY) { return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; } if (err) { return BT_ATT_ERR_UNLIKELY; } preset_set_active(preset); return 0; } static bool is_preset_available(const struct has_preset *preset) { return (preset->properties & BT_HAS_PROP_AVAILABLE) != 0; } static uint8_t handle_set_active_preset(struct net_buf_simple *buf, bool sync) { const struct bt_has_cp_set_active_preset *pdu; struct has_preset *preset; if (buf->len < sizeof(*pdu)) { return BT_HAS_ERR_INVALID_PARAM_LEN; } pdu = net_buf_simple_pull_mem(buf, sizeof(*pdu)); preset = preset_lookup_index(pdu->index); if (preset == NULL) { return BT_ATT_ERR_OUT_OF_RANGE; } if (!is_preset_available(preset)) { return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; } return preset_select(preset, sync); } static uint8_t handle_set_next_preset(bool sync) { struct has_preset *next, *tmp; if (active_preset == NULL) { next = preset_get_head(); } else { next = preset_get_next(active_preset); } tmp = next; do { if (next == NULL) { break; } if (is_preset_available(next)) { return preset_select(next, sync); } next = preset_get_next(next); } while (tmp != next); return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; } static uint8_t handle_set_prev_preset(bool sync) { struct has_preset *prev, *tmp; if (active_preset == NULL) { prev = preset_get_tail(); } else { prev = preset_get_prev(active_preset); } tmp = prev; do { if (prev == NULL) { break; } if (is_preset_available(prev)) { return preset_select(prev, sync); } prev = preset_get_prev(prev); } while (tmp != prev); return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; } static uint8_t handle_control_point_op(struct bt_conn *conn, struct net_buf_simple *buf) { const struct bt_has_cp_hdr *hdr; hdr = net_buf_simple_pull_mem(buf, sizeof(*hdr)); LOG_DBG("conn %p opcode %s (0x%02x)", (void *)conn, bt_has_op_str(hdr->opcode), hdr->opcode); switch (hdr->opcode) { case BT_HAS_OP_READ_PRESET_REQ: return handle_read_preset_req(conn, buf); case BT_HAS_OP_WRITE_PRESET_NAME: if (IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) { return handle_write_preset_name(conn, buf); } else { return BT_HAS_ERR_WRITE_NAME_NOT_ALLOWED; } break; case BT_HAS_OP_SET_ACTIVE_PRESET: return handle_set_active_preset(buf, false); case BT_HAS_OP_SET_NEXT_PRESET: return handle_set_next_preset(false); case BT_HAS_OP_SET_PREV_PRESET: return handle_set_prev_preset(false); case BT_HAS_OP_SET_ACTIVE_PRESET_SYNC: if ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0) { return handle_set_active_preset(buf, true); } else { return BT_HAS_ERR_PRESET_SYNC_NOT_SUPP; } case BT_HAS_OP_SET_NEXT_PRESET_SYNC: if ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0) { return handle_set_next_preset(true); } else { return BT_HAS_ERR_PRESET_SYNC_NOT_SUPP; } case BT_HAS_OP_SET_PREV_PRESET_SYNC: if ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0) { return handle_set_prev_preset(true); } else { return BT_HAS_ERR_PRESET_SYNC_NOT_SUPP; } }; return BT_HAS_ERR_INVALID_OPCODE; } static ssize_t write_control_point(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *data, uint16_t len, uint16_t offset, uint8_t flags) { struct net_buf_simple buf; uint8_t err; LOG_DBG("conn %p attr %p data %p len %d offset %d flags 0x%02x", (void *)conn, attr, data, len, offset, flags); if (offset > 0) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } if (len == 0) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } net_buf_simple_init_with_data(&buf, (void *)data, len); err = handle_control_point_op(conn, &buf); if (err) { LOG_WRN("handle_control_point_op err 0x%02x", err); return BT_GATT_ERR(err); } return len; } int bt_has_preset_register(const struct bt_has_preset_register_param *param) { struct has_preset *preset; size_t name_len; CHECKIF(param == NULL) { LOG_ERR("param is NULL"); return -EINVAL; } CHECKIF(param->index == BT_HAS_PRESET_INDEX_NONE) { LOG_ERR("param->index is invalid"); return -EINVAL; } CHECKIF(param->name == NULL) { LOG_ERR("param->name is NULL"); return -EINVAL; } name_len = strlen(param->name); CHECKIF(name_len < BT_HAS_PRESET_NAME_MIN) { LOG_ERR("param->name is too short (%zu < %u)", name_len, BT_HAS_PRESET_NAME_MIN); return -EINVAL; } CHECKIF(name_len > BT_HAS_PRESET_NAME_MAX) { LOG_WRN("param->name is too long (%zu > %u)", name_len, BT_HAS_PRESET_NAME_MAX); } CHECKIF(param->ops == NULL) { LOG_ERR("param->ops is NULL"); return -EINVAL; } CHECKIF(param->ops->select == NULL) { LOG_ERR("param->ops->select is NULL"); return -EINVAL; } preset = preset_lookup_index(param->index); if (preset != NULL) { return -EALREADY; } CHECKIF(!IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC) && (param->properties & BT_HAS_PROP_WRITABLE) > 0) { LOG_ERR("Writable presets are not supported"); return -ENOTSUP; } preset = preset_alloc(param->index, param->properties, param->name, param->ops); if (preset == NULL) { return -ENOMEM; } if (preset == preset_get_tail()) { update_last_preset_index_known(NULL, preset->index); } return bt_has_cp_generic_update(NULL, preset_get_prev_index(preset), preset->index, preset->properties, preset->name, BT_HAS_IS_LAST); } int bt_has_preset_unregister(uint8_t index) { struct has_preset *preset; int err; CHECKIF(index == BT_HAS_PRESET_INDEX_NONE) { LOG_ERR("index is invalid"); return -EINVAL; } preset = preset_lookup_index(index); if (preset == NULL) { return -ENOENT; } if (preset == active_preset) { return -EADDRINUSE; } err = bt_has_cp_preset_record_deleted(NULL, preset->index); if (err != 0) { return err; } if (preset == preset_get_tail()) { update_last_preset_index_known(NULL, preset_get_prev_index(preset)); } preset_free(preset); return 0; } static int set_preset_availability(uint8_t index, bool available) { NET_BUF_SIMPLE_DEFINE(buf, sizeof(struct bt_has_cp_hdr) + sizeof(struct bt_has_cp_preset_changed) + sizeof(uint8_t)); struct has_preset *preset; uint8_t change_id; CHECKIF(index == BT_HAS_PRESET_INDEX_NONE) { LOG_ERR("index is invalid"); return -EINVAL; } preset = preset_lookup_index(index); if (preset == NULL) { return -ENOENT; } if (is_preset_available(preset) == available) { /* availability not changed */ return 0; } preset->properties ^= BT_HAS_PROP_AVAILABLE; if (is_preset_available(preset)) { change_id = BT_HAS_CHANGE_ID_PRESET_AVAILABLE; } else { change_id = BT_HAS_CHANGE_ID_PRESET_UNAVAILABLE; } preset_changed_prepare(&buf, change_id, BT_HAS_IS_LAST); net_buf_simple_add_u8(&buf, preset->index); return control_point_send_all(&buf); } int bt_has_preset_available(uint8_t index) { return set_preset_availability(index, true); } int bt_has_preset_unavailable(uint8_t index) { return set_preset_availability(index, false); } struct bt_has_preset_foreach_data { bt_has_preset_func_t func; void *user_data; }; static uint8_t bt_has_preset_foreach_func(const struct has_preset *preset, void *user_data) { const struct bt_has_preset_foreach_data *data = user_data; return data->func(preset->index, preset->properties, preset->name, data->user_data); } void bt_has_preset_foreach(uint8_t index, bt_has_preset_func_t func, void *user_data) { uint8_t start_index, end_index; struct bt_has_preset_foreach_data data = { .func = func, .user_data = user_data, }; if (index == BT_HAS_PRESET_INDEX_NONE) { start_index = BT_HAS_PRESET_INDEX_FIRST; end_index = BT_HAS_PRESET_INDEX_LAST; } else { start_index = end_index = index; } preset_foreach(start_index, end_index, bt_has_preset_foreach_func, &data); } int bt_has_preset_active_set(uint8_t index) { struct has_preset *preset; if (index == BT_HAS_PRESET_INDEX_NONE) { preset_set_active(NULL); return 0; } preset = preset_lookup_index(index); if (preset == NULL) { return -ENOENT; } if (!is_preset_available(preset)) { return -EINVAL; } preset_set_active(preset); return 0; } uint8_t bt_has_preset_active_get(void) { if (active_preset == NULL) { return BT_HAS_PRESET_INDEX_NONE; } return active_preset->index; } int bt_has_preset_name_change(uint8_t index, const char *name) { CHECKIF(name == NULL) { return -EINVAL; } if (IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) { return set_preset_name(index, name, strlen(name)); } else { return -EOPNOTSUPP; } } #endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ static int has_features_register(const struct bt_has_features_param *features) { /* Initialize the supported features characteristic value */ has.features = features->type; if (IS_ENABLED(CONFIG_BT_HAS_PRESET_SUPPORT)) { has.features |= BT_HAS_FEAT_DYNAMIC_PRESETS; if (features->preset_sync_support) { if (features->type != BT_HAS_HEARING_AID_TYPE_BINAURAL) { LOG_DBG("Preset sync support only available " "for binaural hearing aid type"); return -EINVAL; } has.features |= BT_HAS_FEAT_PRESET_SYNC_SUPP; } if (features->independent_presets) { if (features->type != BT_HAS_HEARING_AID_TYPE_BINAURAL) { LOG_DBG("Independent presets only available " "for binaural hearing aid type"); return -EINVAL; } has.features |= BT_HAS_FEAT_INDEPENDENT_PRESETS; } } if (IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) { has.features |= BT_HAS_FEAT_WRITABLE_PRESETS_SUPP; } return 0; } #if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) int bt_has_features_set(const struct bt_has_features_param *features) { int err; if (!has.registered) { return -ENOTSUP; } /* Check whether any features will change, otherwise we don't want to notify clients */ if (FEATURE_DEVICE_TYPE_UNCHANGED(features->type) && FEATURE_SYNC_SUPPORT_UNCHANGED(features->preset_sync_support) && FEATURE_IND_PRESETS_UNCHANGED(features->independent_presets)) { return 0; } err = has_features_register(features); if (err != 0) { LOG_DBG("Failed to register features"); return err; } notify(NULL, FLAG_FEATURES_CHANGED); return 0; } #endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ int bt_has_register(const struct bt_has_features_param *features) { int err; LOG_DBG("features %p", features); CHECKIF(!features) { LOG_DBG("NULL params pointer"); return -EINVAL; } if (has.registered) { return -EALREADY; } err = has_features_register(features); if (err != 0) { LOG_DBG("HAS service failed to register features: %d", err); return err; } has_svc = (struct bt_gatt_service)BT_GATT_SERVICE(has_attrs); err = bt_gatt_service_register(&has_svc); if (err != 0) { LOG_DBG("HAS service register failed: %d", err); return err; } if (IS_ENABLED(CONFIG_BT_HAS_FEATURES_NOTIFIABLE)) { hearing_aid_features_attr = bt_gatt_find_by_uuid(has_svc.attrs, has_svc.attr_count, BT_UUID_HAS_HEARING_AID_FEATURES); __ASSERT_NO_MSG(hearing_aid_features_attr != NULL); } if (IS_ENABLED(CONFIG_BT_HAS_PRESET_SUPPORT)) { preset_control_point_attr = bt_gatt_find_by_uuid(has_svc.attrs, has_svc.attr_count, BT_UUID_HAS_PRESET_CONTROL_POINT); __ASSERT_NO_MSG(preset_control_point_attr != NULL); active_preset_index_attr = bt_gatt_find_by_uuid(has_svc.attrs, has_svc.attr_count, BT_UUID_HAS_ACTIVE_PRESET_INDEX); __ASSERT_NO_MSG(active_preset_index_attr != NULL); } #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) for (size_t i = 0; i < ARRAY_SIZE(preset_pool); i++) { struct has_preset *preset = &preset_pool[i]; sys_slist_append(&preset_free_list, &preset->node); } #endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) || defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) bt_conn_auth_info_cb_register(&auth_info_cb); #endif /* CONFIG_BT_HAS_PRESET_SUPPORT || CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ has.registered = true; return 0; }