Bluetooth: audio: pacs: Add bt_pacs_set_available_contexts_for_conn

This adds the function that sets the available contexts value per
connection object so that API user can set different available contexts
per connection.
The lifetime of such overriden value is the same as connection lifetime,
so on reconnection or device reboot all devices return to having the
same available contexts value again.

Fixes: #64968
Signed-off-by: Mariusz Skamra <mariusz.skamra@codecoup.pl>
This commit is contained in:
Mariusz Skamra 2023-12-01 14:50:35 +01:00 committed by Fabio Baltieri
commit 78a4b33e5c
6 changed files with 199 additions and 45 deletions

View file

@ -96,6 +96,37 @@ int bt_pacs_set_available_contexts(enum bt_audio_dir dir,
*/
enum bt_audio_context bt_pacs_get_available_contexts(enum bt_audio_dir dir);
/** @brief Set the available contexts for a given connection
*
* This function sets the available contexts value for a given @p conn connection object.
* If the @p contexts parameter is NULL the available contexts value is reset to default.
* The default value of the available contexts is set using @ref bt_pacs_set_available_contexts
* function.
* The Available Context Value is reset to default on ACL disconnection.
*
* @param conn Connection object.
* @param dir Direction of the endpoints to change available contexts for.
* @param contexts The contexts to be set or NULL to reset to default.
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_pacs_conn_set_available_contexts_for_conn(struct bt_conn *conn, enum bt_audio_dir dir,
enum bt_audio_context *contexts);
/** @brief Get the available contexts for a given connection
*
* This server function returns the available contexts value for a given @p conn connection object.
* The value returned is the one set with @ref bt_pacs_conn_set_available_contexts_for_conn function
* or the default value set with @ref bt_pacs_set_available_contexts function.
*
* @param conn Connection object.
* @param dir Direction of the endpoints to get contexts for.
*
* @return Bitmask of available contexts.
*/
enum bt_audio_context bt_pacs_get_available_contexts_for_conn(struct bt_conn *conn,
enum bt_audio_dir dir);
/** @brief Set the supported contexts for an endpoint type
*
* @param dir Direction of the endpoints to change available contexts for.

View file

@ -1992,10 +1992,16 @@ static ssize_t ascs_qos(struct bt_conn *conn, struct net_buf_simple *buf)
struct ascs_parse_result {
int err;
struct bt_conn *conn;
struct bt_bap_ascs_rsp *rsp;
const struct bt_bap_ep *ep;
};
static bool is_context_available(struct bt_conn *conn, enum bt_audio_dir dir, uint16_t context)
{
return (context & bt_pacs_get_available_contexts_for_conn(conn, dir)) == context;
}
static bool ascs_parse_metadata(struct bt_data *data, void *user_data)
{
struct ascs_parse_result *result = user_data;
@ -2035,7 +2041,7 @@ static bool ascs_parse_metadata(struct bt_data *data, void *user_data)
/* The CAP acceptor shall not accept metadata with unsupported stream context. */
if (IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR) &&
data_type == BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT) {
if (!bt_pacs_context_available(ep->dir, context)) {
if (!is_context_available(result->conn, ep->dir, context)) {
LOG_WRN("Context 0x%04x is unavailable", context);
*result->rsp = BT_BAP_ASCS_RSP(
BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED, data_type);
@ -2120,7 +2126,9 @@ static bool ascs_parse_metadata(struct bt_data *data, void *user_data)
static int ascs_verify_metadata(struct bt_bap_ep *ep, const struct bt_ascs_metadata *meta,
struct bt_bap_ascs_rsp *rsp)
{
struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep);
struct ascs_parse_result result = {
.conn = ase->conn,
.rsp = rsp,
.err = 0,
.ep = ep,

View file

@ -67,6 +67,16 @@ enum {
static struct pacs_client {
bt_addr_le_t addr;
#if defined(CONFIG_BT_PAC_SNK)
/* Sink Available Contexts override value */
uint16_t *snk_available_contexts;
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC)
/* Source Available Contexts override value */
uint16_t *src_available_contexts;
#endif /* CONFIG_BT_PAC_SRC */
/* Pending notification flags */
ATOMIC_DEFINE(flags, FLAG_NUM);
} clients[CONFIG_BT_MAX_PAIRED];
@ -197,8 +207,10 @@ static ssize_t available_contexts_read(struct bt_conn *conn,
uint16_t len, uint16_t offset)
{
struct bt_pacs_context context = {
.snk = sys_cpu_to_le16(snk_available_contexts),
.src = sys_cpu_to_le16(src_available_contexts),
.snk = sys_cpu_to_le16(
bt_pacs_get_available_contexts_for_conn(conn, BT_AUDIO_DIR_SINK)),
.src = sys_cpu_to_le16(
bt_pacs_get_available_contexts_for_conn(conn, BT_AUDIO_DIR_SOURCE)),
};
LOG_DBG("conn %p attr %p buf %p len %u offset %u", conn, attr, buf, len, offset);
@ -336,17 +348,6 @@ static void snk_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
LOG_DBG("attr %p value 0x%04x", attr, value);
}
#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */
static inline int set_snk_available_contexts(uint16_t contexts)
{
return set_available_contexts(contexts, &snk_available_contexts,
snk_supported_contexts);
}
#else
static inline int set_snk_available_contexts(uint16_t contexts)
{
return -ENOTSUP;
}
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SNK_LOC)
@ -452,17 +453,6 @@ static void src_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
LOG_DBG("attr %p value 0x%04x", attr, value);
}
#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */
static inline int set_src_available_contexts(uint16_t contexts)
{
return set_available_contexts(contexts, &src_available_contexts,
src_supported_contexts);
}
#else
static inline int set_src_available_contexts(uint16_t contexts)
{
return -ENOTSUP;
}
#endif /* CONFIG_BT_PAC_SRC */
#if defined(CONFIG_BT_PAC_SRC_LOC)
@ -730,8 +720,10 @@ static int pac_notify(struct bt_conn *conn, enum bt_audio_dir dir)
static int available_contexts_notify(struct bt_conn *conn)
{
struct bt_pacs_context context = {
.snk = sys_cpu_to_le16(snk_available_contexts),
.src = sys_cpu_to_le16(src_available_contexts),
.snk = sys_cpu_to_le16(
bt_pacs_get_available_contexts_for_conn(conn, BT_AUDIO_DIR_SINK)),
.src = sys_cpu_to_le16(
bt_pacs_get_available_contexts_for_conn(conn, BT_AUDIO_DIR_SOURCE)),
};
int err;
@ -961,8 +953,43 @@ static void pacs_security_changed(struct bt_conn *conn, bt_security_t level,
}
}
static void pacs_disconnected(struct bt_conn *conn, uint8_t reason)
{
struct pacs_client *client;
client = client_lookup_conn(conn);
if (client == NULL) {
return;
}
#if defined(CONFIG_BT_PAC_SNK)
if (client->snk_available_contexts != NULL) {
uint16_t old = POINTER_TO_UINT(client->snk_available_contexts);
uint16_t new;
client->snk_available_contexts = NULL;
new = bt_pacs_get_available_contexts_for_conn(conn, BT_AUDIO_DIR_SINK);
atomic_set_bit_to(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED, old != new);
}
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC)
if (client->src_available_contexts != NULL) {
uint16_t old = POINTER_TO_UINT(client->src_available_contexts);
uint16_t new;
client->src_available_contexts = NULL;
new = bt_pacs_get_available_contexts_for_conn(conn, BT_AUDIO_DIR_SOURCE);
atomic_set_bit_to(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED, old != new);
}
#endif /* CONFIG_BT_PAC_SRC */
}
static struct bt_conn_cb conn_callbacks = {
.security_changed = pacs_security_changed,
.disconnected = pacs_disconnected,
};
static struct bt_conn_auth_info_cb auth_callbacks = {
@ -970,19 +997,6 @@ static struct bt_conn_auth_info_cb auth_callbacks = {
.bond_deleted = pacs_bond_deleted
};
bool bt_pacs_context_available(enum bt_audio_dir dir, uint16_t context)
{
if (dir == BT_AUDIO_DIR_SOURCE) {
return (context & src_available_contexts) == context;
}
if (dir == BT_AUDIO_DIR_SINK) {
return (context & snk_available_contexts) == context;
}
return false;
}
void bt_pacs_cap_foreach(enum bt_audio_dir dir, bt_pacs_cap_foreach_func_t func, void *user_data)
{
sys_slist_t *pac;
@ -1122,14 +1136,75 @@ int bt_pacs_set_available_contexts(enum bt_audio_dir dir, enum bt_audio_context
{
switch (dir) {
case BT_AUDIO_DIR_SINK:
return set_snk_available_contexts(contexts);
return set_available_contexts(contexts, &snk_available_contexts,
supported_context_get(dir));
case BT_AUDIO_DIR_SOURCE:
return set_src_available_contexts(contexts);
return set_available_contexts(contexts, &src_available_contexts,
supported_context_get(dir));
}
return -EINVAL;
}
int bt_pacs_conn_set_available_contexts_for_conn(struct bt_conn *conn, enum bt_audio_dir dir,
enum bt_audio_context *contexts)
{
enum bt_audio_context old = bt_pacs_get_available_contexts_for_conn(conn, dir);
struct bt_conn_info info = { 0 };
struct pacs_client *client;
int err;
client = client_lookup_conn(conn);
if (client == NULL) {
return -ENOENT;
}
err = bt_conn_get_info(conn, &info);
if (err < 0) {
LOG_ERR("Could not get conn info: %d", err);
return err;
}
switch (dir) {
#if defined(CONFIG_BT_PAC_SNK)
case BT_AUDIO_DIR_SINK:
if (contexts != NULL) {
client->snk_available_contexts = UINT_TO_POINTER(*contexts);
} else {
client->snk_available_contexts = NULL;
}
break;
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC)
case BT_AUDIO_DIR_SOURCE:
if (contexts != NULL) {
client->src_available_contexts = UINT_TO_POINTER(*contexts);
} else {
client->src_available_contexts = NULL;
}
break;
#endif /* CONFIG_BT_PAC_SRC */
default:
return -EINVAL;
}
if (bt_pacs_get_available_contexts_for_conn(conn, dir) == old) {
/* No change. Skip notification */
return 0;
}
atomic_set_bit(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED);
/* Send notification on encrypted link only */
if (info.security.level > BT_SECURITY_L1) {
k_work_submit(&deferred_nfy_work);
}
return 0;
}
int bt_pacs_set_supported_contexts(enum bt_audio_dir dir, enum bt_audio_context contexts)
{
uint16_t *supported_contexts = NULL;
@ -1172,3 +1247,39 @@ enum bt_audio_context bt_pacs_get_available_contexts(enum bt_audio_dir dir)
return BT_AUDIO_CONTEXT_TYPE_PROHIBITED;
}
enum bt_audio_context bt_pacs_get_available_contexts_for_conn(struct bt_conn *conn,
enum bt_audio_dir dir)
{
const struct pacs_client *client;
CHECKIF(conn == NULL) {
LOG_ERR("NULL conn");
return -EINVAL;
}
client = client_lookup_conn(conn);
if (client == NULL) {
LOG_ERR("No client context for conn %p", (void *)conn);
return bt_pacs_get_available_contexts(dir);
}
switch (dir) {
case BT_AUDIO_DIR_SINK:
#if defined(CONFIG_BT_PAC_SNK)
if (client->snk_available_contexts != NULL) {
return POINTER_TO_UINT(client->snk_available_contexts);
}
#endif /* CONFIG_BT_PAC_SNK */
break;
case BT_AUDIO_DIR_SOURCE:
#if defined(CONFIG_BT_PAC_SRC)
if (client->src_available_contexts != NULL) {
return POINTER_TO_UINT(client->src_available_contexts);
}
#endif /* CONFIG_BT_PAC_SRC */
break;
}
return bt_pacs_get_available_contexts(dir);
}

View file

@ -36,5 +36,3 @@ struct bt_pacs_context {
uint16_t snk;
uint16_t src;
} __packed;
bool bt_pacs_context_available(enum bt_audio_dir dir, uint16_t context);

View file

@ -14,5 +14,7 @@ void mock_bt_pacs_init(void);
void mock_bt_pacs_cleanup(void);
DECLARE_FAKE_VOID_FUNC(bt_pacs_cap_foreach, enum bt_audio_dir, bt_pacs_cap_foreach_func_t, void *);
DECLARE_FAKE_VALUE_FUNC(enum bt_audio_context, bt_pacs_get_available_contexts_for_conn,
struct bt_conn *, enum bt_audio_dir);
#endif /* MOCKS_PACS_H_ */

View file

@ -10,7 +10,9 @@
#include "pacs.h"
/* List of fakes used by this unit tester */
#define PACS_FFF_FAKES_LIST(FAKE) FAKE(bt_pacs_cap_foreach)
#define PACS_FFF_FAKES_LIST(FAKE) \
FAKE(bt_pacs_cap_foreach) \
FAKE(bt_pacs_get_available_contexts_for_conn) \
static const struct bt_audio_codec_cap lc3_codec = BT_AUDIO_CODEC_CAP_LC3(
BT_AUDIO_CODEC_LC3_FREQ_ANY, BT_AUDIO_CODEC_LC3_DURATION_10,
@ -18,6 +20,8 @@ static const struct bt_audio_codec_cap lc3_codec = BT_AUDIO_CODEC_CAP_LC3(
(BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | BT_AUDIO_CONTEXT_TYPE_MEDIA));
DEFINE_FAKE_VOID_FUNC(bt_pacs_cap_foreach, enum bt_audio_dir, bt_pacs_cap_foreach_func_t, void *);
DEFINE_FAKE_VALUE_FUNC(enum bt_audio_context, bt_pacs_get_available_contexts_for_conn,
struct bt_conn *, enum bt_audio_dir);
static void pacs_cap_foreach_custom_fake(enum bt_audio_dir dir, bt_pacs_cap_foreach_func_t func,
void *user_data)