diff --git a/apps/bttester/src/btp/btp_gatt.h b/apps/bttester/src/btp/btp_gatt.h index 6c57ade092..2a311c4874 100644 --- a/apps/bttester/src/btp/btp_gatt.h +++ b/apps/bttester/src/btp/btp_gatt.h @@ -311,7 +311,7 @@ struct btp_gatt_get_attribute_value_rp { struct btp_gatt_change_database_cmd { uint16_t start_handle; uint16_t end_handle; - uint8_t visibility; + uint8_t operation; } __packed; #define BTP_GATT_NOTIFY_MULTIPLE 0x21 diff --git a/apps/bttester/src/btp_gatt.c b/apps/bttester/src/btp_gatt.c index b0f21e07c4..2fdf770f8f 100644 --- a/apps/bttester/src/btp_gatt.c +++ b/apps/bttester/src/btp_gatt.c @@ -81,6 +81,7 @@ static uint8_t gatt_svr_pts_static_short_val; static uint16_t myconn_handle; uint16_t notify_handle; uint16_t notify_handle_alt; +static bool tester_service_visible = true; struct find_attr_data { ble_uuid_any_t *uuid; @@ -1977,12 +1978,88 @@ change_database(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len) { const struct btp_gatt_change_database_cmd *cp = cmd; + uint16_t start_handle, end_handle; - SYS_LOG_DBG("") + if (cmd_len < sizeof(*cp) || cp->operation > 0x02) { + return BTP_STATUS_FAILED; + } - ble_gatts_show_local(); + start_handle = cp->start_handle; + end_handle = cp->end_handle; + + /* Branch 1: Auto-select range */ + if (start_handle == 0 && end_handle == 0) { + uint16_t svc_start, svc_end; + int visible; + /* If provided handles are 0, it is up to IUT to select + * range that will be changed. Let's select last registred service. */ + if (ble_gatts_find_svc_range_by_handle(ble_att_svr_prev_handle(), + &svc_start, &svc_end) != 0) { + return BTP_STATUS_FAILED; + } + + /* Operation values are 0x00 = Remove; 0x01 = Add; 0x02 = Any */ + switch (cp->operation) { + case 0x00: + visible = 0; + break; + case 0x01: + visible = 1; + break; + case 0x02: + /* Toggle visibility based on cached state to avoid a no-op */ + visible = tester_service_visible ? 0 : 1; + break; + default: + return BTP_STATUS_FAILED; + } + if (ble_gatts_svc_set_visibility(svc_start, visible) != 0) { + return BTP_STATUS_FAILED; + } + + /* Update cached visibility to reflect the actual new state */ + tester_service_visible = (visible != 0); + + ble_svc_gatt_changed(svc_start, svc_end); + + return BTP_STATUS_SUCCESS; + } + + /* Branch 2: Explicit handle range validation */ + if (start_handle == 0 || end_handle < start_handle) { + return BTP_STATUS_FAILED; + } + + /* 0x01 = Add (1); 0x00 / 0x02 (Any) = Remove (0) per BTP spec permissions */ + int visible = (cp->operation == 0x01) ? 1 : 0; + uint16_t cur_handle, svc_start, svc_end; + + /* If requested range overlaps multiple services, call + * ble_gatts_svc_set_visibility for each overlapping service */ + for (cur_handle = start_handle; cur_handle <= end_handle; + cur_handle = svc_end + 1) { + if (ble_gatts_find_svc_range_by_handle(cur_handle, &svc_start, &svc_end) != 0) { + return BTP_STATUS_FAILED; + } + + /* Per Core Specification 6.3, the Service Changed value contains + * the start and end of the affected handle range.*/ + start_handle = svc_start; + + if (ble_gatts_svc_set_visibility(svc_start, visible) != 0) { + return BTP_STATUS_FAILED; + } + + if (svc_end == 0xffff) { + break; + } + } + + /* Per Core Specification 6.3, the Service Changed value contains + * the start and end of the affected handle range.*/ + end_handle = svc_end; - ble_svc_gatt_changed(cp->start_handle, cp->end_handle); + ble_svc_gatt_changed(start_handle, end_handle); return BTP_STATUS_SUCCESS; } diff --git a/nimble/host/include/host/ble_gatt.h b/nimble/host/include/host/ble_gatt.h index e74ba9e1a1..1397dad2f1 100644 --- a/nimble/host/include/host/ble_gatt.h +++ b/nimble/host/include/host/ble_gatt.h @@ -1127,6 +1127,19 @@ int ble_gatts_find_chr(const ble_uuid_t *svc_uuid, const ble_uuid_t *chr_uuid, int ble_gatts_find_dsc(const ble_uuid_t *svc_uuid, const ble_uuid_t *chr_uuid, const ble_uuid_t *dsc_uuid, uint16_t *out_dsc_handle); +/** + * Finds the service start and end handles for a given attribute handle. + * + * @param handle Attribute handle to search with + * @param out_start Pointer where the service start handle will be stored. + * @param out_end Pointer where the service end handle will be stored. + * + * @return 0 on success; + * BLE_HS_ENOENT if no matching service is found. + */ +int ble_gatts_find_svc_range_by_handle(uint16_t handle, uint16_t *out_start, + uint16_t *out_end); + /** Type definition for GATT service iteration callback function. */ typedef void (*ble_gatt_svc_foreach_fn)(const struct ble_gatt_svc_def *svc, uint16_t handle, diff --git a/nimble/host/src/ble_gatts.c b/nimble/host/src/ble_gatts.c index ac5576f6dc..613afa737c 100644 --- a/nimble/host/src/ble_gatts.c +++ b/nimble/host/src/ble_gatts.c @@ -2023,6 +2023,34 @@ ble_gatts_find_svc_entry(const ble_uuid_t *uuid) return NULL; } +int +ble_gatts_find_svc_range_by_handle(uint16_t handle, uint16_t *out_start, + uint16_t *out_end) +{ + if (handle == 0) { + return BLE_HS_ENOENT; + } + + for (int i = 0; i < ble_gatts_num_svc_entries; i++) { + struct ble_gatts_svc_entry *entry = &ble_gatts_svc_entries[i]; + + if (entry->handle != 0 && handle >= entry->handle && + handle <= entry->end_group_handle) { + + if (out_start != NULL) { + *out_start = entry->handle; + } + if (out_end != NULL) { + *out_end = entry->end_group_handle; + } + + return 0; + } + } + + return BLE_HS_ENOENT; +} + static int ble_gatts_find_svc_chr_attr(const ble_uuid_t *svc_uuid, const ble_uuid_t *chr_uuid,