From 313ce366d107c9a5c9442e07b13e49db7be9635b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Kopy=C5=9Bci=C5=84ski?= Date: Fri, 9 Feb 2024 14:55:20 +0100 Subject: [PATCH] nimble/host: Add Broadcast Audio Scan Service BASS initial implementation --- .../services/bass/ble_audio_svc_bass.h | 174 ++++ nimble/host/audio/services/bass/pkg.yml | 32 + .../services/bass/src/ble_audio_svc_bass.c | 915 ++++++++++++++++++ nimble/host/audio/services/bass/syscfg.yml | 38 + 4 files changed, 1159 insertions(+) create mode 100644 nimble/host/audio/services/bass/include/services/bass/ble_audio_svc_bass.h create mode 100644 nimble/host/audio/services/bass/pkg.yml create mode 100644 nimble/host/audio/services/bass/src/ble_audio_svc_bass.c create mode 100644 nimble/host/audio/services/bass/syscfg.yml diff --git a/nimble/host/audio/services/bass/include/services/bass/ble_audio_svc_bass.h b/nimble/host/audio/services/bass/include/services/bass/ble_audio_svc_bass.h new file mode 100644 index 0000000000..787d4e215d --- /dev/null +++ b/nimble/host/audio/services/bass/include/services/bass/ble_audio_svc_bass.h @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef H_BLE_AUDIO_SVC_BASS_ +#define H_BLE_AUDIO_SVC_BASS_ + +#include +#include "host/audio/ble_audio_bsnk.h" +#include "syscfg/syscfg.h" + +#define BLE_SVC_AUDIO_BASS_UUID16 0x184F +#define BLE_SVC_AUDIO_BASS_CHR_UUID16_BAS_CONTROL_POINT 0x2BC7 +#define BLE_SVC_AUDIO_BASS_CHR_UUID16_BROADCAST_RECEIVE_STATE 0x2BC8 + +#define BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_REMOTE_SCAN_STOPPED 0x00 +#define BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_REMOTE_SCAN_STARTED 0x01 +#define BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_ADD_SOURCE 0x02 +#define BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_MODIFY_SOURCE 0x03 +#define BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_SET_BROADCAST_CODE 0x04 +#define BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_REMOVE_SOURCE 0x05 + +enum ble_svc_audio_bass_big_enc { + BLE_SVC_AUDIO_BASS_BIG_ENC_NOT_ENCRYPTED, + BLE_SVC_AUDIO_BASS_BIG_ENC_BROADCAST_CODE_REQ, + BLE_SVC_AUDIO_BASS_BIG_ENC_DECRYPTING, + BLE_SVC_AUDIO_BASS_BIG_ENC_BAD_CODE +}; + +enum ble_svc_audio_bass_pa_sync { + BLE_SVC_AUDIO_BASS_PA_SYNC_DO_NOT_SYNC, + BLE_SVC_AUDIO_BASS_PA_SYNC_SYNC_PAST_AVAILABLE, + BLE_SVC_AUDIO_BASS_PA_SYNC_SYNC_PAST_NOT_AVAILABLE +}; + +enum ble_svc_audio_bass_pa_sync_state { + BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_NOT_SYNCED, + BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNC_INFO_REQ, + BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNCED, + BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNCED_FAILED, + BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_NO_PAST +}; + +struct ble_svc_audio_bass_subgroup { + uint32_t bis_sync_state; + uint8_t metadata_length; + uint8_t *metadata; +}; + +struct ble_svc_audio_bass_receiver_state_params { + ble_addr_t source_addr; + uint8_t source_adv_sid; + uint32_t broadcast_id; + uint8_t pa_sync_state; + uint8_t big_encryption; + uint8_t bad_code[BLE_AUDIO_BROADCAST_CODE_SIZE]; + uint8_t num_subgroups; + struct ble_svc_audio_bass_subgroup + subgroups[MYNEWT_VAL(BLE_SVC_AUDIO_BASS_MAX_SUBGROUPS_PER_RECV_STATE)]; +}; + +struct ble_svc_audio_bass_receiver_state { + uint8_t source_id; + struct ble_audio_bsnk *bsnk; + struct ble_svc_audio_bass_receiver_state_params params; +}; + +struct ble_svc_audio_bass_ctrl_point_event { + /** + * Indicates the type of BASS event that occurred. This is one of the + * BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT codes. + */ + uint8_t op; + + /** + * A discriminated union containing additional details concerning the BASS Control Point + * event. The 'type' field indicates which member of the union is valid. + */ + union { + /** + * Represents Add Source operation. Valid for the following event + * types: + * o BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_ADD_SOURCE + * Application can accept or reject Add Source operation. If no application callback is set + * and free Receive State characteristic exists operation is automatically accepted. + * If application callback exists and returns 0 accepted. Otherwise, Operation is rejected. + * If operation is accepted by application callback, this callback may select receiver + * state to be filled. If application doesnt select characteristic, BASS Server falls back + * to searching free one. If none is found, operation is rejected. + */ + struct { + /** + * Advertiser Address + */ + ble_addr_t adv_addr; + uint8_t adv_sid; + uint32_t broadcast_id : 24; + uint8_t pa_sync; + uint16_t pa_interval; + uint16_t num_subgroups; + struct ble_svc_audio_bass_subgroup + subgroups[MYNEWT_VAL(BLE_SVC_AUDIO_BASS_MAX_SUBGROUPS_PER_RECV_STATE)]; + uint8_t *source_id; + } add_source; + + /**newt + * Represents Modify Source operation. Valid for the following event + * types: + * o BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_MODIFY_SOURCE + * Application can accept or reject Add Source operation. If no application callback is set + * or application callback returns 0 operation is automatically accepted. + * If application callback returns non-zero value operation is rejected. + */ + struct { + uint8_t source_id; + uint8_t pa_sync; + uint16_t pa_interval; + uint16_t num_subgroups; + uint32_t bis_sync[MYNEWT_VAL(BLE_SVC_AUDIO_BASS_MAX_SUBGROUPS_PER_RECV_STATE)]; + } modify_source; + + /** + * Represents Set Broadcast_Code operation. Valid for the following event + * types: + * o BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_SET_BROADCAST_CODE + */ + struct { + uint8_t source_id; + uint8_t broadcast_code[BLE_AUDIO_BROADCAST_CODE_SIZE]; + } set_broadcast_code; + + /** + * Represents Remove Source operation. Valid for the following event + * types: + * o BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_REMOVE_SOURCE + */ + struct { + uint8_t source_id; + } remove_source; + }; +}; + +typedef int ble_svc_audio_bass_ctrl_point_ev_fn(struct ble_svc_audio_bass_ctrl_point_event *event, + void *arg); + +int +ble_svc_audio_bass_ctrl_point_cb_set(ble_svc_audio_bass_ctrl_point_ev_fn *cb, void *arg); + +int +ble_svc_audio_bass_receive_state_find_free(struct ble_svc_audio_bass_receiver_state **out_state); + +int +ble_svc_audio_bass_receive_state_add(const struct ble_svc_audio_bass_receiver_state_params *params, + struct ble_svc_audio_bass_receiver_state **out_state); + +int +ble_svc_audio_bass_receive_state_remove(struct ble_svc_audio_bass_receiver_state *state); + +#endif /* H_BLE_AUDIO_SVC_BASS_ */ diff --git a/nimble/host/audio/services/bass/pkg.yml b/nimble/host/audio/services/bass/pkg.yml new file mode 100644 index 0000000000..5d8900ef2e --- /dev/null +++ b/nimble/host/audio/services/bass/pkg.yml @@ -0,0 +1,32 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +pkg.name: nimble/host/services/audio/bass +pkg.description: Broadcast Audio Scan Service +pkg.author: "Apache Mynewt " +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + - ble + - bluetooth + - pacs + - nimble + +pkg.deps: + - nimble/host + +pkg.init: + ble_svc_audio_bass_init: 'MYNEWT_VAL(BLE_SVC_AUDIO_BASS_SYSINIT_STAGE)' diff --git a/nimble/host/audio/services/bass/src/ble_audio_svc_bass.c b/nimble/host/audio/services/bass/src/ble_audio_svc_bass.c new file mode 100644 index 0000000000..db9f054705 --- /dev/null +++ b/nimble/host/audio/services/bass/src/ble_audio_svc_bass.c @@ -0,0 +1,915 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "host/ble_hs.h" +#include "host/ble_gatt.h" +#include "../../host/src/ble_gatt_priv.h" +#include "../../host/src/ble_att_priv.h" +#include "services/bass/ble_audio_svc_bass.h" +#include "../../host/audio/include/host/audio/ble_audio_bsnk.h" + +#define EXPECTED_LEN_VARIABLE (-1) +#define BLE_SVC_AUDIO_BASS_RECEIVE_STATE_SRC_ID_NONE 0xFF +#define BLE_SVC_AUDIO_BASS_RECEIVE_BIS_SYNC_STATE_ANY 0xFFFFFFFF + +enum ble_svc_audio_bass_ctrl_point_op_code { + BLE_AUDIO_SVC_BASS_REMOTE_SCAN_STOPPED, + BLE_AUDIO_SVC_BASS_REMOTE_SCAN_STARTED, + BLE_AUDIO_SVC_BASS_ADD_SOURCE, + BLE_AUDIO_SVC_BASS_MODIFY_SOURCE, + BLE_AUDIO_SVC_BASS_SET_BROADCAST_CODE, + BLE_AUDIO_SVC_BASS_REMOVE_SOURCE +}; + +typedef int ble_svc_audio_bass_ctrl_point_handler_cb(uint8_t *data, void *arg); + +static struct ble_svc_audio_bass_ctrl_point_ev { + ble_svc_audio_bass_ctrl_point_ev_fn *ctrl_point_ev_fn; + void *arg; +} ctrl_point_ev; + +static struct ble_svc_audio_bass_receiver_state + receiver_states[MYNEWT_VAL(BLE_SVC_AUDIO_BASS_RECEIVE_STATE_MAX)] = { + [0 ... MYNEWT_VAL(BLE_SVC_AUDIO_BASS_RECEIVE_STATE_MAX) - 1] = { + .source_id = BLE_SVC_AUDIO_BASS_RECEIVE_STATE_SRC_ID_NONE + } +}; + +static struct ble_audio_event_listener ble_svc_audio_bass_audio_event_listener; + +static struct os_mempool ble_audio_svc_bass_metadata_pool; +static os_membuf_t ble_audio_svc_bass_metadata_mem[ + OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_SVC_AUDIO_BASS_RECEIVE_STATE_MAX) * + MYNEWT_VAL(BLE_SVC_AUDIO_BASS_MAX_SUBGROUPS_PER_RECV_STATE), + MYNEWT_VAL(BLE_SVC_AUDIO_BASS_METADATA_MAX_SZ))]; + +static int +ble_svc_audio_bass_remote_scan_stopped(uint8_t *data, void *arg); +static int +ble_svc_audio_bass_remote_scan_started(uint8_t *data, void *arg); +static int +ble_svc_audio_bass_add_source(uint8_t *data, void *arg); +static int +ble_svc_audio_bass_modify_source(uint8_t *data, void *arg); +static int +ble_svc_audio_bass_set_broadcast_code(uint8_t *data, void *arg); +static int +ble_svc_audio_bass_remove_source(uint8_t *data, void *arg); + +static struct ble_svc_audio_bass_ctrl_point_handler { + uint8_t op_code; + int expected_len; + ble_svc_audio_bass_ctrl_point_handler_cb *handler_cb; +} ble_svc_audio_bass_ctrl_point_handlers[] = { + { + .op_code = BLE_AUDIO_SVC_BASS_REMOTE_SCAN_STOPPED, + .expected_len = 0, + .handler_cb = ble_svc_audio_bass_remote_scan_stopped + }, { + .op_code = BLE_AUDIO_SVC_BASS_REMOTE_SCAN_STARTED, + .expected_len = 0, + .handler_cb = ble_svc_audio_bass_remote_scan_started + }, { + .op_code = BLE_AUDIO_SVC_BASS_ADD_SOURCE, + .expected_len = EXPECTED_LEN_VARIABLE, + .handler_cb = ble_svc_audio_bass_add_source + }, { + .op_code = BLE_AUDIO_SVC_BASS_MODIFY_SOURCE, + .expected_len = EXPECTED_LEN_VARIABLE, + .handler_cb = ble_svc_audio_bass_modify_source + }, { + .op_code = BLE_AUDIO_SVC_BASS_SET_BROADCAST_CODE, + .expected_len = 17, + .handler_cb = ble_svc_audio_bass_set_broadcast_code + }, { + .op_code = BLE_AUDIO_SVC_BASS_REMOVE_SOURCE, + .expected_len = 1, + .handler_cb = ble_svc_audio_bass_remove_source + } +}; + +static int +ble_svc_audio_bass_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg); + +static int +ble_svc_audio_bass_ctrl_point_write_access(struct ble_gatt_access_ctxt *ctxt); +static int +ble_svc_audio_bass_rcv_state_read_access(struct ble_gatt_access_ctxt *ctxt, void *arg); + +static struct ble_gatt_chr_def ble_svc_audio_bass_chrs[MYNEWT_VAL(BLE_SVC_AUDIO_BASS_RECEIVE_STATE_MAX) + 2]; + +static const struct ble_gatt_svc_def ble_svc_audio_bass_defs[MYNEWT_VAL(BLE_SVC_AUDIO_BASS_RECEIVE_STATE_MAX) + 2] = { + { /*** Service: Published Audio Capabilities Service (bass) */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = BLE_UUID16_DECLARE(BLE_SVC_AUDIO_BASS_UUID16), + .characteristics = ble_svc_audio_bass_chrs, + }, + { + 0, /* No more services. */ + }, +}; + +static int +ble_svc_audio_bass_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + uint16_t uuid16 = ble_uuid_u16(ctxt->chr->uuid); + int rc; + + switch (uuid16) { + case BLE_SVC_AUDIO_BASS_CHR_UUID16_BAS_CONTROL_POINT: + if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { + rc = ble_svc_audio_bass_ctrl_point_write_access(ctxt); + } else { + assert(0); + } + return rc; + case BLE_SVC_AUDIO_BASS_CHR_UUID16_BROADCAST_RECEIVE_STATE: + if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { + rc = ble_svc_audio_bass_rcv_state_read_access(ctxt, arg); + } else { + assert(0); + } + return rc; + default: + assert(0); + } +} + +static uint8_t +ble_svc_audio_bass_get_new_source_id() +{ + struct ble_svc_audio_bass_receiver_state *rcv_state; + struct ble_svc_audio_bass_receiver_state *next_rcv_state; + uint8_t free_source_id; + uint8_t highest_source_id = 0; + uint8_t lowest_source_id = BLE_SVC_AUDIO_BASS_RECEIVE_STATE_SRC_ID_NONE; + + int i; + + /* First characteristic is BLE_SVC_AUDIO_BASS_CHR_UUID16_BAS_CONTROL_POINT, iterate from 2nd */ + for (i = 1; i <= MYNEWT_VAL(BLE_SVC_AUDIO_BASS_RECEIVE_STATE_MAX); i++) { + rcv_state = ble_svc_audio_bass_chrs[i].arg; + if (highest_source_id < rcv_state->source_id && + rcv_state->source_id != BLE_SVC_AUDIO_BASS_RECEIVE_STATE_SRC_ID_NONE) + { + highest_source_id = rcv_state->source_id; + } + + if (lowest_source_id > rcv_state->source_id) { + lowest_source_id = rcv_state->source_id; + } + } + + if ((highest_source_id + 1) < BLE_SVC_AUDIO_BASS_RECEIVE_STATE_SRC_ID_NONE) { + return highest_source_id + 1; + } + + return min(lowest_source_id + 1, 0); +} + +static int +ble_svc_audio_bass_remote_scan_stopped(uint8_t *data, void *arg) +{ + struct ble_svc_audio_bass_ctrl_point_event ev = { + .op = BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_REMOTE_SCAN_STOPPED + }; + + if (ctrl_point_ev.ctrl_point_ev_fn) { + ctrl_point_ev.ctrl_point_ev_fn(&ev, ctrl_point_ev.arg); + } + + return 0; +} + +static int +ble_svc_audio_bass_remote_scan_started(uint8_t *data, void *arg) +{ + struct ble_svc_audio_bass_ctrl_point_event ev = { + .op = BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_REMOTE_SCAN_STARTED + }; + + if (ctrl_point_ev.ctrl_point_ev_fn) { + ctrl_point_ev.ctrl_point_ev_fn(&ev, ctrl_point_ev.arg); + } + + return 0; +} + +static int +ble_svc_audio_bass_create_bsnk(ble_addr_t *addr, uint8_t sid, + uint32_t broadcast_id, uint16_t pa_itvl, + struct ble_audio_bsnk **out_bsnk) +{ + struct ble_audio_bsnk_create_params bsnk_create_params; + + bsnk_create_params.adv_addr = addr; + bsnk_create_params.adv_sid = sid; + bsnk_create_params.broadcast_id = broadcast_id; + bsnk_create_params.pa_interval = pa_itvl; + + return ble_audio_bsnk_create(&bsnk_create_params, out_bsnk); +} + +static int +ble_svc_audio_bass_sync_pa(struct ble_audio_bsnk *bsnk) +{ + struct ble_audio_bsnk_pa_sync_params params = { + .conn_handle = BLE_HS_CONN_HANDLE_NONE, + .retry_count = 0, + .skip = 0 + }; + + return ble_audio_bsnk_pa_sync(bsnk, ¶ms); +} + +static int +ble_svc_audio_bass_receive_state_find_by_sink(struct ble_svc_audio_bass_receiver_state **out_state, + struct ble_audio_bsnk *snk) +{ + int i; + + for (i = 0; i < sizeof(receiver_states); i++) { + if (receiver_states[i].bsnk == snk) { + *out_state = &receiver_states[i]; + return 0; + } + } + + return BLE_HS_ENOMEM; +} + +static int +ble_svc_audio_bass_receive_state_find_by_source_id(struct ble_svc_audio_bass_receiver_state **out_state, + uint8_t source_id) +{ + int i; + + for (i = 0; i < sizeof(receiver_states); i++) { + if (receiver_states[i].source_id == source_id) { + *out_state = &receiver_states[i]; + return 0; + } + } + + return BLE_HS_ENOMEM; +} + +static int +iso_event_handler(struct ble_iso_event *event, void *arg) +{ + struct ble_svc_audio_bass_receiver_state *state = arg; + + switch (event->type) { + case BLE_ISO_EVENT_BIG_SYNC_ESTABLISHED: + + break; + default: + break; + } + + return 0; +} + +static void +ble_svc_audio_bass_sync_bises(struct ble_svc_audio_bass_receiver_state *state, + uint32_t bis_sync_state) +{ + struct ble_audio_bsnk_bis_sync_params bis_sync_params[MYNEWT_VAL(BLE_MAX_BIS)] = { 0 }; + struct ble_audio_bsnk_sync_params sync_params = { + .params = bis_sync_params, + }; + uint8_t bis_index = 0x01; + int rc; + int i; + + sync_params.num_bis = __builtin_popcount(bis_sync_state); + + for (i = 0; i < sync_params.num_bis; i++) { + while (bis_sync_params[i].bis_index == 0) { + if (bis_sync_state & (1 << (bis_index - 1))) { + bis_sync_params[i].bis_index = bis_index; + bis_sync_params[i].cb = iso_event_handler; + bis_sync_params[i].cb_arg = state; + } + + bis_index++; + } + } + + rc = ble_audio_bsnk_sync(state->bsnk, &sync_params); + if (rc != 0) { + for (i = 0; i < state->params.num_subgroups; i++) { + state->params.subgroups[i].bis_sync_state = + BLE_SVC_AUDIO_BASS_RECEIVE_BIS_SYNC_STATE_ANY; + } + } +} + +static int +ble_svc_audio_bass_receive_state_notify(struct ble_svc_audio_bass_receiver_state *state) +{ + int i; + + for (i = 0; i < sizeof(ble_svc_audio_bass_chrs); i++) { + if (ble_svc_audio_bass_chrs[i].arg == state) { + ble_gatts_chr_updated(*ble_svc_audio_bass_chrs[i].val_handle); + return 0; + } + } + + return BLE_HS_ENOENT; +} + +static int +ble_svc_audio_bass_add_source(uint8_t *data, void *arg) +{ + struct ble_svc_audio_bass_ctrl_point_event ev; + struct ble_svc_audio_bass_receiver_state *rcv_state = NULL; + uint8_t source_id; + uint8_t offset = 0; + uint8_t *metadata_ptr; + int rc; + int i; + + memset(&ev, 0, sizeof(ev)); + + ev.op = BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_ADD_SOURCE; + + ev.add_source.adv_addr.type = data[offset++]; + memcpy(ev.add_source.adv_addr.val, &data[offset], 6); + offset += 6; + ev.add_source.adv_sid = data[offset++]; + ev.add_source.broadcast_id = get_le24(&data[offset]); + offset += 3; + ev.add_source.pa_sync = data[offset++]; + if (ev.add_source.pa_sync > 0x02) { + return BLE_HS_EINVAL; + } + + ev.add_source.pa_interval = get_le16(&data[offset]); + offset += 2; + ev.add_source.num_subgroups = get_le16(&data[offset]); + offset += 2; + + for (i = 0; i < ev.add_source.num_subgroups; i++) { + ev.add_source.subgroups[i].bis_sync_state = get_le32(&data[offset]); + offset += 4; + ev.add_source.subgroups[i].metadata_length = data[offset++]; + ev.add_source.subgroups[i].metadata = &data[offset]; + offset += ev.add_source.subgroups[i].metadata_length; + } + + *ev.add_source.source_id = BLE_SVC_AUDIO_BASS_RECEIVE_STATE_SRC_ID_NONE; + + if (ctrl_point_ev.ctrl_point_ev_fn) { + rc = ctrl_point_ev.ctrl_point_ev_fn(&ev, ctrl_point_ev.arg); + if (rc != 0) { + return BLE_HS_EREJECT; + } + } + + if (*ev.add_source.source_id == BLE_SVC_AUDIO_BASS_RECEIVE_STATE_SRC_ID_NONE) { + ble_svc_audio_bass_receive_state_find_free(&rcv_state); + } + + if (!rcv_state) { + return BLE_HS_EREJECT; + } + + source_id = ble_svc_audio_bass_get_new_source_id(); + + rcv_state->source_id = source_id; + rcv_state->params.source_addr.type = ev.add_source.adv_addr.type; + memcpy(&rcv_state->params.source_addr.type, ev.add_source.adv_addr.val, 6); + rcv_state->params.source_adv_sid = ev.add_source.adv_sid; + rcv_state->params.broadcast_id = ev.add_source.broadcast_id; + + switch (ev.add_source.pa_sync) { + case 0x00: + rcv_state->params.pa_sync_state = BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_NOT_SYNCED; + break; + case 0x01: + case 0x02: + if (MYNEWT_VAL(BLE_PERIODIC_ADV_SYNC_TRANSFER)) { + rcv_state->params.pa_sync_state = BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNC_INFO_REQ; + } else { + rc = ble_svc_audio_bass_create_bsnk(&ev.add_source.adv_addr, + ev.add_source.adv_sid, + ev.add_source.broadcast_id, + ev.add_source.pa_interval, + &rcv_state->bsnk); + if (rc != 0) { + return rc; + } + + rc = ble_svc_audio_bass_sync_pa(rcv_state->bsnk); + if (rc != 0) { + return rc; + } + } + default: + break; + } + + for (i = 0; i < ev.add_source.num_subgroups; i++) { + metadata_ptr = os_memblock_get(&ble_audio_svc_bass_metadata_pool); + if (!metadata_ptr) { + return 0; + } + rcv_state->params.subgroups[i].metadata_length = ev.add_source.subgroups[i].metadata_length; + memcpy(metadata_ptr, ev.add_source.subgroups[i].metadata, + min(ev.add_source.subgroups[i].metadata_length, + MYNEWT_VAL(BLE_SVC_AUDIO_BASS_METADATA_MAX_SZ))); + if (ev.add_source.subgroups[i].metadata_length > 0) { + rcv_state->params.subgroups[i].metadata = metadata_ptr; + } + rcv_state->params.subgroups[i].metadata = ev.add_source.subgroups[i].metadata; + } + + return ble_svc_audio_bass_receive_state_notify(rcv_state); +} + +static int +ble_svc_audio_bass_modify_source(uint8_t *data, void *arg) +{ + struct ble_svc_audio_bass_ctrl_point_event ev; + struct ble_svc_audio_bass_receiver_state *rcv_state = NULL; + uint8_t source_id; + uint8_t offset = 0; + uint8_t *metadata_ptr; + int rc; + int i; + + memset(&ev, 0, sizeof(ev)); + + ev.op = BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_ADD_SOURCE; + + ev.modify_source.source_id = data[offset++]; + ev.modify_source.pa_sync = data[offset++]; + if (ev.modify_source.pa_sync > 0x02) { + return BLE_HS_EINVAL; + } + + ev.modify_source.pa_interval = get_le16(&data[offset]); + offset += 2; + ev.modify_source.num_subgroups = get_le16(&data[offset]); + offset += 2; + + for (i = 0; i < ev.modify_source.num_subgroups; i++) { + ev.modify_source.bis_sync[i] = get_le32(&data[offset]); + offset += 4; + } + + if (ctrl_point_ev.ctrl_point_ev_fn) { + rc = ctrl_point_ev.ctrl_point_ev_fn(&ev, ctrl_point_ev.arg); + if (rc != 0) { + return BLE_HS_EREJECT; + } + } + + ble_svc_audio_bass_receive_state_find_by_source_id(&rcv_state, ev.modify_source.source_id); + if (rcv_state == NULL) { + return BLE_HS_EREJECT; + } + + if (ev.modify_source.pa_sync == BLE_SVC_AUDIO_BASS_PA_SYNC_DO_NOT_SYNC && + rcv_state->params.pa_sync_state != BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNCED) { + rcv_state->params.pa_sync_state = BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_NOT_SYNCED; + } else if (ev.modify_source.pa_sync == BLE_SVC_AUDIO_BASS_PA_SYNC_DO_NOT_SYNC && + rcv_state->params.pa_sync_state == BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNCED) { + ble_audio_bsnk_pa_sync_term(rcv_state->bsnk); + } else if (ev.modify_source.pa_sync == BLE_SVC_AUDIO_BASS_PA_SYNC_SYNC_PAST_AVAILABLE) { + if (MYNEWT_VAL(BLE_PERIODIC_ADV_SYNC_TRANSFER)) { + rcv_state->params.pa_sync_state = BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNC_INFO_REQ; + } else { + rc = ble_svc_audio_bass_sync_pa(rcv_state->bsnk); + if (rc != 0) { + return rc; + } + } + } else if (ev.modify_source.pa_sync == BLE_SVC_AUDIO_BASS_PA_SYNC_SYNC_PAST_NOT_AVAILABLE) { + rc = ble_svc_audio_bass_sync_pa(rcv_state->bsnk); + if (rc != 0) { + return rc; + } + } + + for (i = 0; i < rcv_state->params.num_subgroups; i++) { + ble_svc_audio_bass_sync_bises(rcv_state, ev.modify_source.bis_sync[i]); + } + + return ble_svc_audio_bass_receive_state_notify(rcv_state); +} + +static int +ble_svc_audio_bass_set_broadcast_code(uint8_t *data, void *arg) +{ + int offset = 0; + struct ble_svc_audio_bass_ctrl_point_event ev = { + .op = BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_SET_BROADCAST_CODE + }; + struct ble_svc_audio_bass_receiver_state *rcv_state = NULL; + + ev.set_broadcast_code.source_id = data[offset++]; + memcpy(ev.set_broadcast_code.broadcast_code, &data[offset], BLE_AUDIO_BROADCAST_CODE_SIZE); + + ble_svc_audio_bass_receive_state_find_by_source_id(&rcv_state, ev.set_broadcast_code.source_id); + if (rcv_state == NULL) { + return BLE_HS_EREJECT; + } + + if (ctrl_point_ev.ctrl_point_ev_fn) { + ctrl_point_ev.ctrl_point_ev_fn(&ev, ctrl_point_ev.arg); + } + + ble_audio_bsnk_code_set(rcv_state->bsnk, (char *)ev.set_broadcast_code.broadcast_code); + + return 0; +} + +static int +ble_svc_audio_bass_remove_source(uint8_t *data, void *arg) +{ + struct ble_svc_audio_bass_ctrl_point_event ev = { + .op = BLE_SVC_AUDIO_BASS_CTRL_POINT_EVENT_REMOVE_SOURCE + }; + struct ble_svc_audio_bass_receiver_state *rcv_state = NULL; + int rc; + int i; + + ev.set_broadcast_code.source_id = data[0]; + + if (ctrl_point_ev.ctrl_point_ev_fn) { + rc = ctrl_point_ev.ctrl_point_ev_fn(&ev, ctrl_point_ev.arg); + if (rc != 0) { + return BLE_HS_EREJECT; + } + } + + ble_svc_audio_bass_receive_state_find_by_source_id(&rcv_state, ev.set_broadcast_code.source_id); + if (rcv_state == NULL) { + return BLE_HS_EREJECT; + } + + for (i = 0; i < rcv_state->params.num_subgroups; i++) { + os_memblock_put(&ble_audio_svc_bass_metadata_pool, rcv_state->params.subgroups[i].metadata); + } + + ble_audio_bsnk_destroy(rcv_state->bsnk); + + memset(rcv_state, 0, sizeof(struct ble_svc_audio_bass_receiver_state)); + rcv_state->source_id = BLE_SVC_AUDIO_BASS_RECEIVE_STATE_SRC_ID_NONE; + + return ble_svc_audio_bass_receive_state_notify(rcv_state); +} + +static struct ble_svc_audio_bass_ctrl_point_handler * +ble_svc_audio_bass_find_handler(uint8_t opcode) +{ + int i; + + for(i = 0; i < sizeof(ble_svc_audio_bass_ctrl_point_handlers); i++) { + if (ble_svc_audio_bass_ctrl_point_handlers[i].op_code == opcode) { + return &ble_svc_audio_bass_ctrl_point_handlers[i]; + } + } + + return NULL; +} + +static int +ble_svc_audio_bass_ctrl_point_write_access(struct ble_gatt_access_ctxt *ctxt) +{ + struct ble_svc_audio_bass_ctrl_point_handler *handler; + + uint8_t opcode = ctxt->om->om_data[0]; + + handler = ble_svc_audio_bass_find_handler(opcode); + + if (!handler) { + return BLE_HS_ENOENT; + } + + if (handler->expected_len != EXPECTED_LEN_VARIABLE && + handler->expected_len != ctxt->om->om_len - 1) { + return BLE_HS_EINVAL; + } + + return handler->handler_cb(&ctxt->om->om_data[1], NULL); +} + +static enum ble_svc_audio_bass_big_enc +ble_svc_audio_bass_bsnk_enc_state_to_big_enc(enum ble_audio_bsnk_enc_state enc_state) +{ + switch (enc_state) { + case BLE_AUDIO_BSNK_ENC_NONE: + return BLE_SVC_AUDIO_BASS_BIG_ENC_NOT_ENCRYPTED; + case BLE_AUDIO_BSNK_ENC_PENDING: + return BLE_SVC_AUDIO_BASS_BIG_ENC_BROADCAST_CODE_REQ; + case BLE_AUDIO_BSNK_ENC_ACTIVE: + return BLE_SVC_AUDIO_BASS_BIG_ENC_DECRYPTING; + case BLE_AUDIO_BSNK_ENC_FAILED_CODE_MISSING: + /* TODO: Check */ + return BLE_SVC_AUDIO_BASS_BIG_ENC_BROADCAST_CODE_REQ; + case BLE_AUDIO_BSNK_ENC_FAILED_CODE_INVALID: + return BLE_SVC_AUDIO_BASS_BIG_ENC_BAD_CODE; + default: + return BLE_SVC_AUDIO_BASS_BIG_ENC_NOT_ENCRYPTED; + } +} +static int +ble_svc_audio_bass_rcv_state_read_access(struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + struct ble_svc_audio_bass_receiver_state *state = arg; + uint8_t *buf; + int i; + + /* Nothing set, return empty buffer */ + if (state->source_id == BLE_SVC_AUDIO_BASS_RECEIVE_STATE_SRC_ID_NONE) { + return 0; + } + + os_mbuf_append(ctxt->om, &state->source_id, 1); + os_mbuf_append(ctxt->om, &state->params.source_addr.type, 1); + os_mbuf_append(ctxt->om, &state->params.source_addr.val, 6); + os_mbuf_append(ctxt->om, &state->params.source_adv_sid, 1); + buf = os_mbuf_extend(ctxt->om, 3); + if (buf == NULL) { + return BLE_ATT_ERR_INSUFFICIENT_RES; + } + + put_le24(buf, state->params.broadcast_id); + os_mbuf_append(ctxt->om, state->params.pa_sync_state != BLE_AUDIO_BSNK_PA_SYNC_TRANSFER_TIMEOUT ? + &state->params.pa_sync_state : BLE_AUDIO_BSNK_PA_SYNC_NONE, 1); + os_mbuf_append(ctxt->om, &state->params.big_encryption, 1); + + if (state->params.big_encryption == BLE_SVC_AUDIO_BASS_BIG_ENC_BAD_CODE) { + os_mbuf_append(ctxt->om, &state->params.bad_code, BLE_AUDIO_BROADCAST_CODE_SIZE); + } + + os_mbuf_append(ctxt->om, &state->params.num_subgroups, 1); + + for (i = 0; i < state->params.num_subgroups; i++) { + buf = os_mbuf_extend(ctxt->om, 4); + if (buf == NULL) { + return BLE_ATT_ERR_INSUFFICIENT_RES; + } + + put_le32(buf, state->params.subgroups[i].bis_sync_state); + os_mbuf_append(ctxt->om, &state->params.subgroups[i].metadata_length, 1); + os_mbuf_append(ctxt->om, state->params.subgroups[i].metadata, + state->params.subgroups[i].metadata_length); + } + + return 0; +} + +int +ble_svc_audio_bass_receive_state_find_free(struct ble_svc_audio_bass_receiver_state **out_state) +{ + int i; + + for (i = 0; i < sizeof(receiver_states); i++) { + if (receiver_states[i].source_id == BLE_SVC_AUDIO_BASS_RECEIVE_STATE_SRC_ID_NONE) { + *out_state = &receiver_states[i]; + return 0; + } + } + + return BLE_HS_ENOMEM; +} + +static void +handle_bsnk_pa_sync_state_changed(struct ble_audio_event_bsnk_pa_sync_state_changed *ev) +{ + struct ble_svc_audio_bass_receiver_state *state; + int i; + + assert(ev->snk != NULL); + + ble_svc_audio_bass_receive_state_find_by_sink(&state, ev->snk); + if (state == NULL) { + return; + } + + switch (ev->state) { + case BLE_AUDIO_BSNK_PA_SYNC_NONE: + state->params.pa_sync_state = BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_NOT_SYNCED; + case BLE_AUDIO_BSNK_PA_SYNC_ACTIVE: + state->params.pa_sync_state = BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNCED; + + for (i = 0; i < state->params.num_subgroups; i++) { + ble_svc_audio_bass_sync_bises(state, state->params.subgroups[i].bis_sync_state); + } + case BLE_AUDIO_BSNK_PA_SYNC_FAILED: + state->params.pa_sync_state = BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNCED_FAILED; + case BLE_AUDIO_BSNK_PA_SYNC_TRANSFER_TIMEOUT: + state->params.pa_sync_state = BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_NO_PAST; + default: + break; + } + ble_svc_audio_bass_receive_state_notify(state); +} + +static void +handle_bsnk_enc_state_changed(struct ble_audio_event_bsnk_enc_state_changed *ev) +{ + struct ble_svc_audio_bass_receiver_state *state; + + assert(ev->snk != NULL); + + ble_svc_audio_bass_receive_state_find_by_sink(&state, ev->snk); + if (state == NULL) { + return; + } + + switch (ev->state) { + case BLE_AUDIO_BSNK_ENC_NONE: + /* No need to write 0x00 (BLE_SVC_AUDIO_BASS_NOT_ENCRYPTED) as this is default */ + break; + case BLE_AUDIO_BSNK_ENC_PENDING: + // TOOD: check + break; + case BLE_AUDIO_BSNK_ENC_ACTIVE: + state->params.big_encryption = BLE_SVC_AUDIO_BASS_BIG_ENC_DECRYPTING; + break; + case BLE_AUDIO_BSNK_ENC_FAILED_CODE_MISSING: + state->params.big_encryption = + BLE_SVC_AUDIO_BASS_BIG_ENC_BROADCAST_CODE_REQ; + break; + case BLE_AUDIO_BSNK_ENC_FAILED_CODE_INVALID: + state->params.big_encryption = BLE_SVC_AUDIO_BASS_BIG_ENC_BAD_CODE; + memcpy(state->params.bad_code, ev->bad_code, + BLE_AUDIO_BROADCAST_CODE_SIZE); + break; + default: + break; + } + ble_svc_audio_bass_receive_state_notify(state); +} + +static void +handle_bsnk_sync_state_changed(struct ble_audio_event_bsnk_sync_state_changed *ev) +{ + struct ble_svc_audio_bass_receiver_state *state; + + assert(ev->snk != NULL); + + ble_svc_audio_bass_receive_state_find_by_sink(&state, ev->snk); + if (state == NULL) { + return; + } + + switch (ev->state) { + case BLE_AUDIO_BSNK_SYNC_NONE: + state->params.subgroups[0].bis_sync_state = 0; + + break; + case BLE_AUDIO_BSNK_SYNC_PENDING: + // TOOD: check + break; + case BLE_AUDIO_BSNK_SYNC_ACTIVE: + /*TODO: these needs to be arrays*/ + state->params.subgroups[0].bis_sync_state = ev->bis_sync; + break; + case BLE_AUDIO_BSNK_SYNC_FAILED: + state->params.subgroups[0].bis_sync_state = BLE_SVC_AUDIO_BASS_RECEIVE_BIS_SYNC_STATE_ANY; + break; + default: + break; + } + + ble_svc_audio_bass_receive_state_notify(state); +} + +static int +ble_svc_audio_bass_audio_event_handler(struct ble_audio_event *event, void *arg) +{ + switch (event->type) { + case BLE_AUDIO_EVENT_BSNK_PA_SYNC_STATE_CHANGED: + handle_bsnk_pa_sync_state_changed(&event->bsnk_pa_sync_state_changed); + break; + case BLE_AUDIO_EVENT_BSNK_ENC_STATE_CHANGED: + handle_bsnk_enc_state_changed(&event->bsnk_enc_state_changed); + break; + case BLE_AUDIO_EVENT_BSNK_SYNC_STATE_CHANGED: + handle_bsnk_sync_state_changed(&event->bsnk_sync_state_changed); + break; + + default: + break; + } + + return 0; +} + +int +ble_svc_audio_bass_ctrl_point_cb_set(ble_svc_audio_bass_ctrl_point_ev_fn *cb, void *arg) +{ + ctrl_point_ev.ctrl_point_ev_fn = cb; + ctrl_point_ev.arg = arg; +} + +int +ble_svc_audio_bass_receive_state_add(const struct ble_svc_audio_bass_receiver_state_params *params, + struct ble_svc_audio_bass_receiver_state **out_state) +{ + struct ble_svc_audio_bass_receiver_state *rcv_state; + int i; + int rc; + + rc = ble_svc_audio_bass_receive_state_find_free(&rcv_state); + if (rc) { + return rc; + } + + rcv_state->source_id = ble_svc_audio_bass_get_new_source_id(); + memcpy(&rcv_state->params, params, sizeof(struct ble_svc_audio_bass_receiver_state_params)); + + for (i = 0; i < rcv_state->params.num_subgroups; i++) { + rcv_state->params.subgroups[i].metadata_length; + rcv_state->params.subgroups[i].metadata = + os_memblock_get(&ble_audio_svc_bass_metadata_pool); + + if (!rcv_state->params.subgroups[i].metadata) { + return 0; + } + + memcpy(rcv_state->params.subgroups[i].metadata, params->subgroups[i].metadata, + params->subgroups[i].metadata_length); + rcv_state->params.subgroups[i].metadata_length = params->subgroups[i].metadata_length; + } + + *out_state = rcv_state; + + return ble_svc_audio_bass_receive_state_notify(rcv_state); +} + +int +ble_svc_audio_bass_receive_state_remove(struct ble_svc_audio_bass_receiver_state *state) +{ + state->source_id = BLE_SVC_AUDIO_BASS_RECEIVE_STATE_SRC_ID_NONE; + memset(&state->params, 0, sizeof(state->params)); + + return ble_svc_audio_bass_receive_state_notify(state); +} + +void +ble_svc_audio_bass_init(void) +{ + int rc; + int i; + + /* Ensure this function only gets called by sysinit. */ + SYSINIT_ASSERT_ACTIVE(); + + ble_svc_audio_bass_chrs[0].uuid = + BLE_UUID16_DECLARE(BLE_SVC_AUDIO_BASS_CHR_UUID16_BAS_CONTROL_POINT); + ble_svc_audio_bass_chrs[0].access_cb = ble_svc_audio_bass_access; + ble_svc_audio_bass_chrs[0].flags = BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_WRITE_ENC; + + for (i = 1; i <= MYNEWT_VAL(BLE_SVC_AUDIO_BASS_RECEIVE_STATE_MAX); i++) { + ble_svc_audio_bass_chrs[i].uuid = + BLE_UUID16_DECLARE(BLE_SVC_AUDIO_BASS_CHR_UUID16_BROADCAST_RECEIVE_STATE); + ble_svc_audio_bass_chrs[i].access_cb = ble_svc_audio_bass_access; + ble_svc_audio_bass_chrs[i].arg = &receiver_states[i-1]; + ble_svc_audio_bass_chrs[i].flags = BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY; + } + + rc = ble_gatts_count_cfg(ble_svc_audio_bass_defs); + SYSINIT_PANIC_ASSERT(rc == 0); + + rc = ble_gatts_add_svcs(ble_svc_audio_bass_defs); + SYSINIT_PANIC_ASSERT(rc == 0); + + rc = ble_audio_event_listener_register(&ble_svc_audio_bass_audio_event_listener, NULL, + ble_svc_audio_bass_audio_event_handler, NULL); + assert(rc == 0); + + rc = os_mempool_init(&ble_audio_svc_bass_metadata_pool, + MYNEWT_VAL(BLE_SVC_AUDIO_BASS_RECEIVE_STATE_MAX) * + MYNEWT_VAL(BLE_SVC_AUDIO_BASS_MAX_SUBGROUPS_PER_RECV_STATE), + MYNEWT_VAL(BLE_SVC_AUDIO_BASS_METADATA_MAX_SZ), + ble_audio_svc_bass_metadata_mem, "ble_audio_svc_bass_metadata_pool"); + SYSINIT_PANIC_ASSERT(rc == 0); + + (void)rc; +} diff --git a/nimble/host/audio/services/bass/syscfg.yml b/nimble/host/audio/services/bass/syscfg.yml new file mode 100644 index 0000000000..2232d9e062 --- /dev/null +++ b/nimble/host/audio/services/bass/syscfg.yml @@ -0,0 +1,38 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +syscfg.defs: + BLE_SVC_AUDIO_BASS_SYSINIT_STAGE: + description: > + Sysinit stage for Published Audio Capabilities Service. + value: 303 + BLE_SVC_AUDIO_BASS_RECEIVE_STATE_MAX: + description: > + Maximum number of Broadcast Receive State characteristics. + value: MYNEWT_VAL(BLE_AUDIO_BSNK_MAX) + BLE_SVC_AUDIO_BASS_MAX_SUBGROUPS_PER_RECV_STATE: + description: > + Maximum number of BIG Subgroups stored in Reveive State characteristic. + value: MYNEWT_VAL(BLE_AUDIO_BSNK_MAX) + BLE_SVC_AUDIO_BASS_METADATA_MAX_SZ: + description: > + Maximum size of metadata that can be saved for subgroup. If set to 0 metadata is + not being saved. + value: 32 + +syscfg.restrictions: + - BLE_SVC_AUDIO_BASS_RECEIVE_STATE_MAX >= BLE_AUDIO_BSNK_MAX