Bluetooth: TBS: Add support for long read with dirty bit

The TBS spec states that if a value is changed during a
long read procedure, then the server shall reject
read requests with offset != 0 with a specific TBS GATT
error until the value has been read from the beginning again
(i.e. with offset = 0).

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2025-02-10 15:14:38 +01:00 committed by Benjamin Cabé
commit 54df371740
2 changed files with 253 additions and 80 deletions

View file

@ -37,6 +37,11 @@
extern "C" { extern "C" {
#endif #endif
/** A characteristic value has changed while a Read Long Value Characteristic sub-procedure is in
* progress
*/
#define BT_TBS_ERR_VAL_CHANGED 0x80
/** /**
* @name Call States * @name Call States
* @{ * @{

View file

@ -44,16 +44,23 @@ LOG_MODULE_REGISTER(bt_tbs, CONFIG_BT_TBS_LOG_LEVEL);
struct tbs_flags { struct tbs_flags {
bool bearer_provider_name_changed: 1; bool bearer_provider_name_changed: 1;
bool bearer_provider_name_dirty: 1;
bool bearer_technology_changed: 1; bool bearer_technology_changed: 1;
bool bearer_uri_schemes_supported_list_changed: 1; bool bearer_uri_schemes_supported_list_changed: 1;
bool bearer_uri_schemes_supported_list_dirty: 1;
bool bearer_signal_strength_changed: 1; bool bearer_signal_strength_changed: 1;
bool bearer_list_current_calls_changed: 1; bool bearer_list_current_calls_changed: 1;
bool bearer_list_current_calls_dirty: 1;
bool status_flags_changed: 1; bool status_flags_changed: 1;
bool incoming_call_target_bearer_uri_changed: 1; bool incoming_call_target_bearer_uri_changed: 1;
bool incoming_call_target_bearer_uri_dirty: 1;
bool call_state_changed: 1; bool call_state_changed: 1;
bool call_state_dirty: 1;
bool termination_reason_changed: 1; bool termination_reason_changed: 1;
bool incoming_call_changed: 1; bool incoming_call_changed: 1;
bool incoming_call_dirty: 1;
bool call_friendly_name_changed: 1; bool call_friendly_name_changed: 1;
bool call_friendly_name_dirty: 1;
}; };
/* A service instance can either be a GTBS or a TBS instance */ /* A service instance can either be a GTBS or a TBS instance */
@ -628,6 +635,7 @@ static void set_call_state_changed_cb(struct tbs_flags *flags)
} }
flags->call_state_changed = true; flags->call_state_changed = true;
flags->call_state_dirty = true;
} }
static void set_list_current_calls_changed_cb(struct tbs_flags *flags) static void set_list_current_calls_changed_cb(struct tbs_flags *flags)
@ -637,6 +645,7 @@ static void set_list_current_calls_changed_cb(struct tbs_flags *flags)
} }
flags->bearer_list_current_calls_changed = true; flags->bearer_list_current_calls_changed = true;
flags->bearer_list_current_calls_dirty = true;
} }
static int inst_notify_calls(struct tbs_inst *inst) static int inst_notify_calls(struct tbs_inst *inst)
@ -950,12 +959,33 @@ static void notify_work_handler(struct k_work *work)
static ssize_t read_provider_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, static ssize_t read_provider_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset) uint16_t len, uint16_t offset)
{ {
const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr);
struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)];
ssize_t ret;
int err;
LOG_DBG("Index %u, Provider name %s", inst_index(inst), inst->provider_name); err = k_mutex_lock(&inst->mutex, MUTEX_TIMEOUT);
if (err != 0) {
LOG_DBG("Failed to lock mutex");
return -EBUSY;
}
return bt_gatt_attr_read(conn, attr, buf, len, offset, inst->provider_name, if (offset != 0 && flags->bearer_provider_name_dirty) {
strlen(inst->provider_name)); LOG_DBG("Value dirty for %p", (void *)conn);
ret = BT_GATT_ERR(BT_TBS_ERR_VAL_CHANGED);
} else {
flags->bearer_provider_name_dirty = false;
LOG_DBG("Index %u, Provider name %s", inst_index(inst), inst->provider_name);
ret = bt_gatt_attr_read(conn, attr, buf, len, offset, inst->provider_name,
strlen(inst->provider_name));
}
err = k_mutex_unlock(&inst->mutex);
__ASSERT(err == 0, "Failed to unlock mutex: %d", err);
return ret;
} }
static void provider_name_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) static void provider_name_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
@ -1000,32 +1030,56 @@ static void technology_cfg_changed(const struct bt_gatt_attr *attr, uint16_t val
static ssize_t read_uri_scheme_list(struct bt_conn *conn, const struct bt_gatt_attr *attr, static ssize_t read_uri_scheme_list(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset) void *buf, uint16_t len, uint16_t offset)
{ {
const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr);
struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)];
ssize_t ret;
int err;
net_buf_simple_reset(&read_buf); err = k_mutex_lock(&inst->mutex, MUTEX_TIMEOUT);
if (err != 0) {
net_buf_simple_add_mem(&read_buf, inst->uri_scheme_list, strlen(inst->uri_scheme_list)); LOG_DBG("Failed to lock mutex");
return -EBUSY;
if (inst_is_gtbs(inst)) {
/* TODO: Make uri schemes unique */
for (size_t i = 0; i < ARRAY_SIZE(svc_insts); i++) {
size_t uri_len = strlen(svc_insts[i].uri_scheme_list);
if (read_buf.len + uri_len >= read_buf.size) {
LOG_WRN("Cannot fit all TBS instances in GTBS "
"URI scheme list");
break;
}
net_buf_simple_add_mem(&read_buf, svc_insts[i].uri_scheme_list, uri_len);
}
LOG_DBG("GTBS: URI scheme %.*s", read_buf.len, read_buf.data);
} else {
LOG_DBG("Index %u: URI scheme %.*s", inst_index(inst), read_buf.len, read_buf.data);
} }
return bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, read_buf.len); if (offset != 0 && flags->bearer_uri_schemes_supported_list_dirty) {
LOG_DBG("Value dirty for %p", (void *)conn);
ret = BT_GATT_ERR(BT_TBS_ERR_VAL_CHANGED);
} else {
flags->bearer_uri_schemes_supported_list_dirty = false;
net_buf_simple_reset(&read_buf);
net_buf_simple_add_mem(&read_buf, inst->uri_scheme_list,
strlen(inst->uri_scheme_list));
if (inst_is_gtbs(inst)) {
/* TODO: Make uri schemes unique */
for (size_t i = 0; i < ARRAY_SIZE(svc_insts); i++) {
size_t uri_len = strlen(svc_insts[i].uri_scheme_list);
if (read_buf.len + uri_len >= read_buf.size) {
LOG_WRN("Cannot fit all TBS instances in GTBS "
"URI scheme list");
break;
}
net_buf_simple_add_mem(&read_buf, svc_insts[i].uri_scheme_list,
uri_len);
}
LOG_DBG("GTBS: URI scheme %.*s", read_buf.len, read_buf.data);
} else {
LOG_DBG("Index %u: URI scheme %.*s", inst_index(inst), read_buf.len,
read_buf.data);
}
ret = bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, read_buf.len);
}
err = k_mutex_unlock(&inst->mutex);
__ASSERT(err == 0, "Failed to unlock mutex: %d", err);
return ret;
} }
static void uri_scheme_list_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) static void uri_scheme_list_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
@ -1110,17 +1164,38 @@ static void current_calls_cfg_changed(const struct bt_gatt_attr *attr, uint16_t
static ssize_t read_current_calls(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, static ssize_t read_current_calls(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset) uint16_t len, uint16_t offset)
{ {
const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr);
struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)];
ssize_t ret;
int err;
LOG_DBG("Index %u", inst_index(inst)); err = k_mutex_lock(&inst->mutex, MUTEX_TIMEOUT);
if (err != 0) {
net_buf_put_current_calls(inst, &read_buf); LOG_DBG("Failed to lock mutex");
return -EBUSY;
if (offset == 0) {
LOG_HEXDUMP_DBG(read_buf.data, read_buf.len, "Current calls");
} }
return bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, read_buf.len); if (offset != 0 && flags->bearer_list_current_calls_dirty) {
LOG_DBG("Value dirty for %p", (void *)conn);
ret = BT_GATT_ERR(BT_TBS_ERR_VAL_CHANGED);
} else {
flags->bearer_list_current_calls_dirty = false;
LOG_DBG("Index %u", inst_index(inst));
net_buf_put_current_calls(inst, &read_buf);
if (offset == 0) {
LOG_HEXDUMP_DBG(read_buf.data, read_buf.len, "Current calls");
}
ret = bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, read_buf.len);
}
err = k_mutex_unlock(&inst->mutex);
__ASSERT(err == 0, "Failed to unlock mutex: %d", err);
return ret;
} }
static ssize_t read_ccid(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, static ssize_t read_ccid(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
@ -1157,24 +1232,46 @@ static void status_flags_cfg_changed(const struct bt_gatt_attr *attr, uint16_t v
static ssize_t read_incoming_uri(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, static ssize_t read_incoming_uri(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset) uint16_t len, uint16_t offset)
{ {
const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr);
const struct bt_tbs_in_uri *inc_call_target; struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)];
size_t val_len; ssize_t ret;
int err;
inc_call_target = &inst->incoming_uri; err = k_mutex_lock(&inst->mutex, MUTEX_TIMEOUT);
if (err != 0) {
LOG_DBG("Index %u: call index 0x%02x, URI %s", inst_index(inst), LOG_DBG("Failed to lock mutex");
inc_call_target->call_index, inc_call_target->uri); return -EBUSY;
if (!inc_call_target->call_index) {
LOG_DBG("URI not set");
return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0);
} }
val_len = sizeof(inc_call_target->call_index) + strlen(inc_call_target->uri); if (offset != 0 && flags->incoming_call_target_bearer_uri_dirty) {
LOG_DBG("Value dirty for %p", (void *)conn);
ret = BT_GATT_ERR(BT_TBS_ERR_VAL_CHANGED);
} else {
const struct bt_tbs_in_uri *inc_call_target;
return bt_gatt_attr_read(conn, attr, buf, len, offset, inc_call_target, val_len); flags->incoming_call_target_bearer_uri_dirty = false;
inc_call_target = &inst->incoming_uri;
LOG_DBG("Index %u: call index 0x%02x, URI %s", inst_index(inst),
inc_call_target->call_index, inc_call_target->uri);
if (!inc_call_target->call_index) {
LOG_DBG("URI not set");
ret = 0U;
} else {
const size_t val_len =
sizeof(inc_call_target->call_index) + strlen(inc_call_target->uri);
ret = bt_gatt_attr_read(conn, attr, buf, len, offset, inc_call_target,
val_len);
}
}
err = k_mutex_unlock(&inst->mutex);
__ASSERT(err == 0, "Failed to unlock mutex: %d", err);
return ret;
} }
static void incoming_uri_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) static void incoming_uri_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
@ -1189,17 +1286,38 @@ static void incoming_uri_cfg_changed(const struct bt_gatt_attr *attr, uint16_t v
static ssize_t read_call_state(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, static ssize_t read_call_state(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset) uint16_t len, uint16_t offset)
{ {
const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr);
struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)];
ssize_t ret;
int err;
LOG_DBG("Index %u", inst_index(inst)); err = k_mutex_lock(&inst->mutex, MUTEX_TIMEOUT);
if (err != 0) {
net_buf_put_call_states(inst, &read_buf); LOG_DBG("Failed to lock mutex");
return -EBUSY;
if (offset == 0) {
LOG_HEXDUMP_DBG(read_buf.data, read_buf.len, "Call state");
} }
return bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, read_buf.len); if (offset != 0 && flags->call_state_dirty) {
LOG_DBG("Value dirty for %p", (void *)conn);
ret = BT_GATT_ERR(BT_TBS_ERR_VAL_CHANGED);
} else {
flags->call_state_dirty = false;
LOG_DBG("Index %u", inst_index(inst));
net_buf_put_call_states(inst, &read_buf);
if (offset == 0) {
LOG_HEXDUMP_DBG(read_buf.data, read_buf.len, "Call state");
}
ret = bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, read_buf.len);
}
err = k_mutex_unlock(&inst->mutex);
__ASSERT(err == 0, "Failed to unlock mutex: %d", err);
return ret;
} }
static void call_state_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) static void call_state_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
@ -1754,21 +1872,44 @@ static void terminate_reason_cfg_changed(const struct bt_gatt_attr *attr, uint16
static ssize_t read_friendly_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, static ssize_t read_friendly_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset) uint16_t len, uint16_t offset)
{ {
const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr);
const struct bt_tbs_in_uri *friendly_name = &inst->friendly_name; struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)];
size_t val_len; ssize_t ret;
int err;
LOG_DBG("Index: 0x%02x call index 0x%02x, URI %s", inst_index(inst), err = k_mutex_lock(&inst->mutex, MUTEX_TIMEOUT);
friendly_name->call_index, friendly_name->uri); if (err != 0) {
LOG_DBG("Failed to lock mutex");
if (friendly_name->call_index == BT_TBS_FREE_CALL_INDEX) { return -EBUSY;
LOG_DBG("URI not set");
return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0);
} }
val_len = sizeof(friendly_name->call_index) + strlen(friendly_name->uri); if (offset != 0 && flags->call_friendly_name_dirty) {
LOG_DBG("Value dirty for %p", (void *)conn);
ret = BT_GATT_ERR(BT_TBS_ERR_VAL_CHANGED);
} else {
const struct bt_tbs_in_uri *friendly_name = &inst->friendly_name;
return bt_gatt_attr_read(conn, attr, buf, len, offset, friendly_name, val_len); flags->call_friendly_name_dirty = false;
LOG_DBG("Index: 0x%02x call index 0x%02x, URI %s", inst_index(inst),
friendly_name->call_index, friendly_name->uri);
if (friendly_name->call_index == BT_TBS_FREE_CALL_INDEX) {
LOG_DBG("URI not set");
ret = 0;
} else {
const size_t val_len =
sizeof(friendly_name->call_index) + strlen(friendly_name->uri);
ret = bt_gatt_attr_read(conn, attr, buf, len, offset, friendly_name,
val_len);
}
}
err = k_mutex_unlock(&inst->mutex);
__ASSERT(err == 0, "Failed to unlock mutex: %d", err);
return ret;
} }
static void friendly_name_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) static void friendly_name_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
@ -1783,22 +1924,44 @@ static void friendly_name_cfg_changed(const struct bt_gatt_attr *attr, uint16_t
static ssize_t read_incoming_call(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, static ssize_t read_incoming_call(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset) uint16_t len, uint16_t offset)
{ {
const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr);
const struct bt_tbs_in_uri *remote_uri = &inst->in_call; struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)];
size_t val_len; ssize_t ret;
int err;
LOG_DBG("Index: 0x%02x call index 0x%02x, URI %s", inst_index(inst), remote_uri->call_index, err = k_mutex_lock(&inst->mutex, MUTEX_TIMEOUT);
remote_uri->uri); if (err != 0) {
LOG_DBG("Failed to lock mutex");
if (remote_uri->call_index == BT_TBS_FREE_CALL_INDEX) { return -EBUSY;
LOG_DBG("URI not set");
return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0);
} }
val_len = sizeof(remote_uri->call_index) + strlen(remote_uri->uri); if (offset != 0 && flags->incoming_call_dirty) {
LOG_DBG("Value dirty for %p", (void *)conn);
ret = BT_GATT_ERR(BT_TBS_ERR_VAL_CHANGED);
} else {
const struct bt_tbs_in_uri *remote_uri = &inst->in_call;
return bt_gatt_attr_read(conn, attr, buf, len, offset, remote_uri, val_len); flags->incoming_call_dirty = false;
LOG_DBG("Index: 0x%02x call index 0x%02x, URI %s", inst_index(inst),
remote_uri->call_index, remote_uri->uri);
if (remote_uri->call_index == BT_TBS_FREE_CALL_INDEX) {
LOG_DBG("URI not set");
ret = 0;
} else {
const size_t val_len =
sizeof(remote_uri->call_index) + strlen(remote_uri->uri);
ret = bt_gatt_attr_read(conn, attr, buf, len, offset, remote_uri, val_len);
}
}
err = k_mutex_unlock(&inst->mutex);
__ASSERT(err == 0, "Failed to unlock mutex: %d", err);
return ret;
} }
static void in_call_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) static void in_call_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
@ -2511,6 +2674,7 @@ static void set_incoming_call_target_bearer_uri_changed_cb(struct tbs_flags *fla
} }
flags->incoming_call_target_bearer_uri_changed = true; flags->incoming_call_target_bearer_uri_changed = true;
flags->incoming_call_target_bearer_uri_dirty = true;
} }
static void set_incoming_call_changed_cb(struct tbs_flags *flags) static void set_incoming_call_changed_cb(struct tbs_flags *flags)
@ -2520,6 +2684,7 @@ static void set_incoming_call_changed_cb(struct tbs_flags *flags)
} }
flags->incoming_call_changed = true; flags->incoming_call_changed = true;
flags->incoming_call_dirty = true;
} }
static void set_call_friendly_name_changed_cb(struct tbs_flags *flags) static void set_call_friendly_name_changed_cb(struct tbs_flags *flags)
@ -2529,6 +2694,7 @@ static void set_call_friendly_name_changed_cb(struct tbs_flags *flags)
} }
flags->call_friendly_name_changed = true; flags->call_friendly_name_changed = true;
flags->call_friendly_name_dirty = true;
} }
static void tbs_inst_remote_incoming(struct tbs_inst *inst, const char *to, const char *from, static void tbs_inst_remote_incoming(struct tbs_inst *inst, const char *to, const char *from,
@ -2609,6 +2775,7 @@ int bt_tbs_remote_incoming(uint8_t bearer_index, const char *to, const char *fro
static void set_bearer_provider_name_changed_cb(struct tbs_flags *flags) static void set_bearer_provider_name_changed_cb(struct tbs_flags *flags)
{ {
flags->bearer_provider_name_changed = true; flags->bearer_provider_name_changed = true;
flags->bearer_provider_name_dirty = true;
} }
int bt_tbs_set_bearer_provider_name(uint8_t bearer_index, const char *name) int bt_tbs_set_bearer_provider_name(uint8_t bearer_index, const char *name)
@ -2747,6 +2914,7 @@ int bt_tbs_set_status_flags(uint8_t bearer_index, uint16_t status_flags)
static void set_bearer_uri_schemes_supported_list_changed_cb(struct tbs_flags *flags) static void set_bearer_uri_schemes_supported_list_changed_cb(struct tbs_flags *flags)
{ {
flags->bearer_uri_schemes_supported_list_changed = true; flags->bearer_uri_schemes_supported_list_changed = true;
flags->bearer_uri_schemes_supported_list_dirty = true;
} }
int bt_tbs_set_uri_scheme_list(uint8_t bearer_index, const char **uri_list, uint8_t uri_count) int bt_tbs_set_uri_scheme_list(uint8_t bearer_index, const char **uri_list, uint8_t uri_count)