diff --git a/cores/rp2040/sdkoverride/att_db.c b/cores/rp2040/sdkoverride/att_db.c new file mode 100644 index 000000000..47af11e71 --- /dev/null +++ b/cores/rp2040/sdkoverride/att_db.c @@ -0,0 +1,1863 @@ +/* + Copyright (C) 2014 BlueKitchen GmbH + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the names of + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + 4. Any redistribution, use, or modification is done solely for + personal benefit and not for any commercial purpose or for + monetary gain. + + THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Please inquire about commercial licensing options at + contact@bluekitchen-gmbh.com + +*/ + +#define BTSTACK_FILE__ "att_db.c" + +#if defined ENABLE_CLASSIC + +#include +#include +#include "ble/att_db.h" +#include "ble/core.h" + +#include "btstack_debug.h" +#include "btstack_util.h" + +// check for ENABLE_ATT_DELAYED_READ_RESPONSE -> ENABLE_ATT_DELAYED_RESPONSE, +#ifdef ENABLE_ATT_DELAYED_READ_RESPONSE +#error "ENABLE_ATT_DELAYED_READ_RESPONSE was replaced by ENABLE_ATT_DELAYED_RESPONSE. Please update btstack_config.h" +#endif + +typedef enum { + ATT_READ, + ATT_WRITE, +} att_operation_t; + + +static int is_Bluetooth_Base_UUID(uint8_t const *uuid) { + // Bluetooth Base UUID 00000000-0000-1000-8000-00805F9B34FB in little endian + static const uint8_t bluetooth_base_uuid[] = { 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + if (memcmp(&uuid[0], &bluetooth_base_uuid[0], 12) != 0) { + return false; + } + if (memcmp(&uuid[14], &bluetooth_base_uuid[14], 2) != 0) { + return false; + } + return true; + +} + +static uint16_t uuid16_from_uuid(uint16_t uuid_len, uint8_t * uuid) { + if (uuid_len == 2u) { + return little_endian_read_16(uuid, 0u); + } + if (!is_Bluetooth_Base_UUID(uuid)) { + return 0; + } + return little_endian_read_16(uuid, 12); +} + +// ATT Database + +// new java-style iterator +typedef struct att_iterator { + // private + uint8_t const * att_ptr; + // public + uint16_t size; + uint16_t flags; + uint16_t handle; + uint8_t const * uuid; + uint16_t value_len; + uint8_t const * value; +} att_iterator_t; + +static void att_persistent_ccc_cache(att_iterator_t * it); + +static uint8_t const * att_database = NULL; +static att_read_callback_t att_read_callback = NULL; +static att_write_callback_t att_write_callback = NULL; +static int att_prepare_write_error_code = 0; +static uint16_t att_prepare_write_error_handle = 0x0000; + +// single cache for att_is_persistent_ccc - stores flags before write callback +static uint16_t att_persistent_ccc_handle; +static uint16_t att_persistent_ccc_uuid16; + +static void att_iterator_init(att_iterator_t *it) { + it->att_ptr = att_database; +} + +static bool att_iterator_has_next(att_iterator_t *it) { + return it->att_ptr != NULL; +} + +static void att_iterator_fetch_next(att_iterator_t *it) { + it->size = little_endian_read_16(it->att_ptr, 0); + if (it->size == 0u) { + it->flags = 0; + it->handle = 0; + it->uuid = NULL; + it->value_len = 0; + it->value = NULL; + it->att_ptr = NULL; + return; + } + it->flags = little_endian_read_16(it->att_ptr, 2); + it->handle = little_endian_read_16(it->att_ptr, 4); + it->uuid = &it->att_ptr[6]; + // handle 128 bit UUIDs + if ((it->flags & (uint16_t)ATT_PROPERTY_UUID128) != 0u) { + it->value_len = it->size - 22u; + it->value = &it->att_ptr[22]; + } else { + it->value_len = it->size - 8u; + it->value = &it->att_ptr[8]; + } + // advance AFTER setting values + it->att_ptr += it->size; +} + +static int att_iterator_match_uuid16(att_iterator_t *it, uint16_t uuid) { + if (it->handle == 0u) { + return 0u; + } + if (it->flags & (uint16_t)ATT_PROPERTY_UUID128) { + if (!is_Bluetooth_Base_UUID(it->uuid)) { + return 0; + } + return little_endian_read_16(it->uuid, 12) == uuid; + } + return little_endian_read_16(it->uuid, 0) == uuid; +} + +static int att_iterator_match_uuid(att_iterator_t *it, uint8_t *uuid, uint16_t uuid_len) { + if (it->handle == 0u) { + return 0u; + } + // input: UUID16 + if (uuid_len == 2u) { + return att_iterator_match_uuid16(it, little_endian_read_16(uuid, 0)); + } + // input and db: UUID128 + if ((it->flags & (uint16_t)ATT_PROPERTY_UUID128) != 0u) { + return memcmp(it->uuid, uuid, 16) == 0; + } + // input: UUID128, db: UUID16 + if (!is_Bluetooth_Base_UUID(uuid)) { + return 0; + } + return little_endian_read_16(uuid, 12) == little_endian_read_16(it->uuid, 0); +} + + +static int att_find_handle(att_iterator_t *it, uint16_t handle) { + if (handle == 0u) { + return 0u; + } + att_iterator_init(it); + while (att_iterator_has_next(it)) { + att_iterator_fetch_next(it); + if (it->handle != handle) { + continue; + } + return 1; + } + return 0; +} + +// experimental client API +uint16_t att_uuid_for_handle(uint16_t attribute_handle) { + att_iterator_t it; + int ok = att_find_handle(&it, attribute_handle); + if (!ok) { + return 0u; + } + if ((it.flags & (uint16_t)ATT_PROPERTY_UUID128) != 0u) { + return 0u; + } + return little_endian_read_16(it.uuid, 0); +} + +const uint8_t * gatt_server_get_const_value_for_handle(uint16_t attribute_handle, uint16_t * out_value_len) { + att_iterator_t it; + int ok = att_find_handle(&it, attribute_handle); + if (!ok) { + return 0u; + } + if ((it.flags & (uint16_t)ATT_PROPERTY_DYNAMIC) != 0u) { + return 0u; + } + *out_value_len = it.value_len; + return it.value; +} + +// end of client API + +static void att_update_value_len(att_iterator_t *it, uint16_t offset, hci_con_handle_t con_handle) { + if ((it->flags & (uint16_t)ATT_PROPERTY_DYNAMIC) == 0u) { + return; + } + it->value_len = (*att_read_callback)(con_handle, it->handle, offset, NULL, 0); + return; +} + +// copy attribute value from offset into buffer with given size +static int att_copy_value(att_iterator_t *it, uint16_t offset, uint8_t * buffer, uint16_t buffer_size, hci_con_handle_t con_handle) { + + // DYNAMIC + if ((it->flags & (uint16_t)ATT_PROPERTY_DYNAMIC) != 0u) { + return (*att_read_callback)(con_handle, it->handle, offset, buffer, buffer_size); + } + + // STATIC + uint16_t bytes_to_copy = btstack_min(it->value_len - offset, buffer_size); + (void)memcpy(buffer, it->value, bytes_to_copy); + return bytes_to_copy; +} + +void att_set_db(uint8_t const * db) { + // validate db version + if (db == NULL) { + return; + } + if (*db != (uint8_t)ATT_DB_VERSION) { + log_error("ATT DB version differs, please regenerate .h from .gatt file or update att_db_util.c"); + return; + } + log_info("att_set_db %p", db); + // ignore db version + att_database = &db[1]; +} + +void att_set_read_callback(att_read_callback_t callback) { + att_read_callback = callback; +} + +void att_set_write_callback(att_write_callback_t callback) { + att_write_callback = callback; +} + +void att_dump_attributes(void) { + att_iterator_t it; + att_iterator_init(&it); + uint8_t uuid128[16]; + log_info("att_dump_attributes, table %p", att_database); + while (att_iterator_has_next(&it)) { + att_iterator_fetch_next(&it); + if (it.handle == 0u) { + log_info("Handle: END"); + return; + } + log_info("Handle: 0x%04x, flags: 0x%04x, uuid: ", it.handle, it.flags); + if ((it.flags & (uint16_t)ATT_PROPERTY_UUID128) != 0u) { + reverse_128(it.uuid, uuid128); + log_info("%s", uuid128_to_str(uuid128)); + } else { + log_info("%04x", little_endian_read_16(it.uuid, 0)); + } + log_info(", value_len: %u, value: ", it.value_len); + log_info_hexdump(it.value, it.value_len); + } +} + +static void att_prepare_write_reset(void) { + att_prepare_write_error_code = 0; + att_prepare_write_error_handle = 0x0000; +} + +static void att_prepare_write_update_errors(uint8_t error_code, uint16_t handle) { + // first ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LENGTH has highest priority + if ((error_code == (uint8_t)ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LENGTH) && (error_code != (uint8_t)att_prepare_write_error_code)) { + att_prepare_write_error_code = error_code; + att_prepare_write_error_handle = handle; + return; + } + // first ATT_ERROR_INVALID_OFFSET is next + if ((error_code == (uint8_t)ATT_ERROR_INVALID_OFFSET) && (att_prepare_write_error_code == 0)) { + att_prepare_write_error_code = error_code; + att_prepare_write_error_handle = handle; + return; + } +} + +static uint16_t setup_error(uint8_t * response_buffer, uint8_t request_opcode, uint16_t handle, uint8_t error_code) { + response_buffer[0] = (uint8_t)ATT_ERROR_RESPONSE; + response_buffer[1] = request_opcode; + little_endian_store_16(response_buffer, 2, handle); + response_buffer[4] = error_code; + return 5; +} + +static inline uint16_t setup_error_read_not_permitted(uint8_t * response_buffer, uint8_t request_opcode, uint16_t start_handle) { + return setup_error(response_buffer, request_opcode, start_handle, ATT_ERROR_READ_NOT_PERMITTED); +} + +static inline uint16_t setup_error_write_not_permitted(uint8_t * response_buffer, uint8_t request, uint16_t start_handle) { + return setup_error(response_buffer, request, start_handle, ATT_ERROR_WRITE_NOT_PERMITTED); +} + +static inline uint16_t setup_error_atribute_not_found(uint8_t * response_buffer, uint8_t request_opcode, uint16_t start_handle) { + return setup_error(response_buffer, request_opcode, start_handle, ATT_ERROR_ATTRIBUTE_NOT_FOUND); +} + +static inline uint16_t setup_error_invalid_handle(uint8_t * response_buffer, uint8_t request_opcode, uint16_t handle) { + return setup_error(response_buffer, request_opcode, handle, ATT_ERROR_INVALID_HANDLE); +} + +static inline uint16_t setup_error_invalid_offset(uint8_t * response_buffer, uint8_t request_opcode, uint16_t handle) { + return setup_error(response_buffer, request_opcode, handle, ATT_ERROR_INVALID_OFFSET); +} + +static inline uint16_t setup_error_invalid_pdu(uint8_t *response_buffer, uint8_t request_opcode) { + return setup_error(response_buffer, request_opcode, 0, ATT_ERROR_INVALID_PDU); +} + +struct att_security_settings { + uint8_t required_security_level; + bool requires_secure_connection; +}; + +static void att_validate_security_get_settings(struct att_security_settings * security_settings, att_operation_t operation, att_iterator_t *it) { + security_settings->required_security_level = 0u; + security_settings->requires_secure_connection = false; + switch (operation) { + case ATT_READ: + if ((it->flags & (uint16_t)ATT_PROPERTY_READ_PERMISSION_BIT_0) != 0u) { + security_settings->required_security_level |= 1u; + } + if ((it->flags & (uint16_t)ATT_PROPERTY_READ_PERMISSION_BIT_1) != 0u) { + security_settings->required_security_level |= 2u; + } + if ((it->flags & (uint16_t)ATT_PROPERTY_READ_PERMISSION_SC) != 0u) { + security_settings->requires_secure_connection = true; + } + break; + case ATT_WRITE: + if ((it->flags & (uint16_t)ATT_PROPERTY_WRITE_PERMISSION_BIT_0) != 0u) { + security_settings->required_security_level |= 1u; + } + if ((it->flags & (uint16_t)ATT_PROPERTY_WRITE_PERMISSION_BIT_1) != 0u) { + security_settings->required_security_level |= 2u; + } + if ((it->flags & (uint16_t)ATT_PROPERTY_WRITE_PERMISSION_SC) != 0u) { + security_settings->requires_secure_connection = true; + } + break; + default: + btstack_assert(false); + break; + } +} + +static uint8_t att_validate_security(att_connection_t * att_connection, att_operation_t operation, att_iterator_t * it) { + struct att_security_settings security_settings; + att_validate_security_get_settings(&security_settings, operation, it); + + uint8_t required_encryption_size = (uint8_t)(it->flags >> 12); + if (required_encryption_size != 0u) { + required_encryption_size++; // store -1 to fit into 4 bit + } + log_debug("att_validate_security. flags 0x%04x (=> security level %u, key size %u) authorized %u, authenticated %u, encryption_key_size %u, secure connection %u", + it->flags, security_settings.required_security_level, required_encryption_size, att_connection->authorized, att_connection->authenticated, att_connection->encryption_key_size, att_connection->secure_connection); + + bool sc_missing = security_settings.requires_secure_connection && (att_connection->secure_connection == 0u); + switch (security_settings.required_security_level) { + case ATT_SECURITY_AUTHORIZED: + if ((att_connection->authorized == 0u) || sc_missing) { + return ATT_ERROR_INSUFFICIENT_AUTHORIZATION; + } + /* fall through */ + case ATT_SECURITY_AUTHENTICATED: + if ((att_connection->authenticated == 0u) || sc_missing) { + return ATT_ERROR_INSUFFICIENT_AUTHENTICATION; + } + /* fall through */ + case ATT_SECURITY_ENCRYPTED: + if ((required_encryption_size > 0u) && ((att_connection->encryption_key_size == 0u) || sc_missing)) { + return ATT_ERROR_INSUFFICIENT_ENCRYPTION; + } + if (required_encryption_size > att_connection->encryption_key_size) { + return ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE; + } + break; + default: + break; + } + return ATT_ERROR_SUCCESS; +} + +// +// MARK: ATT_EXCHANGE_MTU_REQUEST +// +static uint16_t handle_exchange_mtu_request(att_connection_t * att_connection, uint8_t * request_buffer, uint16_t request_len, + uint8_t * response_buffer) { + + if (request_len != 3u) { + return setup_error_invalid_pdu(response_buffer, ATT_EXCHANGE_MTU_REQUEST); + } + + uint16_t client_rx_mtu = little_endian_read_16(request_buffer, 1); + + // find min(local max mtu, remote mtu) >= ATT_DEFAULT_MTU and use as mtu for this connection + uint16_t min_mtu = btstack_min(client_rx_mtu, att_connection->max_mtu); + uint16_t new_mtu = btstack_max(ATT_DEFAULT_MTU, min_mtu); + att_connection->mtu_exchanged = true; + att_connection->mtu = new_mtu; + + response_buffer[0] = ATT_EXCHANGE_MTU_RESPONSE; + little_endian_store_16(response_buffer, 1, att_connection->mtu); + return 3; +} + + +// +// MARK: ATT_FIND_INFORMATION_REQUEST +// +// TODO: handle other types then GATT_PRIMARY_SERVICE_UUID and GATT_SECONDARY_SERVICE_UUID +// +static uint16_t handle_find_information_request2(att_connection_t * att_connection, uint8_t * response_buffer, uint16_t response_buffer_size, + uint16_t start_handle, uint16_t end_handle) { + + UNUSED(att_connection); + + log_info("ATT_FIND_INFORMATION_REQUEST: from %04X to %04X", start_handle, end_handle); + uint8_t request_type = ATT_FIND_INFORMATION_REQUEST; + + if ((start_handle > end_handle) || (start_handle == 0u)) { + return setup_error_invalid_handle(response_buffer, request_type, start_handle); + } + + uint16_t offset = 1; + uint16_t uuid_len = 0; + + att_iterator_t it; + att_iterator_init(&it); + while (att_iterator_has_next(&it)) { + att_iterator_fetch_next(&it); + if (!it.handle) { + break; + } + if (it.handle > end_handle) { + break; + } + if (it.handle < start_handle) { + continue; + } + + // log_info("Handle 0x%04x", it.handle); + + uint16_t this_uuid_len = (it.flags & (uint16_t)ATT_PROPERTY_UUID128) ? 16u : 2u; + + // check if value has same len as last one if not first result + if (offset > 1u) { + if (this_uuid_len != uuid_len) { + break; + } + } + + // first + if (offset == 1u) { + uuid_len = this_uuid_len; + // set format field + response_buffer[offset] = (it.flags & (uint16_t)ATT_PROPERTY_UUID128) ? 0x02u : 0x01u; + offset++; + } + + // space? + if ((offset + 2u + uuid_len) > response_buffer_size) { + break; + } + + // store + little_endian_store_16(response_buffer, offset, it.handle); + offset += 2u; + + (void)memcpy(response_buffer + offset, it.uuid, uuid_len); + offset += uuid_len; + } + + if (offset == 1u) { + return setup_error_atribute_not_found(response_buffer, request_type, start_handle); + } + + response_buffer[0] = ATT_FIND_INFORMATION_REPLY; + return offset; +} + +static uint16_t handle_find_information_request(att_connection_t * att_connection, uint8_t * request_buffer, uint16_t request_len, + uint8_t * response_buffer, uint16_t response_buffer_size) { + + if (request_len != 5u) { + return setup_error_invalid_pdu(response_buffer, ATT_FIND_INFORMATION_REQUEST); + } + + uint16_t start_handle = little_endian_read_16(request_buffer, 1); + uint16_t end_handle = little_endian_read_16(request_buffer, 3); + return handle_find_information_request2(att_connection, response_buffer, response_buffer_size, start_handle, end_handle); +} + +// +// MARK: ATT_FIND_BY_TYPE_VALUE +// +// "Only attributes with attribute handles between and including the Starting Handle parameter +// and the Ending Handle parameter that match the requested attri- bute type and the attribute +// value that have sufficient permissions to allow reading will be returned" -> (1) +// +// TODO: handle other types then GATT_PRIMARY_SERVICE_UUID and GATT_SECONDARY_SERVICE_UUID +// +// NOTE: doesn't handle DYNAMIC values +// NOTE: only supports 16 bit UUIDs +// +static uint16_t handle_find_by_type_value_request(att_connection_t * att_connection, uint8_t * request_buffer, uint16_t request_len, + uint8_t * response_buffer, uint16_t response_buffer_size) { + UNUSED(att_connection); + + if (request_len < 7u) { + return setup_error_invalid_pdu(response_buffer, ATT_FIND_BY_TYPE_VALUE_REQUEST); + } + + // parse request + uint16_t start_handle = little_endian_read_16(request_buffer, 1); + uint16_t end_handle = little_endian_read_16(request_buffer, 3); + uint16_t attribute_type = little_endian_read_16(request_buffer, 5); + const uint8_t *attribute_value = &request_buffer[7]; + uint16_t attribute_len = request_len - 7u; + + log_info("ATT_FIND_BY_TYPE_VALUE_REQUEST: from %04X to %04X, type %04X, value: ", start_handle, end_handle, attribute_type); + log_info_hexdump(attribute_value, attribute_len); + uint8_t request_type = ATT_FIND_BY_TYPE_VALUE_REQUEST; + + if ((start_handle > end_handle) || (start_handle == 0u)) { + return setup_error_invalid_handle(response_buffer, request_type, start_handle); + } + + uint16_t offset = 1; + bool in_group = false; + uint16_t prev_handle = 0; + + att_iterator_t it; + att_iterator_init(&it); + while (att_iterator_has_next(&it)) { + att_iterator_fetch_next(&it); + + if ((it.handle != 0u) && (it.handle < start_handle)) { + continue; + } + if (it.handle > end_handle) { + break; // (1) + } + + // close current tag, if within a group and a new service definition starts or we reach end of att db + if (in_group && + ((it.handle == 0u) || att_iterator_match_uuid16(&it, GATT_PRIMARY_SERVICE_UUID) || att_iterator_match_uuid16(&it, GATT_SECONDARY_SERVICE_UUID))) { + + log_info("End of group, handle 0x%04x", prev_handle); + little_endian_store_16(response_buffer, offset, prev_handle); + offset += 2u; + in_group = false; + + // check if space for another handle pair available + if ((offset + 4u) > response_buffer_size) { + break; + } + } + + // keep track of previous handle + prev_handle = it.handle; + + // does current attribute match + if ((it.handle != 0u) && att_iterator_match_uuid16(&it, attribute_type) && (attribute_len == it.value_len) && (memcmp(attribute_value, it.value, it.value_len) == 0)) { + log_info("Begin of group, handle 0x%04x", it.handle); + little_endian_store_16(response_buffer, offset, it.handle); + offset += 2u; + in_group = true; + } + } + + if (offset == 1u) { + return setup_error_atribute_not_found(response_buffer, request_type, start_handle); + } + + response_buffer[0] = ATT_FIND_BY_TYPE_VALUE_RESPONSE; + return offset; +} + +// +// MARK: ATT_READ_BY_TYPE_REQUEST +// +static uint16_t handle_read_by_type_request2(att_connection_t * att_connection, uint8_t * response_buffer, uint16_t response_buffer_size, + uint16_t start_handle, uint16_t end_handle, + uint16_t attribute_type_len, uint8_t * attribute_type) { + + log_info("ATT_READ_BY_TYPE_REQUEST: from %04X to %04X, type: ", start_handle, end_handle); + log_info_hexdump(attribute_type, attribute_type_len); + uint8_t request_type = ATT_READ_BY_TYPE_REQUEST; + + if ((start_handle > end_handle) || (start_handle == 0u)) { + return setup_error_invalid_handle(response_buffer, request_type, start_handle); + } + + uint16_t offset = 1; + uint16_t pair_len = 0; + + att_iterator_t it; + att_iterator_init(&it); + uint8_t error_code = 0; + uint16_t first_matching_but_unreadable_handle = 0; + + while (att_iterator_has_next(&it)) { + att_iterator_fetch_next(&it); + + if ((it.handle == 0u) || (it.handle > end_handle)) { + break; + } + + // does current attribute match + if ((it.handle < start_handle) || !att_iterator_match_uuid(&it, attribute_type, attribute_type_len)) { + continue; + } + + // skip handles that cannot be read but remember that there has been at least one + if ((it.flags & (uint16_t)ATT_PROPERTY_READ) == 0u) { + if (first_matching_but_unreadable_handle == 0u) { + first_matching_but_unreadable_handle = it.handle; + } + continue; + } + + // check security requirements + error_code = att_validate_security(att_connection, ATT_READ, &it); + if (error_code != 0u) { + break; + } + + att_update_value_len(&it, 0, att_connection->con_handle); + +#ifdef ENABLE_ATT_DELAYED_RESPONSE + if (it.value_len == (uint16_t)ATT_READ_RESPONSE_PENDING) { + return ATT_READ_RESPONSE_PENDING; + } +#endif + + // allow to return ATT Error Code in ATT Read Callback + if (it.value_len > (uint16_t)ATT_READ_ERROR_CODE_OFFSET) { + error_code = (uint8_t)(it.value_len - (uint16_t)ATT_READ_ERROR_CODE_OFFSET); + break; + } + + // check if value has same len as last one + uint16_t this_pair_len = 2u + it.value_len; + if ((offset > 1u) && (pair_len != this_pair_len)) { + break; + } + + // first + if (offset == 1u) { + pair_len = this_pair_len; + response_buffer[offset] = (uint8_t) pair_len; + offset++; + } + + // space? + if ((offset + pair_len) > response_buffer_size) { + if (offset > 2u) { + break; + } + it.value_len = response_buffer_size - 4u; + response_buffer[1u] = 2u + it.value_len; + } + + // store + little_endian_store_16(response_buffer, offset, it.handle); + offset += 2u; + uint16_t bytes_copied = att_copy_value(&it, 0, response_buffer + offset, it.value_len, att_connection->con_handle); + offset += bytes_copied; + } + + // at least one attribute could be read + if (offset > 1u) { + response_buffer[0] = ATT_READ_BY_TYPE_RESPONSE; + return offset; + } + + // first attribute had an error + if (error_code != 0u) { + return setup_error(response_buffer, request_type, start_handle, error_code); + } + + // no other errors, but all found attributes had been non-readable + if (first_matching_but_unreadable_handle != 0u) { + return setup_error_read_not_permitted(response_buffer, request_type, first_matching_but_unreadable_handle); + } + + // attribute not found + return setup_error_atribute_not_found(response_buffer, request_type, start_handle); +} + +static uint16_t handle_read_by_type_request(att_connection_t * att_connection, uint8_t * request_buffer, uint16_t request_len, + uint8_t * response_buffer, uint16_t response_buffer_size) { + + uint16_t attribute_type_len; + switch (request_len) { + case 7: + attribute_type_len = 2; + break; + case 21: + attribute_type_len = 16; + break; + default: + return setup_error_invalid_pdu(response_buffer, ATT_READ_BY_TYPE_REQUEST); + } + + uint16_t start_handle = little_endian_read_16(request_buffer, 1); + uint16_t end_handle = little_endian_read_16(request_buffer, 3); + return handle_read_by_type_request2(att_connection, response_buffer, response_buffer_size, start_handle, end_handle, attribute_type_len, &request_buffer[5]); +} + +// +// MARK: ATT_READ_BY_TYPE_REQUEST +// +static uint16_t handle_read_request2(att_connection_t * att_connection, uint8_t * response_buffer, uint16_t response_buffer_size, uint16_t handle) { + + log_info("ATT_READ_REQUEST: handle %04x", handle); + uint8_t request_type = ATT_READ_REQUEST; + + att_iterator_t it; + int ok = att_find_handle(&it, handle); + if (!ok) { + return setup_error_invalid_handle(response_buffer, request_type, handle); + } + + // check if handle can be read + if ((it.flags & (uint16_t)ATT_PROPERTY_READ) == 0u) { + return setup_error_read_not_permitted(response_buffer, request_type, handle); + } + + // check security requirements + uint8_t error_code = att_validate_security(att_connection, ATT_READ, &it); + if (error_code != 0u) { + return setup_error(response_buffer, request_type, handle, error_code); + } + + att_update_value_len(&it, 0, att_connection->con_handle); + +#ifdef ENABLE_ATT_DELAYED_RESPONSE + if (it.value_len == (uint16_t)ATT_READ_RESPONSE_PENDING) { + return ATT_READ_RESPONSE_PENDING; + } +#endif + + // allow to return ATT Error Code in ATT Read Callback + if (it.value_len > (uint16_t)ATT_READ_ERROR_CODE_OFFSET) { + error_code = (uint8_t)(it.value_len - (uint16_t)ATT_READ_ERROR_CODE_OFFSET); + return setup_error(response_buffer, request_type, handle, error_code); + } + + // store + uint16_t offset = 1; + uint16_t bytes_copied = att_copy_value(&it, 0, response_buffer + offset, response_buffer_size - offset, att_connection->con_handle); + offset += bytes_copied; + + response_buffer[0] = ATT_READ_RESPONSE; + return offset; +} + +static uint16_t handle_read_request(att_connection_t * att_connection, uint8_t * request_buffer, uint16_t request_len, + uint8_t * response_buffer, uint16_t response_buffer_size) { + + if (request_len != 3u) { + return setup_error_invalid_pdu(response_buffer, ATT_READ_REQUEST); + } + + uint16_t handle = little_endian_read_16(request_buffer, 1); + return handle_read_request2(att_connection, response_buffer, response_buffer_size, handle); +} + +//s +// MARK: ATT_READ_BLOB_REQUEST 0x0c +// +static uint16_t handle_read_blob_request2(att_connection_t * att_connection, uint8_t * response_buffer, uint16_t response_buffer_size, uint16_t handle, uint16_t value_offset) { + log_info("ATT_READ_BLOB_REQUEST: handle %04x, offset %u", handle, value_offset); + uint8_t request_type = ATT_READ_BLOB_REQUEST; + + att_iterator_t it; + int ok = att_find_handle(&it, handle); + if (!ok) { + return setup_error_invalid_handle(response_buffer, request_type, handle); + } + + // check if handle can be read + if ((it.flags & (uint16_t)ATT_PROPERTY_READ) == 0u) { + return setup_error_read_not_permitted(response_buffer, request_type, handle); + } + + // check security requirements + uint8_t error_code = att_validate_security(att_connection, ATT_READ, &it); + if (error_code != 0u) { + return setup_error(response_buffer, request_type, handle, error_code); + } + + att_update_value_len(&it, value_offset, att_connection->con_handle); + +#ifdef ENABLE_ATT_DELAYED_RESPONSE + if (it.value_len == (uint16_t)ATT_READ_RESPONSE_PENDING) { + return ATT_READ_RESPONSE_PENDING; + } +#endif + + // allow to return ATT Error Code in ATT Read Callback + if (it.value_len > (uint16_t)ATT_READ_ERROR_CODE_OFFSET) { + error_code = (uint8_t)(it.value_len - (uint16_t)ATT_READ_ERROR_CODE_OFFSET); + return setup_error(response_buffer, request_type, handle, error_code); + } + + if (value_offset > it.value_len) { + return setup_error_invalid_offset(response_buffer, request_type, handle); + } + + // prepare response + response_buffer[0] = ATT_READ_BLOB_RESPONSE; + uint16_t offset = 1; + + // fetch more data if available + if (value_offset < it.value_len) { + uint16_t bytes_copied = att_copy_value(&it, value_offset, &response_buffer[offset], response_buffer_size - offset, att_connection->con_handle); + offset += bytes_copied; + } + return offset; +} + +static uint16_t handle_read_blob_request(att_connection_t * att_connection, uint8_t * request_buffer, uint16_t request_len, + uint8_t * response_buffer, uint16_t response_buffer_size) { + + if (request_len != 5u) { + return setup_error_invalid_pdu(response_buffer, ATT_READ_BLOB_REQUEST); + } + + uint16_t handle = little_endian_read_16(request_buffer, 1); + uint16_t value_offset = little_endian_read_16(request_buffer, 3); + return handle_read_blob_request2(att_connection, response_buffer, response_buffer_size, handle, value_offset); +} + +// +// MARK: ATT_READ_MULTIPLE_REQUEST 0x0e +// MARK: ATT_READ_MULTIPLE_REQUEST 0x20 +// +static uint16_t handle_read_multiple_request2(att_connection_t * att_connection, uint8_t * response_buffer, uint16_t response_buffer_size, uint16_t num_handles, uint8_t * handles, bool store_length) { + log_info("ATT_READ_MULTIPLE_(VARIABLE_)REQUEST: num handles %u", num_handles); + uint8_t request_type = store_length ? ATT_READ_MULTIPLE_VARIABLE_REQ : ATT_READ_MULTIPLE_REQUEST; + uint8_t response_type = store_length ? ATT_READ_MULTIPLE_VARIABLE_RSP : ATT_READ_MULTIPLE_RESPONSE; + uint16_t offset = 1; + + uint16_t i; + uint8_t error_code = 0; + uint16_t handle = 0; + +#ifdef ENABLE_ATT_DELAYED_RESPONSE + bool read_request_pending = false; +#endif + + for (i = 0; i < num_handles; i++) { + handle = little_endian_read_16(handles, i << 1); + + if (handle == 0u) { + return setup_error_invalid_handle(response_buffer, request_type, handle); + } + + att_iterator_t it; + + int ok = att_find_handle(&it, handle); + if (!ok) { + return setup_error_invalid_handle(response_buffer, request_type, handle); + } + + // check if handle can be read + if ((it.flags & (uint16_t)ATT_PROPERTY_READ) == 0u) { + error_code = (uint8_t)ATT_ERROR_READ_NOT_PERMITTED; + break; + } + + // check security requirements + error_code = att_validate_security(att_connection, ATT_READ, &it); + if (error_code != 0u) { + break; + } + + att_update_value_len(&it, 0, att_connection->con_handle); + +#ifdef ENABLE_ATT_DELAYED_RESPONSE + if (it.value_len == (uint16_t)ATT_READ_RESPONSE_PENDING) { + read_request_pending = true; + } + if (read_request_pending) { + continue; + } +#endif + + // allow to return ATT Error Code in ATT Read Callback + if (it.value_len > (uint16_t)ATT_READ_ERROR_CODE_OFFSET) { + error_code = (uint8_t)(it.value_len - (uint16_t)ATT_READ_ERROR_CODE_OFFSET); + break; + } + +#ifdef ENABLE_GATT_OVER_EATT + // assert that at least Value Length can be stored + if (store_length && ((offset + 2) >= response_buffer_size)) { + break; + } + // skip length field + uint16_t offset_value_length = offset; + if (store_length) { + offset += 2; + } +#endif + // store data + uint16_t bytes_copied = att_copy_value(&it, 0, response_buffer + offset, response_buffer_size - offset, att_connection->con_handle); + offset += bytes_copied; +#ifdef ENABLE_GATT_OVER_EATT + // set length field + if (store_length) { + little_endian_store_16(response_buffer, offset_value_length, bytes_copied); + } +#endif + } + + if (error_code != 0u) { + return setup_error(response_buffer, request_type, handle, error_code); + } + + response_buffer[0] = (uint8_t)response_type; + return offset; +} + +static uint16_t +handle_read_multiple_request(att_connection_t *att_connection, uint8_t *request_buffer, uint16_t request_len, + uint8_t *response_buffer, uint16_t response_buffer_size, bool store_length) { + + uint8_t request_type = store_length ? ATT_READ_MULTIPLE_VARIABLE_REQ : ATT_READ_MULTIPLE_REQUEST; + + // 1 byte opcode + two or more attribute handles (2 bytes each) + if ((request_len < 5u) || ((request_len & 1u) == 0u)) { + return setup_error_invalid_pdu(response_buffer, request_type); + } + + uint8_t num_handles = (request_len - 1u) >> 1u; + return handle_read_multiple_request2(att_connection, response_buffer, response_buffer_size, num_handles, + &request_buffer[1], store_length); +} + +// +// MARK: ATT_READ_BY_GROUP_TYPE_REQUEST 0x10 +// +// Only handles GATT_PRIMARY_SERVICE_UUID and GATT_SECONDARY_SERVICE_UUID +// Core v4.0, vol 3, part g, 2.5.3 +// "The «Primary Service» and «Secondary Service» grouping types may be used in the Read By Group Type Request. +// The «Characteristic» grouping type shall not be used in the ATT Read By Group Type Request." +// +// NOTE: doesn't handle DYNAMIC values +// +// NOTE: we don't check for security as PRIMARY and SECONDARY SERVICE definition shouldn't be protected +// Core 4.0, vol 3, part g, 8.1 +// "The list of services and characteristics that a device supports is not considered private or +// confidential information, and therefore the Service and Characteristic Discovery procedures +// shall always be permitted. " +// +static uint16_t handle_read_by_group_type_request2(att_connection_t * att_connection, uint8_t * response_buffer, uint16_t response_buffer_size, + uint16_t start_handle, uint16_t end_handle, + uint16_t attribute_type_len, uint8_t * attribute_type) { + + UNUSED(att_connection); + + log_info("ATT_READ_BY_GROUP_TYPE_REQUEST: from %04X to %04X, buffer size %u, type: ", start_handle, end_handle, response_buffer_size); + log_info_hexdump(attribute_type, attribute_type_len); + uint8_t request_type = ATT_READ_BY_GROUP_TYPE_REQUEST; + + if ((start_handle > end_handle) || (start_handle == 0u)) { + return setup_error_invalid_handle(response_buffer, request_type, start_handle); + } + + // assert UUID is primary or secondary service uuid + uint16_t uuid16 = uuid16_from_uuid(attribute_type_len, attribute_type); + if ((uuid16 != (uint16_t)GATT_PRIMARY_SERVICE_UUID) && (uuid16 != (uint16_t)GATT_SECONDARY_SERVICE_UUID)) { + return setup_error(response_buffer, request_type, start_handle, ATT_ERROR_UNSUPPORTED_GROUP_TYPE); + } + + uint16_t offset = 1; + uint16_t pair_len = 0; + bool in_group = false; + uint16_t group_start_handle = 0; + uint8_t const * group_start_value = NULL; + uint16_t prev_handle = 0; + + att_iterator_t it; + att_iterator_init(&it); + while (att_iterator_has_next(&it)) { + att_iterator_fetch_next(&it); + + if ((it.handle != 0u) && (it.handle < start_handle)) { + continue; + } + if (it.handle > end_handle) { + break; // (1) + } + + // log_info("Handle 0x%04x", it.handle); + + // close current tag, if within a group and a new service definition starts or we reach end of att db + if (in_group && + ((it.handle == 0u) || att_iterator_match_uuid16(&it, GATT_PRIMARY_SERVICE_UUID) || att_iterator_match_uuid16(&it, GATT_SECONDARY_SERVICE_UUID))) { + // log_info("End of group, handle 0x%04x, val_len: %u", prev_handle, pair_len - 4); + + little_endian_store_16(response_buffer, offset, group_start_handle); + offset += 2u; + little_endian_store_16(response_buffer, offset, prev_handle); + offset += 2u; + (void)memcpy(response_buffer + offset, group_start_value, + pair_len - 4u); + offset += pair_len - 4u; + in_group = false; + + // check if space for another handle pair available + if ((offset + pair_len) > response_buffer_size) { + break; + } + } + + // keep track of previous handle + prev_handle = it.handle; + + // does current attribute match + // log_info("compare: %04x == %04x", *(uint16_t*) context->attribute_type, *(uint16_t*) uuid); + if ((it.handle != 0u) && att_iterator_match_uuid(&it, attribute_type, attribute_type_len)) { + + // check if value has same len as last one + uint16_t this_pair_len = 4u + it.value_len; + if (offset > 1u) { + if (this_pair_len != pair_len) { + break; + } + } + + // log_info("Begin of group, handle 0x%04x", it.handle); + + // first + if (offset == 1u) { + pair_len = this_pair_len; + response_buffer[offset] = (uint8_t) this_pair_len; + offset++; + } + + group_start_handle = it.handle; + group_start_value = it.value; + in_group = true; + } + } + + if (offset == 1u) { + return setup_error_atribute_not_found(response_buffer, request_type, start_handle); + } + + response_buffer[0] = ATT_READ_BY_GROUP_TYPE_RESPONSE; + return offset; +} + +static uint16_t handle_read_by_group_type_request(att_connection_t * att_connection, uint8_t * request_buffer, uint16_t request_len, + uint8_t * response_buffer, uint16_t response_buffer_size) { + uint16_t attribute_type_len; + switch (request_len) { + case 7: + attribute_type_len = 2; + break; + case 21: + attribute_type_len = 16; + break; + default: + return setup_error_invalid_pdu(response_buffer, ATT_READ_BY_GROUP_TYPE_REQUEST); + } + + uint16_t start_handle = little_endian_read_16(request_buffer, 1); + uint16_t end_handle = little_endian_read_16(request_buffer, 3); + return handle_read_by_group_type_request2(att_connection, response_buffer, response_buffer_size, start_handle, end_handle, attribute_type_len, &request_buffer[5]); +} + +// +// MARK: ATT_WRITE_REQUEST 0x12 +static uint16_t handle_write_request(att_connection_t * att_connection, uint8_t * request_buffer, uint16_t request_len, + uint8_t * response_buffer, uint16_t response_buffer_size) { + + UNUSED(response_buffer_size); + + if (request_len < 3u) { + return setup_error_invalid_pdu(response_buffer, ATT_WRITE_REQUEST); + } + + uint8_t request_type = ATT_WRITE_REQUEST; + + uint16_t handle = little_endian_read_16(request_buffer, 1); + att_iterator_t it; + int ok = att_find_handle(&it, handle); + if (!ok) { + return setup_error_invalid_handle(response_buffer, request_type, handle); + } + if (att_write_callback == NULL) { + return setup_error_write_not_permitted(response_buffer, request_type, handle); + } + if ((it.flags & (uint16_t)ATT_PROPERTY_WRITE) == 0u) { + return setup_error_write_not_permitted(response_buffer, request_type, handle); + } + if ((it.flags & (uint16_t)ATT_PROPERTY_DYNAMIC) == 0u) { + return setup_error_write_not_permitted(response_buffer, request_type, handle); + } + // check security requirements + int error_code = att_validate_security(att_connection, ATT_WRITE, &it); + if (error_code != 0) { + return setup_error(response_buffer, request_type, handle, error_code); + } + att_persistent_ccc_cache(&it); + error_code = (*att_write_callback)(att_connection->con_handle, handle, ATT_TRANSACTION_MODE_NONE, 0u, request_buffer + 3u, request_len - 3u); + +#ifdef ENABLE_ATT_DELAYED_RESPONSE + if (error_code == ATT_ERROR_WRITE_RESPONSE_PENDING) { + return ATT_INTERNAL_WRITE_RESPONSE_PENDING; + } +#endif + + if (error_code != 0) { + return setup_error(response_buffer, request_type, handle, error_code); + } + response_buffer[0] = (uint8_t)ATT_WRITE_RESPONSE; + return 1; +} + +// +// MARK: ATT_PREPARE_WRITE_REQUEST 0x16 +static uint16_t handle_prepare_write_request(att_connection_t * att_connection, uint8_t * request_buffer, uint16_t request_len, + uint8_t * response_buffer, uint16_t response_buffer_size) { + + uint8_t request_type = ATT_PREPARE_WRITE_REQUEST; + + if (request_len < 5u) { + return setup_error_invalid_pdu(response_buffer, request_type); + } + + uint16_t handle = little_endian_read_16(request_buffer, 1); + uint16_t offset = little_endian_read_16(request_buffer, 3); + if (att_write_callback == NULL) { + return setup_error_write_not_permitted(response_buffer, request_type, handle); + } + att_iterator_t it; + if (att_find_handle(&it, handle) == 0) { + return setup_error_invalid_handle(response_buffer, request_type, handle); + } + if ((it.flags & (uint16_t)ATT_PROPERTY_WRITE) == 0u) { + return setup_error_write_not_permitted(response_buffer, request_type, handle); + } + if ((it.flags & (uint16_t)ATT_PROPERTY_DYNAMIC) == 0u) { + return setup_error_write_not_permitted(response_buffer, request_type, handle); + } + // check security requirements + int error_code = att_validate_security(att_connection, ATT_WRITE, &it); + if (error_code != 0) { + return setup_error(response_buffer, request_type, handle, error_code); + } + + error_code = (*att_write_callback)(att_connection->con_handle, handle, ATT_TRANSACTION_MODE_ACTIVE, offset, request_buffer + 5u, request_len - 5u); + switch (error_code) { + case 0: + break; + case ATT_ERROR_INVALID_OFFSET: + case ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LENGTH: + // postpone to execute write request + att_prepare_write_update_errors(error_code, handle); + break; +#ifdef ENABLE_ATT_DELAYED_RESPONSE + case ATT_ERROR_WRITE_RESPONSE_PENDING: + return ATT_INTERNAL_WRITE_RESPONSE_PENDING; +#endif + default: + return setup_error(response_buffer, request_type, handle, error_code); + } + + // response: echo request + uint16_t bytes_to_echo = btstack_min(request_len, response_buffer_size); + (void)memcpy(response_buffer, request_buffer, bytes_to_echo); + response_buffer[0] = ATT_PREPARE_WRITE_RESPONSE; + return request_len; +} + +/* + @brief transcation queue of prepared writes, e.g., after disconnect +*/ +void att_clear_transaction_queue(att_connection_t * att_connection) { + (*att_write_callback)(att_connection->con_handle, 0, ATT_TRANSACTION_MODE_CANCEL, 0, NULL, 0); +} + +// MARK: ATT_EXECUTE_WRITE_REQUEST 0x18 +// NOTE: security has been verified by handle_prepare_write_request +static uint16_t handle_execute_write_request(att_connection_t * att_connection, uint8_t * request_buffer, uint16_t request_len, + uint8_t * response_buffer, uint16_t response_buffer_size) { + + UNUSED(response_buffer_size); + + uint8_t request_type = ATT_EXECUTE_WRITE_REQUEST; + + if (request_len < 2u) { + return setup_error_invalid_pdu(response_buffer, request_type); + } + + if (att_write_callback == NULL) { + return setup_error_write_not_permitted(response_buffer, request_type, 0); + } + + if (request_buffer[1]) { + // validate queued write + if (att_prepare_write_error_code == 0) { + att_prepare_write_error_code = (*att_write_callback)(att_connection->con_handle, 0, ATT_TRANSACTION_MODE_VALIDATE, 0, NULL, 0); + } +#ifdef ENABLE_ATT_DELAYED_RESPONSE + if (att_prepare_write_error_code == ATT_ERROR_WRITE_RESPONSE_PENDING) { + return ATT_INTERNAL_WRITE_RESPONSE_PENDING; + } +#endif + // deliver queued errors + if (att_prepare_write_error_code != 0) { + att_clear_transaction_queue(att_connection); + uint8_t error_code = att_prepare_write_error_code; + uint16_t handle = att_prepare_write_error_handle; + att_prepare_write_reset(); + return setup_error(response_buffer, request_type, handle, error_code); + } + att_write_callback(att_connection->con_handle, 0, ATT_TRANSACTION_MODE_EXECUTE, 0, NULL, 0); + } else { + att_clear_transaction_queue(att_connection); + } + response_buffer[0] = ATT_EXECUTE_WRITE_RESPONSE; + return 1; +} + +// MARK: ATT_WRITE_COMMAND 0x52 +// Core 4.0, vol 3, part F, 3.4.5.3 +// "No Error Response or Write Response shall be sent in response to this command" +static void handle_write_command(att_connection_t * att_connection, uint8_t * request_buffer, uint16_t request_len, uint16_t required_flags) { + + if (request_len < 3u) { + return; + } + + uint16_t handle = little_endian_read_16(request_buffer, 1); + if (att_write_callback == NULL) { + return; + } + + att_iterator_t it; + int ok = att_find_handle(&it, handle); + if (!ok) { + return; + } + if ((it.flags & (uint16_t)ATT_PROPERTY_DYNAMIC) == 0u) { + return; + } + if ((it.flags & required_flags) == 0u) { + return; + } + if (att_validate_security(att_connection, ATT_WRITE, &it)) { + return; + } + att_persistent_ccc_cache(&it); + (*att_write_callback)(att_connection->con_handle, handle, ATT_TRANSACTION_MODE_NONE, 0u, request_buffer + 3u, request_len - 3u); +} + +// MARK: helper for ATT_HANDLE_VALUE_NOTIFICATION and ATT_HANDLE_VALUE_INDICATION +static uint16_t prepare_handle_value(att_connection_t * att_connection, + uint16_t handle, + const uint8_t *value, + uint16_t value_len, + uint8_t * response_buffer) { + little_endian_store_16(response_buffer, 1, handle); + uint16_t bytes_to_copy = btstack_min(value_len, att_connection->mtu - 3u); + (void)memcpy(&response_buffer[3], value, bytes_to_copy); + return value_len + 3u; +} + +// MARK: ATT_HANDLE_VALUE_NOTIFICATION 0x1b +uint16_t att_prepare_handle_value_notification(att_connection_t * att_connection, + uint16_t attribute_handle, + const uint8_t *value, + uint16_t value_len, + uint8_t * response_buffer) { + + response_buffer[0] = ATT_HANDLE_VALUE_NOTIFICATION; + return prepare_handle_value(att_connection, attribute_handle, value, value_len, response_buffer); +} + +// MARK: ATT_MULTIPLE_HANDLE_VALUE_NTF 0x23u +uint16_t att_prepare_handle_value_multiple_notification(att_connection_t * att_connection, + uint8_t num_attributes, + const uint16_t * attribute_handles, + const uint8_t ** values_data, + const uint16_t * values_len, + uint8_t * response_buffer) { + + response_buffer[0] = ATT_MULTIPLE_HANDLE_VALUE_NTF; + uint8_t i; + uint16_t offset = 1; + uint16_t response_buffer_size = att_connection->mtu - 3u; + for (i = 0; i < num_attributes; i++) { + uint16_t value_len = values_len[i]; + if ((offset + 4 + value_len) > response_buffer_size) { + break; + } + little_endian_store_16(response_buffer, offset, attribute_handles[i]); + offset += 2; + little_endian_store_16(response_buffer, offset, value_len); + offset += 2; + (void) memcpy(&response_buffer[offset], values_data[i], value_len); + offset += value_len; + } + return offset; +} + +// MARK: ATT_HANDLE_VALUE_INDICATION 0x1d +uint16_t att_prepare_handle_value_indication(att_connection_t * att_connection, + uint16_t attribute_handle, + const uint8_t *value, + uint16_t value_len, + uint8_t * response_buffer) { + + response_buffer[0] = ATT_HANDLE_VALUE_INDICATION; + return prepare_handle_value(att_connection, attribute_handle, value, value_len, response_buffer); +} + +// MARK: Dispatcher +uint16_t att_handle_request(att_connection_t * att_connection, + uint8_t * request_buffer, + uint16_t request_len, + uint8_t * response_buffer) { + uint16_t response_len = 0; + const uint16_t response_buffer_size = att_connection->mtu; + const uint8_t request_opcode = request_buffer[0]; + + switch (request_opcode) { + case ATT_EXCHANGE_MTU_REQUEST: + response_len = handle_exchange_mtu_request(att_connection, request_buffer, request_len, response_buffer); + break; + case ATT_FIND_INFORMATION_REQUEST: + response_len = handle_find_information_request(att_connection, request_buffer, request_len, response_buffer, response_buffer_size); + break; + case ATT_FIND_BY_TYPE_VALUE_REQUEST: + response_len = handle_find_by_type_value_request(att_connection, request_buffer, request_len, response_buffer, response_buffer_size); + break; + case ATT_READ_BY_TYPE_REQUEST: + response_len = handle_read_by_type_request(att_connection, request_buffer, request_len, response_buffer, response_buffer_size); + break; + case ATT_READ_REQUEST: + response_len = handle_read_request(att_connection, request_buffer, request_len, response_buffer, response_buffer_size); + break; + case ATT_READ_BLOB_REQUEST: + response_len = handle_read_blob_request(att_connection, request_buffer, request_len, response_buffer, response_buffer_size); + break; + case ATT_READ_MULTIPLE_REQUEST: + response_len = handle_read_multiple_request(att_connection, request_buffer, request_len, response_buffer, + response_buffer_size, false); + break; + case ATT_READ_MULTIPLE_VARIABLE_REQ: + response_len = handle_read_multiple_request(att_connection, request_buffer, request_len, response_buffer, + response_buffer_size, true); + break; + case ATT_READ_BY_GROUP_TYPE_REQUEST: + response_len = handle_read_by_group_type_request(att_connection, request_buffer, request_len, response_buffer, response_buffer_size); + break; + case ATT_WRITE_REQUEST: + response_len = handle_write_request(att_connection, request_buffer, request_len, response_buffer, response_buffer_size); + break; + case ATT_PREPARE_WRITE_REQUEST: + response_len = handle_prepare_write_request(att_connection, request_buffer, request_len, response_buffer, response_buffer_size); + break; + case ATT_EXECUTE_WRITE_REQUEST: + response_len = handle_execute_write_request(att_connection, request_buffer, request_len, response_buffer, response_buffer_size); + break; + case ATT_WRITE_COMMAND: + handle_write_command(att_connection, request_buffer, request_len, ATT_PROPERTY_WRITE_WITHOUT_RESPONSE); + break; +#ifdef ENABLE_LE_SIGNED_WRITE + case ATT_SIGNED_WRITE_COMMAND: + handle_write_command(att_connection, request_buffer, request_len, ATT_PROPERTY_AUTHENTICATED_SIGNED_WRITE); + break; +#endif + default: + response_len = setup_error(response_buffer, request_opcode, 0, ATT_ERROR_REQUEST_NOT_SUPPORTED); + break; + } + return response_len; +} + +// returns 1 if service found. only primary service. +bool gatt_server_get_handle_range_for_service_with_uuid16(uint16_t uuid16, uint16_t * start_handle, uint16_t * end_handle) { + bool in_group = false; + uint16_t prev_handle = 0; + uint16_t service_start = 0; + + uint8_t attribute_value[2]; + int attribute_len = sizeof(attribute_value); + little_endian_store_16(attribute_value, 0, uuid16); + + att_iterator_t it; + att_iterator_init(&it); + while (att_iterator_has_next(&it)) { + att_iterator_fetch_next(&it); + int new_service_started = att_iterator_match_uuid16(&it, GATT_PRIMARY_SERVICE_UUID) || att_iterator_match_uuid16(&it, GATT_SECONDARY_SERVICE_UUID); + + // close current tag, if within a group and a new service definition starts or we reach end of att db + if (in_group && + ((it.handle == 0u) || new_service_started)) { + in_group = false; + // check range + if ((service_start >= *start_handle) && (prev_handle <= *end_handle)) { + *start_handle = service_start; + *end_handle = prev_handle; + return true; + } + } + + // keep track of previous handle + prev_handle = it.handle; + + // check if found + if ((it.handle != 0u) && new_service_started && (attribute_len == it.value_len) && (memcmp(attribute_value, it.value, it.value_len) == 0)) { + service_start = it.handle; + in_group = true; + } + } + return false; +} + +// returns false if not found +uint16_t gatt_server_get_value_handle_for_characteristic_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t uuid16) { + att_iterator_t it; + att_iterator_init(&it); + while (att_iterator_has_next(&it)) { + att_iterator_fetch_next(&it); + if ((it.handle != 0u) && (it.handle < start_handle)) { + continue; + } + if (it.handle > end_handle) { + break; // (1) + } + if (it.handle == 0u) { + break; + } + if (att_iterator_match_uuid16(&it, uuid16)) { + return it.handle; + } + } + return 0; +} + +uint16_t gatt_server_get_descriptor_handle_for_characteristic_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t characteristic_uuid16, uint16_t descriptor_uuid16) { + att_iterator_t it; + att_iterator_init(&it); + bool characteristic_found = false; + while (att_iterator_has_next(&it)) { + att_iterator_fetch_next(&it); + if ((it.handle != 0u) && (it.handle < start_handle)) { + continue; + } + if (it.handle > end_handle) { + break; // (1) + } + if (it.handle == 0u) { + break; + } + if (att_iterator_match_uuid16(&it, characteristic_uuid16)) { + characteristic_found = true; + continue; + } + if (att_iterator_match_uuid16(&it, GATT_PRIMARY_SERVICE_UUID) + || att_iterator_match_uuid16(&it, GATT_SECONDARY_SERVICE_UUID) + || att_iterator_match_uuid16(&it, GATT_CHARACTERISTICS_UUID)) { + if (characteristic_found) { + break; + } + continue; + } + if (characteristic_found && att_iterator_match_uuid16(&it, descriptor_uuid16)) { + return it.handle; + } + } + return 0; +} + +// returns 0 if not found +uint16_t gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t characteristic_uuid16) { + return gatt_server_get_descriptor_handle_for_characteristic_with_uuid16(start_handle, end_handle, characteristic_uuid16, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION); +} +// returns 0 if not found + +uint16_t gatt_server_get_server_configuration_handle_for_characteristic_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t characteristic_uuid16) { + return gatt_server_get_descriptor_handle_for_characteristic_with_uuid16(start_handle, end_handle, characteristic_uuid16, GATT_SERVER_CHARACTERISTICS_CONFIGURATION); +} + +// returns true if service found. only primary service. +bool gatt_server_get_handle_range_for_service_with_uuid128(const uint8_t * uuid128, uint16_t * start_handle, uint16_t * end_handle) { + bool in_group = false; + uint16_t prev_handle = 0; + + uint8_t attribute_value[16]; + uint16_t attribute_len = (uint16_t)sizeof(attribute_value); + reverse_128(uuid128, attribute_value); + + att_iterator_t it; + att_iterator_init(&it); + while (att_iterator_has_next(&it)) { + att_iterator_fetch_next(&it); + int new_service_started = att_iterator_match_uuid16(&it, GATT_PRIMARY_SERVICE_UUID) || att_iterator_match_uuid16(&it, GATT_SECONDARY_SERVICE_UUID); + + // close current tag, if within a group and a new service definition starts or we reach end of att db + if (in_group && + ((it.handle == 0u) || new_service_started)) { + *end_handle = prev_handle; + return true; + } + + // keep track of previous handle + prev_handle = it.handle; + + // check if found + if ((it.handle != 0u) && new_service_started && (attribute_len == it.value_len) && (memcmp(attribute_value, it.value, it.value_len) == 0)) { + *start_handle = it.handle; + in_group = true; + } + } + return false; +} + +// returns 0 if not found +uint16_t gatt_server_get_value_handle_for_characteristic_with_uuid128(uint16_t start_handle, uint16_t end_handle, const uint8_t * uuid128) { + uint8_t attribute_value[16]; + reverse_128(uuid128, attribute_value); + att_iterator_t it; + att_iterator_init(&it); + while (att_iterator_has_next(&it)) { + att_iterator_fetch_next(&it); + if ((it.handle != 0u) && (it.handle < start_handle)) { + continue; + } + if (it.handle > end_handle) { + break; // (1) + } + if (it.handle == 0u) { + break; + } + if (att_iterator_match_uuid(&it, attribute_value, 16)) { + return it.handle; + } + } + return 0; +} + +// returns 0 if not found +uint16_t gatt_server_get_client_configuration_handle_for_characteristic_with_uuid128(uint16_t start_handle, uint16_t end_handle, const uint8_t * uuid128) { + uint8_t attribute_value[16]; + reverse_128(uuid128, attribute_value); + att_iterator_t it; + att_iterator_init(&it); + int characteristic_found = 0; + while (att_iterator_has_next(&it)) { + att_iterator_fetch_next(&it); + if ((it.handle != 0u) && (it.handle < start_handle)) { + continue; + } + if (it.handle > end_handle) { + break; // (1) + } + if (it.handle == 0u) { + break; + } + if (att_iterator_match_uuid(&it, attribute_value, 16)) { + characteristic_found = 1; + continue; + } + if (att_iterator_match_uuid16(&it, GATT_PRIMARY_SERVICE_UUID) + || att_iterator_match_uuid16(&it, GATT_SECONDARY_SERVICE_UUID) + || att_iterator_match_uuid16(&it, GATT_CHARACTERISTICS_UUID)) { + if (characteristic_found) { + break; + } + continue; + } + if (characteristic_found && att_iterator_match_uuid16(&it, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION)) { + return it.handle; + } + } + return 0; +} + + +bool gatt_server_get_included_service_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t uuid16, + uint16_t * out_included_service_handle, uint16_t * out_included_service_start_handle, uint16_t * out_included_service_end_handle) { + + att_iterator_t it; + att_iterator_init(&it); + while (att_iterator_has_next(&it)) { + att_iterator_fetch_next(&it); + if ((it.handle != 0u) && (it.handle < start_handle)) { + continue; + } + if (it.handle > end_handle) { + break; // (1) + } + if (it.handle == 0u) { + break; + } + if ((it.value_len == 6) && (att_iterator_match_uuid16(&it, GATT_INCLUDE_SERVICE_UUID))) { + if (little_endian_read_16(it.value, 4) == uuid16) { + *out_included_service_handle = it.handle; + *out_included_service_start_handle = little_endian_read_16(it.value, 0); + *out_included_service_end_handle = little_endian_read_16(it.value, 2); + return true; + } + } + } + return false; +} + +// 1-item cache to optimize query during write_callback +static void att_persistent_ccc_cache(att_iterator_t * it) { + att_persistent_ccc_handle = it->handle; + if (it->flags & (uint16_t)ATT_PROPERTY_UUID128) { + att_persistent_ccc_uuid16 = 0u; + } else { + att_persistent_ccc_uuid16 = little_endian_read_16(it->uuid, 0); + } +} + +bool att_is_persistent_ccc(uint16_t handle) { + if (handle != att_persistent_ccc_handle) { + att_iterator_t it; + int ok = att_find_handle(&it, handle); + if (!ok) { + return false; + } + att_persistent_ccc_cache(&it); + } + switch (att_persistent_ccc_uuid16) { + case GATT_CLIENT_CHARACTERISTICS_CONFIGURATION: + case GATT_CLIENT_SUPPORTED_FEATURES: + return true; + default: + return false; + } +} + +// att_read_callback helpers +uint16_t att_read_callback_handle_blob(const uint8_t * blob, uint16_t blob_size, uint16_t offset, uint8_t * buffer, uint16_t buffer_size) { + btstack_assert(blob != NULL); + + if (buffer != NULL) { + uint16_t bytes_to_copy = 0; + if (blob_size >= offset) { + bytes_to_copy = btstack_min(blob_size - offset, buffer_size); + (void)memcpy(buffer, &blob[offset], bytes_to_copy); + } + return bytes_to_copy; + } else { + return blob_size; + } +} + +uint16_t att_read_callback_handle_little_endian_32(uint32_t value, uint16_t offset, uint8_t * buffer, uint16_t buffer_size) { + uint8_t value_buffer[4]; + little_endian_store_32(value_buffer, 0, value); + return att_read_callback_handle_blob(value_buffer, sizeof(value_buffer), offset, buffer, buffer_size); +} + +uint16_t att_read_callback_handle_little_endian_16(uint16_t value, uint16_t offset, uint8_t * buffer, uint16_t buffer_size) { + uint8_t value_buffer[2]; + little_endian_store_16(value_buffer, 0, value); + return att_read_callback_handle_blob(value_buffer, sizeof(value_buffer), offset, buffer, buffer_size); +} + +uint16_t att_read_callback_handle_byte(uint8_t value, uint16_t offset, uint8_t * buffer, uint16_t buffer_size) { + uint8_t value_buffer[1]; + value_buffer[0] = value; + return att_read_callback_handle_blob(value_buffer, sizeof(value_buffer), offset, buffer, buffer_size); +} + + +#ifdef ENABLE_BTP + +// start of auto-PTS testing code, not used in production +// LCOV_EXCL_START +#include "btp.h" + +static uint8_t btp_permissions_for_flags(uint16_t flags) { + + // see BT_GATT_PERM_* + // https://docs.zephyrproject.org/latest/reference/bluetooth/gatt.html + // set bit indicates requirement, e.g. BTP_GATT_PERM_READ_AUTHN requires authenticated connection + + uint8_t permissions = 0; + + uint8_t read_security_level = 0; + uint8_t write_security_level = 0; + if (flags & (uint16_t)ATT_PROPERTY_READ) { + if (flags & (uint16_t)ATT_PROPERTY_READ_PERMISSION_BIT_0) { + read_security_level |= 1; + } + if (flags & (uint16_t)ATT_PROPERTY_READ_PERMISSION_BIT_1) { + read_security_level |= 2; + } + if (read_security_level == ATT_SECURITY_AUTHORIZED) { + permissions |= BTP_GATT_PERM_READ_AUTHZ; + } + if (read_security_level == ATT_SECURITY_AUTHENTICATED) { + permissions |= BTP_GATT_PERM_READ_AUTHN; + } + if (read_security_level == ATT_SECURITY_ENCRYPTED) { + permissions |= BTP_GATT_PERM_READ_ENC; + } + if (read_security_level == ATT_SECURITY_NONE) { + permissions |= BTP_GATT_PERM_READ; + } + } + if (flags & (ATT_PROPERTY_WRITE | ATT_PROPERTY_WRITE_WITHOUT_RESPONSE)) { + if (flags & (uint16_t)ATT_PROPERTY_WRITE_PERMISSION_BIT_0) { + write_security_level |= 1; + } + if (flags & (uint16_t)ATT_PROPERTY_WRITE_PERMISSION_BIT_1) { + write_security_level |= 2; + } + if (write_security_level == ATT_SECURITY_AUTHORIZED) { + permissions |= BTP_GATT_PERM_WRITE_AUTHZ; + } + if (write_security_level == ATT_SECURITY_AUTHENTICATED) { + permissions |= BTP_GATT_PERM_WRITE_AUTHN; + } + if (write_security_level == ATT_SECURITY_ENCRYPTED) { + permissions |= BTP_GATT_PERM_WRITE_ENC; + } + if (write_security_level == ATT_SECURITY_NONE) { + permissions |= BTP_GATT_PERM_WRITE; + } + } + return permissions; +} + +uint16_t btp_att_get_attributes_by_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t uuid16, uint8_t * response_buffer, uint16_t response_buffer_size) { + log_info("btp_att_get_attributes_by_uuid16 %04x from 0x%04x to 0x%04x, db %p", uuid16, start_handle, end_handle, att_database); + att_dump_attributes(); + + uint8_t num_attributes = 0; + uint16_t pos = 1; + + att_iterator_t it; + att_iterator_init(&it); + while (att_iterator_has_next(&it) && ((pos + 6) < response_buffer_size)) { + att_iterator_fetch_next(&it); + log_info("handle %04x", it.handle); + if (it.handle == 0) { + break; + } + if (it.handle < start_handle) { + continue; + } + if (it.handle > end_handle) { + break; + } + if ((uuid16 == 0) || att_iterator_match_uuid16(&it, uuid16)) { + little_endian_store_16(response_buffer, pos, it.handle); + pos += 2; + response_buffer[pos++] = btp_permissions_for_flags(it.flags); + response_buffer[pos++] = 2; + little_endian_store_16(response_buffer, pos, uuid16); + pos += 2; + num_attributes++; + } + } + response_buffer[0] = num_attributes; + return pos; +} + +uint16_t btp_att_get_attributes_by_uuid128(uint16_t start_handle, uint16_t end_handle, const uint8_t * uuid128, uint8_t * response_buffer, uint16_t response_buffer_size) { + uint8_t num_attributes = 0; + uint16_t pos = 1; + att_iterator_t it; + att_iterator_init(&it); + while (att_iterator_has_next(&it) && ((pos + 20) < response_buffer_size)) { + att_iterator_fetch_next(&it); + if (it.handle == 0) { + break; + } + if (it.handle < start_handle) { + continue; + } + if (it.handle > end_handle) { + break; + } + if (att_iterator_match_uuid(&it, (uint8_t*) uuid128, 16)) { + little_endian_store_16(response_buffer, pos, it.handle); + pos += 2; + response_buffer[pos++] = btp_permissions_for_flags(it.flags); + response_buffer[pos++] = 16; + reverse_128(uuid128, &response_buffer[pos]); + pos += 16; + num_attributes++; + } + } + response_buffer[0] = num_attributes; + return pos; +} + +uint16_t btp_att_get_attribute_value(att_connection_t * att_connection, uint16_t attribute_handle, uint8_t * response_buffer, uint16_t response_buffer_size) { + att_iterator_t it; + int ok = att_find_handle(&it, attribute_handle); + if (!ok) { + return 0; + } + + uint16_t pos = 0; + // field: ATT_Response - simulate READ operation on given connection + response_buffer[pos++] = att_validate_security(att_connection, ATT_READ, &it); + // fetch len + // assume: con handle not relevant here, else, it needs to get passed in + // att_update_value_len(&it, HCI_CON_HANDLE_INVALID); + uint16_t bytes_to_copy = btstack_min(response_buffer_size - 3, it.value_len); + little_endian_store_16(response_buffer, pos, bytes_to_copy); + pos += 2; + // get value - only works for non-dynamic data + if (it.value) { + memcpy(&response_buffer[pos], it.value, bytes_to_copy); + pos += bytes_to_copy; + } + return pos; +} +// LCOV_EXCL_STOP +#endif + +#endif diff --git a/cores/rp2040/sdkoverride/att_db.h b/cores/rp2040/sdkoverride/att_db.h new file mode 100644 index 000000000..ed8591fdb --- /dev/null +++ b/cores/rp2040/sdkoverride/att_db.h @@ -0,0 +1,443 @@ +/* + Copyright (C) 2014 BlueKitchen GmbH + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the names of + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + 4. Any redistribution, use, or modification is done solely for + personal benefit and not for any commercial purpose or for + monetary gain. + + THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Please inquire about commercial licensing options at + contact@bluekitchen-gmbh.com + +*/ + +/** + @title ATT Database Engine + +*/ + +#ifndef ATT_DB_H +#define ATT_DB_H + +#if defined ENABLE_CLASSIC + +#include +#include +#include "btstack_linked_list.h" +#include "btstack_defines.h" +#include "btstack_bool.h" + +#if defined __cplusplus +extern "C" { +#endif + + // MARK: Attribute PDU Opcodes +#define ATT_ERROR_RESPONSE 0x01u + +#define ATT_EXCHANGE_MTU_REQUEST 0x02u +#define ATT_EXCHANGE_MTU_RESPONSE 0x03u + +#define ATT_FIND_INFORMATION_REQUEST 0x04u +#define ATT_FIND_INFORMATION_REPLY 0x05u +#define ATT_FIND_BY_TYPE_VALUE_REQUEST 0x06u +#define ATT_FIND_BY_TYPE_VALUE_RESPONSE 0x07u + +#define ATT_READ_BY_TYPE_REQUEST 0x08u +#define ATT_READ_BY_TYPE_RESPONSE 0x09u +#define ATT_READ_REQUEST 0x0au +#define ATT_READ_RESPONSE 0x0bu +#define ATT_READ_BLOB_REQUEST 0x0cu +#define ATT_READ_BLOB_RESPONSE 0x0du +#define ATT_READ_MULTIPLE_REQUEST 0x0eu +#define ATT_READ_MULTIPLE_RESPONSE 0x0fu +#define ATT_READ_BY_GROUP_TYPE_REQUEST 0x10u +#define ATT_READ_BY_GROUP_TYPE_RESPONSE 0x11u + +#define ATT_WRITE_REQUEST 0x12u +#define ATT_WRITE_RESPONSE 0x13u + +#define ATT_PREPARE_WRITE_REQUEST 0x16u +#define ATT_PREPARE_WRITE_RESPONSE 0x17u +#define ATT_EXECUTE_WRITE_REQUEST 0x18u +#define ATT_EXECUTE_WRITE_RESPONSE 0x19u + +#define ATT_HANDLE_VALUE_NOTIFICATION 0x1bu +#define ATT_HANDLE_VALUE_INDICATION 0x1du +#define ATT_HANDLE_VALUE_CONFIRMATION 0x1eu + +#define ATT_READ_MULTIPLE_VARIABLE_REQ 0x20u +#define ATT_READ_MULTIPLE_VARIABLE_RSP 0x21u +#define ATT_MULTIPLE_HANDLE_VALUE_NTF 0x23u + +#define ATT_WRITE_COMMAND 0x52u +#define ATT_SIGNED_WRITE_COMMAND 0xD2u + + + // internal additions + // 128 bit UUID used +#define ATT_PROPERTY_UUID128 0x200u + // Read/Write Permission bits +#define ATT_PROPERTY_READ_PERMISSION_BIT_0 0x0400u +#define ATT_PROPERTY_READ_PERMISSION_BIT_1 0x0800u +#define ATT_PROPERTY_WRITE_PERMISSION_BIT_0 0x0001u +#define ATT_PROPERTY_WRITE_PERMISSION_BIT_1 0x0010u +#define ATT_PROPERTY_READ_PERMISSION_SC 0x0020u +#define ATT_PROPERTY_WRITE_PERMISSION_SC 0x0080u + + + typedef struct att_connection { + hci_con_handle_t con_handle; + uint16_t mtu; // initialized to ATT_DEFAULT_MTU (23), negotiated during MTU exchange + uint16_t max_mtu; // local maximal L2CAP_MTU, set to l2cap_max_le_mtu() + bool mtu_exchanged; + uint8_t encryption_key_size; + uint8_t authenticated; + uint8_t authorized; + uint8_t secure_connection; + } att_connection_t; + + /* API_START */ + + // map ATT ERROR CODES on to att_read_callback length +#define ATT_READ_ERROR_CODE_OFFSET 0xfe00u + + // custom BTstack ATT Response Pending for att_read_callback +#define ATT_READ_RESPONSE_PENDING 0xffffu + + // internally used to signal write response pending +#define ATT_INTERNAL_WRITE_RESPONSE_PENDING 0xfffeu + + /** + @brief ATT Client Read Callback for Dynamic Data + - if buffer == NULL, don't copy data, just return size of value + - if buffer != NULL, copy data and return number bytes copied + If ENABLE_ATT_DELAYED_READ_RESPONSE is defined, you may return ATT_READ_RESPONSE_PENDING if data isn't available yet + @param con_handle of hci le connection + @param attribute_handle to be read + @param offset defines start of attribute value + @param buffer + @param buffer_size + @return size of value if buffer is NULL, otherwise number of bytes copied + */ + typedef uint16_t (*att_read_callback_t)(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size); + + /** + @brief ATT Client Write Callback for Dynamic Data + Each Prepared Write Request triggers a callback with transaction mode ATT_TRANSACTION_MODE_ACTIVE. + On Execute Write, the callback will be called with ATT_TRANSACTION_MODE_VALIDATE and allows to validate all queued writes and return an application error. + If none of the registered callbacks return an error for ATT_TRANSACTION_MODE_VALIDATE and the callback will be called with ATT_TRANSACTION_MODE_EXECUTE. + Otherwise, all callbacks will be called with ATT_TRANSACTION_MODE_CANCEL. + + If the additional validation step is not needed, just return 0 for all callbacks with transaction mode ATT_TRANSACTION_MODE_VALIDATE. + + @param con_handle of hci le connection + @param attribute_handle to be written + @param transaction - ATT_TRANSACTION_MODE_NONE for regular writes. For prepared writes: ATT_TRANSACTION_MODE_ACTIVE, ATT_TRANSACTION_MODE_VALIDATE, ATT_TRANSACTION_MODE_EXECUTE, ATT_TRANSACTION_MODE_CANCEL + @param offset into the value - used for queued writes and long attributes + @param buffer + @param buffer_size + @param signature used for signed write commands + @return 0 if write was ok, ATT_ERROR_PREPARE_QUEUE_FULL if no space in queue, ATT_ERROR_INVALID_OFFSET if offset is larger than max buffer + */ + typedef int (*att_write_callback_t)(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size); + + // Read & Write Callbacks for handle range + typedef struct att_service_handler { + btstack_linked_item_t * item; + uint16_t start_handle; + uint16_t end_handle; + att_read_callback_t read_callback; + att_write_callback_t write_callback; + btstack_packet_handler_t packet_handler; + } att_service_handler_t; + + // MARK: ATT Operations + + /** + @brief setup ATT database + @param db + */ + void att_set_db(uint8_t const * db); + + /* + @brief set callback for read of dynamic attributes + @param callback + */ + void att_set_read_callback(att_read_callback_t callback); + + /** + @brief set callback for write of dynamic attributes + @param callback + */ + void att_set_write_callback(att_write_callback_t callback); + + /** + @brief debug helper, dump ATT database to stdout using log_info + */ + void att_dump_attributes(void); + + /** + @brief process ATT request against database and put response into response buffer + @param att_connection used for mtu and security properties + @param request_buffer, request_len: ATT request from client + @param response_buffer for result + @return len of data in response buffer. 0 = no response, + ATT_READ_RESPONSE_PENDING if it was returned at least once for dynamic data (requires ENABLE_ATT_DELAYED_READ_RESPONSE) + */ + uint16_t att_handle_request(att_connection_t * att_connection, + uint8_t * request_buffer, + uint16_t request_len, + uint8_t * response_buffer); + + /** + @brief setup value notification in response buffer for a given handle and value + @param att_connection + @param attribute_handle + @param value + @param value_len + @param response_buffer for notification + */ + uint16_t att_prepare_handle_value_notification(att_connection_t * att_connection, + uint16_t attribute_handle, + const uint8_t *value, + uint16_t value_len, + uint8_t * response_buffer); + + /** + @brief setup value notification in response buffer for multiple handles and values + @param att_connection + @param attribute_handle + @param value + @param value_len + @param response_buffer for notification + */ + uint16_t att_prepare_handle_value_multiple_notification(att_connection_t * att_connection, + uint8_t num_attributes, + const uint16_t * attribute_handles, + const uint8_t ** values_data, + const uint16_t * values_len, + uint8_t * response_buffer); + + /** + @brief setup value indication in response buffer for a given handle and value + @param att_connection + @param attribute_handle + @param value + @param value_len + @param response_buffer for indication + */ + uint16_t att_prepare_handle_value_indication(att_connection_t * att_connection, + uint16_t attribute_handle, + const uint8_t *value, + uint16_t value_len, + uint8_t * response_buffer); + + /** + @brief transcation queue of prepared writes, e.g., after disconnect + @return att_connection + */ + void att_clear_transaction_queue(att_connection_t * att_connection); + + // att_read_callback helpers for a various data types + + /** + @brief Handle read of blob like data for att_read_callback + @param blob of data + @param blob_size of blob + @param offset from att_read_callback + @param buffer from att_read_callback + @param buffer_size from att_read_callback + @return value size for buffer == 0 and num bytes copied otherwise + */ + uint16_t att_read_callback_handle_blob(const uint8_t * blob, uint16_t blob_size, uint16_t offset, uint8_t * buffer, uint16_t buffer_size); + + /** + @brief Handle read of little endian unsigned 32 bit value for att_read_callback + @param value + @param offset from att_read_callback + @param buffer from att_read_callback + @param buffer_size from att_read_callback + @return value size for buffer == 0 and num bytes copied otherwise + */ + uint16_t att_read_callback_handle_little_endian_32(uint32_t value, uint16_t offset, uint8_t * buffer, uint16_t buffer_size); + + /** + @brief Handle read of little endian unsigned 16 bit value for att_read_callback + @param value + @param offset from att_read_callback + @param buffer from att_read_callback + @param buffer_size from att_read_callback + @return value size for buffer == 0 and num bytes copied otherwise + */ + uint16_t att_read_callback_handle_little_endian_16(uint16_t value, uint16_t offset, uint8_t * buffer, uint16_t buffer_size); + + /** + @brief Handle read of single byte for att_read_callback + @param blob of data + @param blob_size of blob + @param offset from att_read_callback + @param buffer from att_read_callback + @param buffer_size from att_read_callback + @return value size for buffer == 0 and num bytes copied otherwise + */ + uint16_t att_read_callback_handle_byte(uint8_t value, uint16_t offset, uint8_t * buffer, uint16_t buffer_size); + + + // experimental client API + /** + @brief Get UUID for handle + @param attribute_handle + @return 0 if not found + */ + uint16_t att_uuid_for_handle(uint16_t attribute_handle); + + /** + @brief Get const value for handle + @param attribute_handle + @param out_value_len output variable that hold value len + @return value + */ + + const uint8_t * gatt_server_get_const_value_for_handle(uint16_t attribute_handle, uint16_t * out_value_len); + + // experimental GATT Server API + + /** + @brief Get handle range for primary service. + @param uuid16 + @param start_handle + @param end_handle + @return false if not found + */ + bool gatt_server_get_handle_range_for_service_with_uuid16(uint16_t uuid16, uint16_t * start_handle, uint16_t * end_handle); + + /** + @brief Get handle range for included service. + @param start_handle + @param end_handle + @param uuid16 + @param out_included_service_handle + @param out_included_service_start_handle + @param out_included_service_end_handle + @return false if not found + */ + bool gatt_server_get_included_service_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t uuid16, + uint16_t * out_included_service_handle, uint16_t * out_included_service_start_handle, uint16_t * out_included_service_end_handle); + + /** + @brief Get value handle for characteristic. + @param start_handle + @param end_handle + @param uuid16 + @return 0 if not found + */ + uint16_t gatt_server_get_value_handle_for_characteristic_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t uuid16); + + /** + @brief Get descriptor handle for characteristic. + @param start_handle + @param end_handle + @param characteristic_uuid16 + @param descriptor_uuid16 + @return 0 if not found + */ + uint16_t gatt_server_get_descriptor_handle_for_characteristic_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t characteristic_uuid16, uint16_t descriptor_uuid16); + + /** + @brief Get client configuration handle for characteristic. + @param start_handle + @param end_handle + @param characteristic_uuid16 + @return 0 if not found + */ + uint16_t gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t characteristic_uuid16); + + /** + @brief Get server configuration handle for characteristic. + @param start_handle + @param end_handle + @param characteristic_uuid16 + @param descriptor_uuid16 + @return 0 if not found + */ + uint16_t gatt_server_get_server_configuration_handle_for_characteristic_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t characteristic_uuid16); + + + /** + @brief Get handle range for primary service. + @param uuid128 + @param start_handle + @param end_handle + @return false if not found + */ + bool gatt_server_get_handle_range_for_service_with_uuid128(const uint8_t * uuid128, uint16_t * start_handle, uint16_t * end_handle); + + /** + @brief Get value handle. + @param start_handle + @param end_handle + @param uuid128 + @return 0 if not found + */ + uint16_t gatt_server_get_value_handle_for_characteristic_with_uuid128(uint16_t start_handle, uint16_t end_handle, const uint8_t * uuid128); + + /** + @brief Get client configuration handle. + @param start_handle + @param end_handle + @param uuid128 + @return 0 if not found + */ + uint16_t gatt_server_get_client_configuration_handle_for_characteristic_with_uuid128(uint16_t start_handle, uint16_t end_handle, const uint8_t * uuid128); + + /* API_END */ + + // non-user functionality for att_server + + /** + @brief Check if writes to handle should be persistent + @param handle + @return 1 if persistent + */ + bool att_is_persistent_ccc(uint16_t handle); + + + + // auto-pts testing, returns response size +#ifdef ENABLE_BTP + uint16_t btp_att_get_attributes_by_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t uuid16, uint8_t * response_buffer, uint16_t response_buffer_size); + uint16_t btp_att_get_attributes_by_uuid128(uint16_t start_handle, uint16_t end_handle, const uint8_t * uuid128, uint8_t * response_buffer, uint16_t response_buffer_size); + uint16_t btp_att_get_attribute_value(att_connection_t * att_connection, uint16_t attribute_handle, uint8_t * response_buffer, uint16_t response_buffer_size); +#endif + +#if defined __cplusplus +} +#endif + +#endif + +#endif // ATT_H diff --git a/cores/rp2040/sdkoverride/bluetooth.h b/cores/rp2040/sdkoverride/bluetooth.h new file mode 100644 index 000000000..d27835e5b --- /dev/null +++ b/cores/rp2040/sdkoverride/bluetooth.h @@ -0,0 +1,926 @@ +/* + Copyright (C) 2015 BlueKitchen GmbH + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the names of + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + 4. Any redistribution, use, or modification is done solely for + personal benefit and not for any commercial purpose or for + monetary gain. + + THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Please inquire about commercial licensing options at + contact@bluekitchen-gmbh.com + +*/ + +/* + bluetooth.h + + Numbers defined or derived from the official Bluetooth specification +*/ + +#ifndef BLUETOOTH_H +#define BLUETOOTH_H + +#include + +/** + @brief hci connection handle type +*/ +typedef uint16_t hci_con_handle_t; + +/** + @brief Length of a bluetooth device address. +*/ +#define BD_ADDR_LEN 6 + +/** + @brief Bluetooth address +*/ +typedef uint8_t bd_addr_t[BD_ADDR_LEN]; + +/** + Address types +*/ +typedef enum { + // Public Device Address + BD_ADDR_TYPE_LE_PUBLIC = 0, + // Random Device Address + BD_ADDR_TYPE_LE_RANDOM = 1, + // Public Identity Address (Corresponds to Resolved Private Address) + BD_ADDR_TYPE_LE_PUBLIC_IDENTITY = 2, + // Random (static) Identity Address (Corresponds to Resolved Private Address) + BD_ADDR_TYPE_LE_RANDOM_IDENTITY = 3, + // internal BTstack addr types for Classic connections + BD_ADDR_TYPE_SCO = 0xfc, + BD_ADDR_TYPE_ACL = 0xfd, + BD_ADDR_TYPE_UNKNOWN = 0xfe, // also used as 'invalid' +} bd_addr_type_t; + +/** + Link types for BR/EDR Connections +*/ +typedef enum { + HCI_LINK_TYPE_SCO = 0, + HCI_LINK_TYPE_ACL = 1, + HCI_LINK_TYPE_ESCO = 2, +} hci_link_type_t; + + +/** + @brief link key +*/ +#define LINK_KEY_LEN 16 +#define LINK_KEY_STR_LEN (LINK_KEY_LEN*2) +typedef uint8_t link_key_t[LINK_KEY_LEN]; + +/** + @brief link key type +*/ +typedef enum { + INVALID_LINK_KEY = 0xffff, + COMBINATION_KEY = 0, // standard pairing + LOCAL_UNIT_KEY, // ? + REMOTE_UNIT_KEY, // ? + DEBUG_COMBINATION_KEY, // SSP with debug + UNAUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P192, // SSP Simple Pairing + AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P192, // SSP Passkey, Number confirm, OOB + CHANGED_COMBINATION_KEY, // Link key changed using Change Connection Lnk Key + UNAUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P256, // SSP Simpe Pairing + AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P256, // SSP Passkey, Number confirm, OOB +} link_key_type_t; + +/** + LE Privacy 1.2 +*/ +typedef enum { + LE_PRIVACY_MODE_NETWORK = 0, + LE_PRIVACY_MODE_DEVICE = 1, +} le_privacy_mode_t; + +/** + @brief Extended Inquiry Response +*/ +#define EXTENDED_INQUIRY_RESPONSE_DATA_LEN 240 + +/** + @brief Inquiry modes +*/ +typedef enum { + INQUIRY_MODE_STANDARD = 0, + INQUIRY_MODE_RSSI, + INQUIRY_MODE_RSSI_AND_EIR, +} inquiry_mode_t; + +/** + @brief Page Scan Types +*/ +typedef enum { + PAGE_SCAN_MODE_STANDARD = 0, + PAGE_SCAN_MODE_INTERLACED, +} page_scan_type_t; + +/** + @brief Inquiry Scan Types +*/ +typedef enum { + INQUIRY_SCAN_MODE_STANDARD = 0, + INQUIRY_SCAN_MODE_INTERLACED, +} inquiry_scan_type_t; + +/** + Link Supervision Timeout Default, 0x7d00 * 0.625ms = 20s +*/ +#define HCI_LINK_SUPERVISION_TIMEOUT_DEFAULT 0x7D00 + +/** + Service Type used for QoS Setup and Flow Specification +*/ +typedef enum { + HCI_SERVICE_TYPE_NO_TRAFFIC = 0, + HCI_SERVICE_TYPE_BEST_EFFORT, + HCI_SERVICE_TYPE_GUARANTEED, + HCI_SERVICE_TYPE_INVALID, +} hci_service_type_t; + +/** + HCI Transport +*/ + +/** + packet types - used in BTstack and over the H4 UART interface +*/ +#define HCI_COMMAND_DATA_PACKET 0x01 +#define HCI_ACL_DATA_PACKET 0x02 +#define HCI_SCO_DATA_PACKET 0x03 +#define HCI_EVENT_PACKET 0x04 +#define HCI_ISO_DATA_PACKET 0x05 + +/** + Other assigned numbers, Assigned_Numbers_Host Controller Interface.pdf +*/ + +typedef enum { + HCI_AUDIO_CODING_FORMAT_U_LAW_LOG = 0x00, + HCI_AUDIO_CODING_FORMAT_A_LAW_LOG, + HCI_AUDIO_CODING_FORMAT_CVSD, + HCI_AUDIO_CODING_FORMAT_TRANSPARENT, // Indicates that the controller does not do any transcoding or resampling. This is also used for test mode. + HCI_AUDIO_CODING_FORMAT_LINEAR_PCM, + HCI_AUDIO_CODING_FORMAT_MSBC, + HCI_AUDIO_CODING_FORMAT_LC3, + HCI_AUDIO_CODING_FORMAT_G_729A, + HCI_AUDIO_CODING_FORMAT_RFU, + HCI_AUDIO_CODING_FORMAT_VENDOR_SPECIFIC = 0xFF +} hci_audio_coding_format_t; + +/** + HCI Layer +*/ + +// +// Error Codes rfom Bluetooth Core Specification +// + +/* ENUM_START: BLUETOOTH_ERROR_CODE */ +#define ERROR_CODE_SUCCESS 0x00 +#define ERROR_CODE_UNKNOWN_HCI_COMMAND 0x01 +#define ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER 0x02 +#define ERROR_CODE_HARDWARE_FAILURE 0x03 +#define ERROR_CODE_PAGE_TIMEOUT 0x04 +#define ERROR_CODE_AUTHENTICATION_FAILURE 0x05 +#define ERROR_CODE_PIN_OR_KEY_MISSING 0x06 +#define ERROR_CODE_MEMORY_CAPACITY_EXCEEDED 0x07 +#define ERROR_CODE_CONNECTION_TIMEOUT 0x08 +#define ERROR_CODE_CONNECTION_LIMIT_EXCEEDED 0x09 +#define ERROR_CODE_SYNCHRONOUS_CONNECTION_LIMIT_TO_A_DEVICE_EXCEEDED 0x0A +#define ERROR_CODE_ACL_CONNECTION_ALREADY_EXISTS 0x0B +#define ERROR_CODE_COMMAND_DISALLOWED 0x0C +#define ERROR_CODE_CONNECTION_REJECTED_DUE_TO_LIMITED_RESOURCES 0x0D +#define ERROR_CODE_CONNECTION_REJECTED_DUE_TO_SECURITY_REASONS 0x0E +#define ERROR_CODE_CONNECTION_REJECTED_DUE_TO_UNACCEPTABLE_BD_ADDR 0x0F +#define ERROR_CODE_CONNECTION_ACCEPT_TIMEOUT_EXCEEDED 0x10 +#define ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE 0x11 +#define ERROR_CODE_INVALID_HCI_COMMAND_PARAMETERS 0x12 +#define ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION 0x13 +#define ERROR_CODE_REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_LOW_RESOURCES 0x14 +#define ERROR_CODE_REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_POWER_OFF 0x15 +#define ERROR_CODE_CONNECTION_TERMINATED_BY_LOCAL_HOST 0x16 +#define ERROR_CODE_REPEATED_ATTEMPTS 0x17 +#define ERROR_CODE_PAIRING_NOT_ALLOWED 0x18 +#define ERROR_CODE_UNKNOWN_LMP_PDU 0x19 +#define ERROR_CODE_UNSUPPORTED_REMOTE_FEATURE_UNSUPPORTED_LMP_FEATURE 0x1A +#define ERROR_CODE_SCO_OFFSET_REJECTED 0x1B +#define ERROR_CODE_SCO_INTERVAL_REJECTED 0x1C +#define ERROR_CODE_SCO_AIR_MODE_REJECTED 0x1D +#define ERROR_CODE_INVALID_LMP_PARAMETERS_INVALID_LL_PARAMETERS 0x1E +#define ERROR_CODE_UNSPECIFIED_ERROR 0x1F +#define ERROR_CODE_UNSUPPORTED_LMP_PARAMETER_VALUE_UNSUPPORTED_LL_PARAMETER_VALUE 0x20 +#define ERROR_CODE_ROLE_CHANGE_NOT_ALLOWED 0x21 +#define ERROR_CODE_LMP_RESPONSE_TIMEOUT_LL_RESPONSE_TIMEOUT 0x22 +#define ERROR_CODE_LMP_ERROR_TRANSACTION_COLLISION 0x23 +#define ERROR_CODE_LMP_PDU_NOT_ALLOWED 0x24 +#define ERROR_CODE_ENCRYPTION_MODE_NOT_ACCEPTABLE 0x25 +#define ERROR_CODE_LINK_KEY_CANNOT_BE_CHANGED 0x26 +#define ERROR_CODE_REQUESTED_QOS_NOT_SUPPORTED 0x27 +#define ERROR_CODE_INSTANT_PASSED 0x28 +#define ERROR_CODE_PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED 0x29 +#define ERROR_CODE_DIFFERENT_TRANSACTION_COLLISION 0x2A +#define ERROR_CODE_RESERVED 0x2B +#define ERROR_CODE_QOS_UNACCEPTABLE_PARAMETER 0x2C +#define ERROR_CODE_QOS_REJECTED 0x2D +#define ERROR_CODE_CHANNEL_CLASSIFICATION_NOT_SUPPORTED 0x2E +#define ERROR_CODE_INSUFFICIENT_SECURITY 0x2F +#define ERROR_CODE_PARAMETER_OUT_OF_MANDATORY_RANGE 0x30 +// #define ERROR_CODE_RESERVED +#define ERROR_CODE_ROLE_SWITCH_PENDING 0x32 +// #define ERROR_CODE_RESERVED +#define ERROR_CODE_RESERVED_SLOT_VIOLATION 0x34 +#define ERROR_CODE_ROLE_SWITCH_FAILED 0x35 +#define ERROR_CODE_EXTENDED_INQUIRY_RESPONSE_TOO_LARGE 0x36 +#define ERROR_CODE_SECURE_SIMPLE_PAIRING_NOT_SUPPORTED_BY_HOST 0x37 +#define ERROR_CODE_HOST_BUSY_PAIRING 0x38 +#define ERROR_CODE_CONNECTION_REJECTED_DUE_TO_NO_SUITABLE_CHANNEL_FOUND 0x39 +#define ERROR_CODE_CONTROLLER_BUSY 0x3A +#define ERROR_CODE_UNACCEPTABLE_CONNECTION_PARAMETERS 0x3B +#define ERROR_CODE_DIRECTED_ADVERTISING_TIMEOUT 0x3C +#define ERROR_CODE_CONNECTION_TERMINATED_DUE_TO_MIC_FAILURE 0x3D +#define ERROR_CODE_CONNECTION_FAILED_TO_BE_ESTABLISHED 0x3E +#define ERROR_CODE_MAC_CONNECTION_FAILED 0x3F +#define ERROR_CODE_COARSE_CLOCK_ADJUSTMENT_REJECTED_BUT_WILL_TRY_TO_ADJUST_USING_CLOCK_DRAGGING 0x40 + +// BTstack defined ERRORS, mapped into BLuetooth status code range + +#define BTSTACK_CONNECTION_TO_BTDAEMON_FAILED 0x50 +#define BTSTACK_ACTIVATION_FAILED_SYSTEM_BLUETOOTH 0x51 +#define BTSTACK_ACTIVATION_POWERON_FAILED 0x52 +#define BTSTACK_ACTIVATION_FAILED_UNKNOWN 0x53 +#define BTSTACK_NOT_ACTIVATED 0x54 +#define BTSTACK_BUSY 0x55 +#define BTSTACK_MEMORY_ALLOC_FAILED 0x56 +#define BTSTACK_ACL_BUFFERS_FULL 0x57 + +// l2cap errors - enumeration by the command that created them +#define L2CAP_COMMAND_REJECT_REASON_COMMAND_NOT_UNDERSTOOD 0x60 +#define L2CAP_COMMAND_REJECT_REASON_SIGNALING_MTU_EXCEEDED 0x61 +#define L2CAP_COMMAND_REJECT_REASON_INVALID_CID_IN_REQUEST 0x62 + +#define L2CAP_CONNECTION_RESPONSE_RESULT_SUCCESSFUL 0x63 +#define L2CAP_CONNECTION_RESPONSE_RESULT_PENDING 0x64 +#define L2CAP_CONNECTION_RESPONSE_RESULT_REFUSED_PSM 0x65 +#define L2CAP_CONNECTION_RESPONSE_RESULT_REFUSED_SECURITY 0x66 +#define L2CAP_CONNECTION_RESPONSE_RESULT_REFUSED_RESOURCES 0x67 +#define L2CAP_CONNECTION_RESPONSE_RESULT_ERTM_NOT_SUPPORTED 0x68 + +// should be L2CAP_CONNECTION_RTX_TIMEOUT +#define L2CAP_CONNECTION_RESPONSE_RESULT_RTX_TIMEOUT 0x69 +#define L2CAP_CONNECTION_BASEBAND_DISCONNECT 0x6A +#define L2CAP_SERVICE_ALREADY_REGISTERED 0x6B +#define L2CAP_DATA_LEN_EXCEEDS_REMOTE_MTU 0x6C +#define L2CAP_SERVICE_DOES_NOT_EXIST 0x6D +#define L2CAP_LOCAL_CID_DOES_NOT_EXIST 0x6E +#define L2CAP_CONNECTION_RESPONSE_UNKNOWN_ERROR 0x6F + +#define RFCOMM_MULTIPLEXER_STOPPED 0x70 +#define RFCOMM_CHANNEL_ALREADY_REGISTERED 0x71 +#define RFCOMM_NO_OUTGOING_CREDITS 0x72 +#define RFCOMM_AGGREGATE_FLOW_OFF 0x73 +#define RFCOMM_DATA_LEN_EXCEEDS_MTU 0x74 + +#define HFP_REMOTE_REJECTS_AUDIO_CONNECTION 0x7F + +#define SDP_HANDLE_ALREADY_REGISTERED 0x80 +#define SDP_QUERY_INCOMPLETE 0x81 +#define SDP_SERVICE_NOT_FOUND 0x82 +#define SDP_HANDLE_INVALID 0x83 +#define SDP_QUERY_BUSY 0x84 + +#define ATT_HANDLE_VALUE_INDICATION_IN_PROGRESS 0x90 +#define ATT_HANDLE_VALUE_INDICATION_TIMEOUT 0x91 +#define ATT_HANDLE_VALUE_INDICATION_DISCONNECT 0x92 + +#define GATT_CLIENT_NOT_CONNECTED 0x93 +#define GATT_CLIENT_BUSY 0x94 +#define GATT_CLIENT_IN_WRONG_STATE 0x95 +#define GATT_CLIENT_DIFFERENT_CONTEXT_FOR_ADDRESS_ALREADY_EXISTS 0x96 +#define GATT_CLIENT_VALUE_TOO_LONG 0x97 +#define GATT_CLIENT_CHARACTERISTIC_NOTIFICATION_NOT_SUPPORTED 0x98 +#define GATT_CLIENT_CHARACTERISTIC_INDICATION_NOT_SUPPORTED 0x99 + +#define BNEP_SERVICE_ALREADY_REGISTERED 0xA0 +#define BNEP_CHANNEL_NOT_CONNECTED 0xA1 +#define BNEP_DATA_LEN_EXCEEDS_MTU 0xA2 + +// OBEX ERRORS +#define OBEX_UNKNOWN_ERROR 0xB0 +#define OBEX_CONNECT_FAILED 0xB1 +#define OBEX_DISCONNECTED 0xB2 +#define OBEX_NOT_FOUND 0xB3 +#define OBEX_NOT_ACCEPTABLE 0xB4 +#define OBEX_ABORTED 0xB5 + +#define MESH_ERROR_APPKEY_INDEX_INVALID 0xD0 +/* ENUM_END */ + + +/* ENUM_START: AVRCP_BROWSING_ERROR_CODE */ +#define AVRCP_BROWSING_ERROR_CODE_INVALID_COMMAND 0x00 // Sent if TG received a PDU that it did not understand. Valid for All. +#define AVRCP_BROWSING_ERROR_CODE_INVALID_PARAMETER 0x01 // Sent if the TG received a PDU with a parameter ID that it did not understand. Sent if there is only one parameter ID in the PDU. Valid for All. +#define AVRCP_BROWSING_ERROR_CODE_SPECIFIED_PARAMETER_NOT_FOUND 0x02 // Sent if the parameter ID is understood, but content is wrong or corrupted. Valid for All. +#define AVRCP_BROWSING_ERROR_CODE_INTERNAL_ERROR 0x03 // Sent if there are error conditions not covered by a more specific error code. Valid for All. +#define AVRCP_BROWSING_ERROR_CODE_SUCCESS 0x04 // This is the status that should be returned if the operation was successful. Valid for All except where the response CType is AV/C REJECTED. +#define AVRCP_BROWSING_ERROR_CODE_UID_CHANGED 0x05 // The UIDs on the device have changed. Valid for All. +#define AVRCP_BROWSING_ERROR_CODE_RESERVED_06 0x06 // Valid for All. +#define AVRCP_BROWSING_ERROR_CODE_INVALID_DIRECTION 0x07 // The Direction parameter is invalid. Valid for Change Path. +#define AVRCP_BROWSING_ERROR_CODE_NOT_A_DIRECTORY 0x08 // The UID provided does not refer to a folder item. Valid for Change Path. +#define AVRCP_BROWSING_ERROR_CODE_DOES_NOT_EXIST 0x09 // The UID provided does not refer to any currently valid. Valid for Change Path, PlayItem, AddToNowPlaying, GetItemAttributes. +#define AVRCP_BROWSING_ERROR_CODE_INVALID_SCOPE 0x0a // The scope parameter is invalid. Valid for GetFolderItems, PlayItem, AddToNowPlayer, GetItemAttributes,. +#define AVRCP_BROWSING_ERROR_CODE_RANGE_OUT_OF_BOUNDS 0x0b // The start of range provided is not valid. Valid for GetFolderItems. +#define AVRCP_BROWSING_ERROR_CODE_UID_IS_A_DIRECTORY 0x0c // The UID provided refers to a directory, which cannot be handled by this media player. Valid for PlayItem, AddToNowPlaying. +#define AVRCP_BROWSING_ERROR_CODE_MEDIA_IN_USES 0x0d // The media is not able to be used for this operation at this time. Valid for PlayItem, AddToNowPlaying. +#define AVRCP_BROWSING_ERROR_CODE_NOW_PLAYING_LIST_FULL 0x0e // No more items can be added to the Now Playing List. Valid for AddToNowPlaying. +#define AVRCP_BROWSING_ERROR_CODE_SEARCH_NOT_SUPPORTED 0x0f // The Browsed Media Player does not support search. Valid for Search. +#define AVRCP_BROWSING_ERROR_CODE_SEARCH_IN_PROGRESS 0x10 // A search operation is already in progress. Valid for Search. +#define AVRCP_BROWSING_ERROR_CODE_INVALID_PLAYER_ID 0x11 // The specified Player Id does not refer to a valid player. Valid for SetAddressedPlayer, SetBrowsedPlayer. +#define AVRCP_BROWSING_ERROR_CODE_PLAYER_NOT_BROWSABLE 0x12 // The Player Id supplied refers to a Media Player which does not support browsing. Valid for SetBrowsedPlayer. +#define AVRCP_BROWSING_ERROR_CODE_PLAYER_NOT_ADDRESSED 0x13 // The Player Id supplied refers to a player which is not currently addressed, and the command is not able to be performed if the player is not set as addressed. Valid for Search SetBrowsedPlayer. +#define AVRCP_BROWSING_ERROR_CODE_NO_VALID_SEARCH_RESULTS 0x14 // The Search result list does not contain valid entries, e.g. after being invalidated due to change of browsed player. Valid for GetFolderItems. +#define AVRCP_BROWSING_ERROR_CODE_NO_AVAILABLE_PLAYERS 0x15 // Valid for All. +#define AVRCP_BROWSING_ERROR_CODE_ADDRESSED_PLAYER_CHANGED 0x16 // Valid for Register Notification. +// 0x17-0xff Reserved +/* ENUM_END */ + +// HCI roles +typedef enum { + HCI_ROLE_MASTER = 0, + HCI_ROLE_SLAVE = 1, + HCI_ROLE_INVALID = 0xff, +} hci_role_t; + +// packet sizes (max payload) +#define HCI_ACL_DM1_SIZE 17 +#define HCI_ACL_DH1_SIZE 27 +#define HCI_ACL_2DH1_SIZE 54 +#define HCI_ACL_3DH1_SIZE 83 +#define HCI_ACL_DM3_SIZE 121 +#define HCI_ACL_DH3_SIZE 183 +#define HCI_ACL_DM5_SIZE 224 +#define HCI_ACL_DH5_SIZE 339 +#define HCI_ACL_2DH3_SIZE 367 +#define HCI_ACL_3DH3_SIZE 552 +#define HCI_ACL_2DH5_SIZE 679 +#define HCI_ACL_3DH5_SIZE 1021 +#define HCI_SCO_HV1_SIZE 10 +#define HCI_SCO_HV2_SIZE 20 +#define HCI_SCO_HV3_SIZE 30 +#define HCI_SCO_EV3_SIZE 30 +#define HCI_SCO_EV4_SIZE 120 +#define HCI_SCO_EV5_SIZE 180 +#define HCI_SCO_2EV3_SIZE 60 +#define HCI_SCO_2EV5_SIZE 360 +#define HCI_SCO_3EV3_SIZE 90 +#define HCI_SCO_3EV5_SIZE 540 + +#define LE_ADVERTISING_DATA_SIZE 31 +#define LE_EXTENDED_ADVERTISING_DATA_SIZE 229 +#define LE_EXTENDED_ADVERTISING_MAX_HANDLE 0xEFu +#define LE_EXTENDED_ADVERTISING_MAX_CHUNK_LEN 251 + +// advertising event properties for extended advertising +#define LE_ADVERTISING_PROPERTIES_CONNECTABLE (1u<<0) +#define LE_ADVERTISING_PROPERTIES_SCANNABLE (1u<<1) +#define LE_ADVERTISING_PROPERTIES_DIRECTED (1u<<2) +#define LE_ADVERTISING_PROPERTIES_HIGH_DUTY_CYCLE (1u<<3) +#define LE_ADVERTISING_PROPERTIES_LEGACY (1u<<4) +#define LE_ADVERTISING_PROPERTIES_ANONYMOUS (1u<<5) +#define LE_ADVERTISING_PROPERTIES_INCLUDE_TX_POWER (1u<<6) + + +// SCO Packet Types +#define SCO_PACKET_TYPES_NONE 0x0000 +#define SCO_PACKET_TYPES_HV1 0x0001 +#define SCO_PACKET_TYPES_HV2 0x0002 +#define SCO_PACKET_TYPES_HV3 0x0004 +#define SCO_PACKET_TYPES_EV3 0x0008 +#define SCO_PACKET_TYPES_EV4 0x0010 +#define SCO_PACKET_TYPES_EV5 0x0020 +#define SCO_PACKET_TYPES_2EV3 0x0040 +#define SCO_PACKET_TYPES_3EV3 0x0080 +#define SCO_PACKET_TYPES_2EV5 0x0100 +#define SCO_PACKET_TYPES_3EV5 0x0200 +#define SCO_PACKET_TYPES_ALL 0x03FF +#define SCO_PACKET_TYPES_SCO 0x0007 +#define SCO_PACKET_TYPES_ESCO 0x03F8 + +// Link Policy Settings +#define LM_LINK_POLICY_DISABLE_ALL_LM_MODES 0 +#define LM_LINK_POLICY_ENABLE_ROLE_SWITCH 1 +#define LM_LINK_POLICY_ENABLE_HOLD_MODE 2 +#define LM_LINK_POLICY_ENABLE_SNIFF_MODE 4 + +// ACL Connection Modes +#define ACL_CONNECTION_MODE_ACTIVE 0 +#define ACL_CONNECTION_MODE_HOLD 1 +#define ACL_CONNECTION_MODE_SNIFF 2 + +/** + Default INQ Mode +*/ +#define GAP_IAC_GENERAL_INQUIRY 0x9E8B33L // General/Unlimited Inquiry Access Code (GIAC) +#define GAP_IAC_LIMITED_INQUIRY 0x9E8B00L // Limited Dedicated Inquiry Access Code (LIAC) + +/** + SSP IO Capabilities +*/ +#define SSP_IO_CAPABILITY_DISPLAY_ONLY 0 +#define SSP_IO_CAPABILITY_DISPLAY_YES_NO 1 +#define SSP_IO_CAPABILITY_KEYBOARD_ONLY 2 +#define SSP_IO_CAPABILITY_NO_INPUT_NO_OUTPUT 3 +#define SSP_IO_CAPABILITY_UNKNOWN 0xff + + +/** + SSP Authentication Requirements, see IO Capability Request Reply Command +*/ + +// Numeric comparison with automatic accept allowed. +#define SSP_IO_AUTHREQ_MITM_PROTECTION_NOT_REQUIRED_NO_BONDING 0x00 + +// Use IO Capabilities to deter- mine authentication procedure +#define SSP_IO_AUTHREQ_MITM_PROTECTION_REQUIRED_NO_BONDING 0x01 + +// Numeric compar- ison with automatic accept allowed. +#define SSP_IO_AUTHREQ_MITM_PROTECTION_NOT_REQUIRED_DEDICATED_BONDING 0x02 + +// Use IO Capabilities to determine authentication procedure +#define SSP_IO_AUTHREQ_MITM_PROTECTION_REQUIRED_DEDICATED_BONDING 0x03 + +// Numeric Compari- son with automatic accept allowed. +#define SSP_IO_AUTHREQ_MITM_PROTECTION_NOT_REQUIRED_GENERAL_BONDING 0x04 + +// Use IO capabilities to determine authentication procedure. +#define SSP_IO_AUTHREQ_MITM_PROTECTION_REQUIRED_GENERAL_BONDING 0x05 + + +// OGFs +#define OGF_LINK_CONTROL 0x01 +#define OGF_LINK_POLICY 0x02 +#define OGF_CONTROLLER_BASEBAND 0x03 +#define OGF_INFORMATIONAL_PARAMETERS 0x04 +#define OGF_STATUS_PARAMETERS 0x05 +#define OGF_TESTING 0x06 +#define OGF_LE_CONTROLLER 0x08 +#define OGF_VENDOR 0x3f + + + + +/** + L2CAP Layer +*/ + +#define L2CAP_HEADER_SIZE 4 + +// minimum signaling MTU +#define L2CAP_MINIMAL_MTU 48 +#define L2CAP_DEFAULT_MTU 672 + +// Minimum/default MTU +#define L2CAP_LE_DEFAULT_MTU 23 + +// L2CAP Fixed Channel IDs +#define L2CAP_CID_SIGNALING 0x0001 +#define L2CAP_CID_CONNECTIONLESS_CHANNEL 0x0002 +#define L2CAP_CID_ATTRIBUTE_PROTOCOL 0x0004 +#define L2CAP_CID_SIGNALING_LE 0x0005 +#define L2CAP_CID_SECURITY_MANAGER_PROTOCOL 0x0006 +#define L2CAP_CID_BR_EDR_SECURITY_MANAGER 0x0007 + +// L2CAP Channels in Basic and Enhanced Retransmission Mode + +// connection response result +#define L2CAP_CONNECTION_RESULT_SUCCESS 0x0000 +#define L2CAP_CONNECTION_RESULT_PENDING 0x0001 +#define L2CAP_CONNECTION_RESULT_PSM_NOT_SUPPORTED 0x0002 +#define L2CAP_CONNECTION_RESULT_SECURITY_BLOCK 0x0003 +#define L2CAP_CONNECTION_RESULT_NO_RESOURCES_AVAILABLE 0x0004 +#define L2CAP_CONNECTION_RESULT_INVALID_SOURCE_CID 0x0006 +#define L2CAP_CONNECTION_RESULT_SOURCE_CID_ALREADY_ALLOCATED 0x0007 + +// L2CAP Channels in LE Credit-Based Flow-Control Mode + +// connection response result +#define L2CAP_CBM_CONNECTION_RESULT_SUCCESS 0x0000 +#define L2CAP_CBM_CONNECTION_RESULT_SPSM_NOT_SUPPORTED 0x0002 +#define L2CAP_CBM_CONNECTION_RESULT_NO_RESOURCES_AVAILABLE 0x0004 +#define L2CAP_CBM_CONNECTION_RESULT_INSUFFICIENT_AUTHENTICATION 0x0005 +#define L2CAP_CBM_CONNECTION_RESULT_INSUFFICIENT_AUTHORIZATION 0x0006 +#define L2CAP_CBM_CONNECTION_RESULT_ENCYRPTION_KEY_SIZE_TOO_SHORT 0x0007 +#define L2CAP_CBM_CONNECTION_RESULT_INSUFFICIENT_ENCRYPTION 0x0008 +#define L2CAP_CBM_CONNECTION_RESULT_INVALID_SOURCE_CID 0x0009 +#define L2CAP_CBM_CONNECTION_RESULT_SOURCE_CID_ALREADY_ALLOCATED 0x000A +#define L2CAP_CBM_CONNECTION_RESULT_UNACCEPTABLE_PARAMETERS 0x000B + + +// L2CAP Channels in Enhanced Credit-Based Flow-Control Mode + +// number of CIDs in single connection+reconfiguration request/response +#define L2CAP_ECBM_MAX_CID_ARRAY_SIZE 5 + +// connection response result +#define L2CAP_ECBM_CONNECTION_RESULT_ALL_SUCCESS 0x0000 +#define L2CAP_ECBM_CONNECTION_RESULT_ALL_REFUSED_SPSM_NOT_SUPPORTED 0x0002 +#define L2CAP_ECBM_CONNECTION_RESULT_SOME_REFUSED_INSUFFICIENT_RESOURCES_AVAILABLE 0x0004 +#define L2CAP_ECBM_CONNECTION_RESULT_ALL_REFUSED_INSUFFICIENT_AUTHENTICATION 0x0005 +#define L2CAP_ECBM_CONNECTION_RESULT_ALL_REFUSED_INSUFFICIENT_AUTHORIZATION 0x0006 +#define L2CAP_ECBM_CONNECTION_RESULT_ALL_REFUSED_ENCYRPTION_KEY_SIZE_TOO_SHORT 0x0007 +#define L2CAP_ECBM_CONNECTION_RESULT_ALL_REFUSED_INSUFFICIENT_ENCRYPTION 0x0008 +#define L2CAP_ECBM_CONNECTION_RESULT_SOME_REFUSED_INVALID_SOURCE_CID 0x0009 +#define L2CAP_ECBM_CONNECTION_RESULT_SOME_REFUSED_SOURCE_CID_ALREADY_ALOCATED 0x000A +#define L2CAP_ECBM_CONNECTION_RESULT_ALL_REFUSED_UNACCEPTABLE_PARAMETERS 0x000B +#define L2CAP_ECBM_CONNECTION_RESULT_ALL_REFUSED_INVALID_PARAMETERS 0x000C +#define L2CAP_ECBM_CONNECTION_RESULT_ALL_PENDING_NO_FURTHER_INFORMATION 0x000D +#define L2CAP_ECBM_CONNECTION_RESULT_ALL_PENDING_AUTHENTICATION 0x000E +#define L2CAP_ECBM_CONNECTION_RESULT_ALL_PENDING_AUTHORIZATION 0x000F + + +// Result for Reconfigure Request +#define L2CAP_ECBM_RECONFIGURE_SUCCESS 0 +#define L2CAP_ECBM_RECONFIGURE_FAILED_MTU_REDUCTION_NOT_ALLOWED 1 +#define L2CAP_ECBM_RECONFIGURE_FAILED_MPS_REDUCTION_MULTIPLE_CHANNELS 2 +#define L2CAP_ECBM_RECONFIGURE_FAILED_DESTINATION_CID_INVALID 3 +#define L2CAP_ECBM_RECONFIGURE_FAILED_UNACCEPTABLE_PARAMETERS 4 + +/** + SDP Protocol +*/ + +// Device Vendor ID Sources +#define DEVICE_ID_VENDOR_ID_SOURCE_BLUETOOTH 0x0001 +#define DEVICE_ID_VENDOR_ID_SOURCE_USB 0x0002 + +// OBEX +#define SDP_vCard_2_1 0x01 +#define SDP_vCard_3_0 0x02 +#define SDP_vCal_1_0 0x03 +#define SDP_iCal_2_0 0x04 +#define SDP_vNote 0x05 +#define SDP_vMessage 0x06 +#define SDP_OBEXFileTypeAny 0xFF + +/** + RFCOMM Protocol +*/ + +// Line Status +#define LINE_STATUS_NO_ERROR 0x00 +#define LINE_STATUS_OVERRUN_ERROR 0x03 +#define LINE_STATUS_PARITY_ERORR 0x05 +#define LINE_STATUS_FRAMING_ERROR 0x09 + +// Modem Status Flags +#define MODEM_STATUS_FC 0x02 +#define MODEM_STATUS_RTC 0x04 +#define MODEM_STATUS_RTR 0x08 +#define MODEM_STATUS_IC 0x40 +#define MODEM_STATUS_DV 0x80 + +typedef enum rpn_baud { + RPN_BAUD_2400 = 0, + RPN_BAUD_4800, + RPN_BAUD_7200, + RPN_BAUD_9600, + RPN_BAUD_19200, + RPN_BAUD_38400, + RPN_BAUD_57600, + RPN_BAUD_115200, + RPN_BAUD_230400 +} rpn_baud_t; + +typedef enum rpn_data_bits { + RPN_DATA_BITS_5 = 0, + RPN_DATA_BITS_6 = 0, + RPN_DATA_BITS_7 = 0, + RPN_DATA_BITS_8 = 0 +} rpn_data_bits_t; + +typedef enum rpn_stop_bits { + RPN_STOP_BITS_1_0 = 0, + RPN_STOP_BITS_1_5 +} rpn_stop_bits_t; + +typedef enum rpn_parity { + RPN_PARITY_NONE = 0, + RPN_PARITY_ODD = 1, + RPN_PARITY_EVEN = 3, + RPN_PARITY_MARK = 5, + RPN_PARITY_SPACE = 7, +} rpn_parity_t; + +#define RPN_FLOW_CONTROL_XONXOFF_ON_INPUT 0x01 +#define RPN_FLOW_CONTROL_XONXOFF_ON_OUTPUT 0x02 +#define RPN_FLOW_CONTROL_RTR_ON_INPUT 0x04 +#define RPN_FLOW_CONTROL_RTR_ON_OUTPUT 0x08 +#define RPN_FLOW_CONTROL_RTC_ON_INPUT 0x10 +#define RPN_FLOW_CONTROL_RTC_ON_OUTPUT 0x20 + +#define RPN_PARAM_MASK_0_BAUD 0x01 +#define RPN_PARAM_MASK_0_DATA_BITS 0x02 +#define RPN_PARAM_MASK_0_STOP_BITS 0x04 +#define RPN_PARAM_MASK_0_PARITY 0x08 +#define RPN_PARAM_MASK_0_PARITY_TYPE 0x10 +#define RPN_PARAM_MASK_0_XON_CHAR 0x20 +#define RPN_PARAM_MASK_0_XOFF_CHAR 0x40 +#define RPN_PARAM_MASK_0_RESERVED 0x80 + +// @note: values are identical to rpn_flow_control_t +#define RPN_PARAM_MASK_1_XONOFF_ON_INPUT 0x01 +#define RPN_PARAM_MASK_1_XONOFF_ON_OUTPUT 0x02 +#define RPN_PARAM_MASK_1_RTR_ON_INPUT 0x04 +#define RPN_PARAM_MASK_1_RTR_ON_OUTPUT 0x08 +#define RPN_PARAM_MASK_1_RTC_ON_INPUT 0x10 +#define RPN_PARAM_MASK_1_RTC_ON_OUTPUT 0x20 +#define RPN_PARAM_MASK_1_RESERVED_0 0x40 +#define RPN_PARAM_MASK_1_RESERVED_1 0x80 + +/** + BNEP Protocol +*/ + +#ifndef ETHER_ADDR_LEN +#define ETHER_ADDR_LEN 6 +#endif + +#ifndef ETHERTYPE_VLAN +#define ETHERTYPE_VLAN 0x8100 /* IEEE 802.1Q VLAN tag */ +#endif + +#define BNEP_MTU_MIN 1691 + + +/** + PAN Profile +*/ + +typedef enum { + BNEP_SECURITY_NONE = 0x0000, + BNEP_SECURITY_SERVICE_LEVEL_ENFORCED, + BNEP_SECURITY_802_1X +} security_description_t; + +typedef enum { + PAN_NET_ACCESS_TYPE_PSTN = 0x0000, + PAN_NET_ACCESS_TYPE_ISDN, + PAN_NET_ACCESS_TYPE_DSL, + PAN_NET_ACCESS_TYPE_CABLE_MODEM, + PAN_NET_ACCESS_TYPE_10MB_ETHERNET, + PAN_NET_ACCESS_TYPE_100MB_ETHERNET, + PAN_NET_ACCESS_TYPE_4MB_TOKEN_RING, + PAN_NET_ACCESS_TYPE_16MB_TOKEN_RING, + PAN_NET_ACCESS_TYPE_100MB_TOKEN_RING, + PAN_NET_ACCESS_TYPE_FDDI, + PAN_NET_ACCESS_TYPE_GSM, + PAN_NET_ACCESS_TYPE_CDMA, + PAN_NET_ACCESS_TYPE_GPRS, + PAN_NET_ACCESS_TYPE_3G, + PAN_NET_ACCESS_TYPE_CELULAR, + PAN_NET_ACCESS_TYPE_OTHER = 0xFFFE, + PAN_NET_ACCESS_TYPE_NONE +} net_access_type_t; + +/** + ATT +*/ + +// Minimum/default MTU +#define ATT_DEFAULT_MTU 23 + +// MARK: ATT Error Codes +#define ATT_ERROR_SUCCESS 0x00 +#define ATT_ERROR_INVALID_HANDLE 0x01 +#define ATT_ERROR_READ_NOT_PERMITTED 0x02 +#define ATT_ERROR_WRITE_NOT_PERMITTED 0x03 +#define ATT_ERROR_INVALID_PDU 0x04 +#define ATT_ERROR_INSUFFICIENT_AUTHENTICATION 0x05 +#define ATT_ERROR_REQUEST_NOT_SUPPORTED 0x06 +#define ATT_ERROR_INVALID_OFFSET 0x07 +#define ATT_ERROR_INSUFFICIENT_AUTHORIZATION 0x08 +#define ATT_ERROR_PREPARE_QUEUE_FULL 0x09 +#define ATT_ERROR_ATTRIBUTE_NOT_FOUND 0x0a +#define ATT_ERROR_ATTRIBUTE_NOT_LONG 0x0b +#define ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE 0x0c +#define ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LENGTH 0x0d +#define ATT_ERROR_UNLIKELY_ERROR 0x0e +#define ATT_ERROR_INSUFFICIENT_ENCRYPTION 0x0f +#define ATT_ERROR_UNSUPPORTED_GROUP_TYPE 0x10 +#define ATT_ERROR_INSUFFICIENT_RESOURCES 0x11 +#define ATT_ERROR_VALUE_NOT_ALLOWED 0x13 + +// MARK: ATT Error Codes defined by BTstack +#define ATT_ERROR_HCI_DISCONNECT_RECEIVED 0x1f +#define ATT_ERROR_BONDING_INFORMATION_MISSING 0x70 +#define ATT_ERROR_DATA_MISMATCH 0x7e +#define ATT_ERROR_TIMEOUT 0x7F +#define ATT_ERROR_WRITE_RESPONSE_PENDING 0x100 + +// MARK: ATT Error Codes from Bluetooth Core Specification Supplement, Version 9 or later +#define ATT_ERROR_WRITE_REQUEST_REJECTED 0xFC +#define ATT_ERROR_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_IMPROPERLY_CONFIGURED 0xFD +#define ATT_ERROR_PROCEDURE_ALREADY_IN_PROGRESS 0xFE +#define ATT_ERROR_OUT_OF_RANGE 0xFF + +// MARK: ATT Error Codes from Cycling Power Service spec +#define CYCLING_POWER_ERROR_CODE_INAPPROPRIATE_CONNECTION_PARAMETERS 0x80 +#define CYCLING_POWER_ERROR_CODE_PROCEDURE_ALREADY_IN_PROGRESS 0xFE +#define CYCLING_POWER_ERROR_CODE_CCC_DESCRIPTOR_IMPROPERLY_CONFIGURED 0xFD + +// MARK: ATT Error Codes from Cycling Speed and Cadence Service spec +#define CYCLING_SPEED_AND_CADENCE_ERROR_CODE_PROCEDURE_ALREADY_IN_PROGRESS 0x80 +#define CYCLING_SPEED_AND_CADENCE_ERROR_CODE_CCC_DESCRIPTOR_IMPROPERLY_CONFIGURED 0x81 + + +// MARK: Attribute Property Flags +#define ATT_PROPERTY_BROADCAST 0x01 +#define ATT_PROPERTY_READ 0x02 +#define ATT_PROPERTY_WRITE_WITHOUT_RESPONSE 0x04 +#define ATT_PROPERTY_WRITE 0x08 +#define ATT_PROPERTY_NOTIFY 0x10 +#define ATT_PROPERTY_INDICATE 0x20 +#define ATT_PROPERTY_AUTHENTICATED_SIGNED_WRITE 0x40 +#define ATT_PROPERTY_EXTENDED_PROPERTIES 0x80 + +// MARK: Attribute Property Flag, BTstack extension +// value is asked from client +#define ATT_PROPERTY_DYNAMIC 0x100 + +// Security levels +#define ATT_SECURITY_NONE 0 +#define ATT_SECURITY_ENCRYPTED 1 +#define ATT_SECURITY_AUTHENTICATED 2 +#define ATT_SECURITY_AUTHORIZED 3 +#define ATT_SECURITY_AUTHENTICATED_SC 4 + +// ATT Transaction Timeout of 30 seconds for Command/Response or Indication/Confirmation +#define ATT_TRANSACTION_TIMEOUT_MS 30000 + +#define ATT_TRANSACTION_MODE_NONE 0x0 +#define ATT_TRANSACTION_MODE_ACTIVE 0x1 +#define ATT_TRANSACTION_MODE_EXECUTE 0x2 +#define ATT_TRANSACTION_MODE_CANCEL 0x3 +#define ATT_TRANSACTION_MODE_VALIDATE 0x4 + +// MARK: GATT UUIDs +#define GATT_PRIMARY_SERVICE_UUID 0x2800 +#define GATT_SECONDARY_SERVICE_UUID 0x2801 +#define GATT_INCLUDE_SERVICE_UUID 0x2802 +#define GATT_CHARACTERISTICS_UUID 0x2803 +#define GATT_CHARACTERISTIC_EXTENDED_PROPERTIES 0x2900 +#define GATT_CHARACTERISTIC_USER_DESCRIPTION 0x2901 +#define GATT_CLIENT_CHARACTERISTICS_CONFIGURATION 0x2902 +#define GATT_SERVER_CHARACTERISTICS_CONFIGURATION 0x2903 +#define GATT_CHARACTERISTIC_PRESENTATION_FORMAT 0x2904 +#define GATT_CHARACTERISTIC_AGGREGATE_FORMAT 0x2905 +#define GATT_CLIENT_SUPPORTED_FEATURES 0x2B29 +#define GATT_SERVER_SUPPORTED_FEATURES 0x2B3A + +#define GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NONE 0 +#define GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION 1 +#define GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_INDICATION 2 + +#define GATT_CLIENT_ANY_CONNECTION 0xffff +#define GATT_CLIENT_ANY_VALUE_HANDLE 0x0000 + +// GAP Service and Characteristics +#define GAP_SERVICE_UUID 0x1800 +#define GAP_DEVICE_NAME_UUID 0x2a00 +#define GAP_APPEARANCE_UUID 0x2a01 +#define GAP_PERIPHERAL_PRIVACY_FLAG 0x2a02 +#define GAP_RECONNECTION_ADDRESS_UUID 0x2a03 +#define GAP_PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS_UUID 0x2a04 +#define GAP_SERVICE_CHANGED 0x2a05 + +// Bluetooth GATT types + +typedef struct { + uint16_t year; // 0 - year is not known; or [1582,9999] + uint8_t month; // 0 - month is not known; or [1,12] + uint8_t day; // 0 - day is not known; or [1,31] + uint8_t hours; // [0,23] + uint8_t minutes; // [0,59] + uint8_t seconds; // [0,59] +} gatt_date_time_t; + +typedef enum { + GATT_MICROPHONE_CONTROL_MUTE_OFF = 0x00, + GATT_MICROPHONE_CONTROL_MUTE_ON, + GATT_MICROPHONE_CONTROL_MUTE_DISABLED +} gatt_microphone_control_mute_t; + +/** + SM - LE Security Manager +*/ +// Bluetooth Spec definitions +typedef enum { + SM_CODE_PAIRING_REQUEST = 0X01, + SM_CODE_PAIRING_RESPONSE, + SM_CODE_PAIRING_CONFIRM, + SM_CODE_PAIRING_RANDOM, + SM_CODE_PAIRING_FAILED, + SM_CODE_ENCRYPTION_INFORMATION, + SM_CODE_MASTER_IDENTIFICATION, + SM_CODE_IDENTITY_INFORMATION, + SM_CODE_IDENTITY_ADDRESS_INFORMATION, + SM_CODE_SIGNING_INFORMATION, + SM_CODE_SECURITY_REQUEST, + SM_CODE_PAIRING_PUBLIC_KEY, + SM_CODE_PAIRING_DHKEY_CHECK, + SM_CODE_KEYPRESS_NOTIFICATION, +} SECURITY_MANAGER_COMMANDS; + +// IO Capability Values +typedef enum { + IO_CAPABILITY_DISPLAY_ONLY = 0, + IO_CAPABILITY_DISPLAY_YES_NO, + IO_CAPABILITY_KEYBOARD_ONLY, + IO_CAPABILITY_NO_INPUT_NO_OUTPUT, + IO_CAPABILITY_KEYBOARD_DISPLAY, // not used by secure simple pairing +} io_capability_t; + +// Authentication requirement flags +#define SM_AUTHREQ_NO_BONDING 0x00 +#define SM_AUTHREQ_BONDING 0x01 +#define SM_AUTHREQ_MITM_PROTECTION 0x04 +#define SM_AUTHREQ_SECURE_CONNECTION 0x08 +#define SM_AUTHREQ_KEYPRESS 0x10 +#define SM_AUTHREQ_CT2 0x20 + +// Key distribution flags used by spec +#define SM_KEYDIST_ENC_KEY 0x01 +#define SM_KEYDIST_ID_KEY 0x02 +#define SM_KEYDIST_SIGN 0x04 +#define SM_KEYDIST_LINK_KEY 0x08 + +// Key distribution flags used internally +#define SM_KEYDIST_FLAG_ENCRYPTION_INFORMATION 0x01 +#define SM_KEYDIST_FLAG_MASTER_IDENTIFICATION 0x02 +#define SM_KEYDIST_FLAG_IDENTITY_INFORMATION 0x04 +#define SM_KEYDIST_FLAG_IDENTITY_ADDRESS_INFORMATION 0x08 +#define SM_KEYDIST_FLAG_SIGNING_IDENTIFICATION 0x10 + +// STK Generation Methods +#define SM_STK_GENERATION_METHOD_JUST_WORKS 0x01 +#define SM_STK_GENERATION_METHOD_OOB 0x02 +#define SM_STK_GENERATION_METHOD_PASSKEY 0x04 +#define SM_STK_GENERATION_METHOD_NUMERIC_COMPARISON 0x08 + +// Pairing Failed Reasons +#define SM_REASON_RESERVED 0x00 +#define SM_REASON_PASSKEY_ENTRY_FAILED 0x01 +#define SM_REASON_OOB_NOT_AVAILABLE 0x02 +#define SM_REASON_AUTHENTHICATION_REQUIREMENTS 0x03 +#define SM_REASON_CONFIRM_VALUE_FAILED 0x04 +#define SM_REASON_PAIRING_NOT_SUPPORTED 0x05 +#define SM_REASON_ENCRYPTION_KEY_SIZE 0x06 +#define SM_REASON_COMMAND_NOT_SUPPORTED 0x07 +#define SM_REASON_UNSPECIFIED_REASON 0x08 +#define SM_REASON_REPEATED_ATTEMPTS 0x09 +#define SM_REASON_INVALID_PARAMETERS 0x0a +#define SM_REASON_DHKEY_CHECK_FAILED 0x0b +#define SM_REASON_NUMERIC_COMPARISON_FAILED 0x0c +#define SM_REASON_BR_EDR_PAIRING_IN_PROGRESS 0x0d +#define SM_REASON_CROSS_TRANSPORT_KEY_DERIVATION_NOT_ALLOWED 0x0e +#define SM_REASON_KEY_REJECTED 0x0f + +// also, invalid parameters +// and reserved + +// Keypress Notifications +#define SM_KEYPRESS_PASSKEY_ENTRY_STARTED 0x00 +#define SM_KEYPRESS_PASSKEY_DIGIT_ENTERED 0x01 +#define SM_KEYPRESS_PASSKEY_DIGIT_ERASED 0x02 +#define SM_KEYPRESS_PASSKEY_CLEARED 0x03 +#define SM_KEYPRESS_PASSKEY_ENTRY_COMPLETED 0x04 + + +#endif diff --git a/cores/rp2040/sdkoverride/hids_device.c b/cores/rp2040/sdkoverride/hids_device.c new file mode 100644 index 000000000..17a74b24a --- /dev/null +++ b/cores/rp2040/sdkoverride/hids_device.c @@ -0,0 +1,524 @@ +/* + Copyright (C) 2014 BlueKitchen GmbH + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the names of + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + 4. Any redistribution, use, or modification is done solely for + personal benefit and not for any commercial purpose or for + monetary gain. + + THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Please inquire about commercial licensing options at + contact@bluekitchen-gmbh.com + +*/ + +#if defined ENABLE_CLASSIC + +#define BTSTACK_FILE__ "hids_device.c" + +/** + Implementation of the GATT HIDS Device + To use with your application, add '#import ' to your .gatt file +*/ + +#include "hids_device.h" + +#include "ble/att_db.h" +#include "ble/att_server.h" +#include "bluetooth_gatt.h" +#include "btstack_util.h" +#include "btstack_debug.h" + +#define HIDS_DEVICE_ERROR_CODE_INAPPROPRIATE_CONNECTION_PARAMETERS 0x80 + +// storage for 'generic' HID Device with single Input, Output, and Feature Report +static hids_device_report_t hid_reports_generic_storage[3]; + +typedef struct { + uint16_t con_handle; + + uint8_t hid_country_code; + const uint8_t * hid_descriptor; + uint16_t hid_descriptor_size; + + uint16_t hid_report_map_handle; + uint8_t hid_protocol_mode; + uint16_t hid_protocol_mode_value_handle; + + uint16_t hid_boot_mouse_input_value_handle; + uint16_t hid_boot_mouse_input_client_configuration_handle; + uint16_t hid_boot_mouse_input_client_configuration_value; + + uint16_t hid_boot_keyboard_input_value_handle; + uint16_t hid_boot_keyboard_input_client_configuration_handle; + uint16_t hid_boot_keyboard_input_client_configuration_value; + + hids_device_report_t * hid_reports; + + uint8_t hid_input_reports_num; + uint8_t hid_output_reports_num; + uint8_t hid_feature_reports_num; + + uint16_t hid_control_point_value_handle; + uint8_t hid_control_point_suspend; + + btstack_context_callback_registration_t battery_callback; +} hids_device_t; + +static hids_device_t hids_device; + +static btstack_packet_handler_t packet_handler; +static att_service_handler_t hid_service; + +// TODO: store hids device connection into list +static hids_device_t * hids_device_get_instance_for_con_handle(uint16_t con_handle) { + UNUSED(con_handle); + return &hids_device; +} + +static hids_device_t * hids_device_create_instance(void) { + memset(&hids_device, 0, sizeof(hids_device_t)); + return &hids_device; +} + +static hids_device_report_t * hids_device_get_report_for_client_configuration_handle(hids_device_t * device, uint16_t client_configuration_handle) { + uint8_t pos; + uint8_t total_reports = device->hid_input_reports_num + device->hid_output_reports_num + device->hid_feature_reports_num; + for (pos = 0 ; pos < total_reports ; pos++) { + if (device->hid_reports[pos].client_configuration_handle == client_configuration_handle) { + return &device->hid_reports[pos]; + } + } + return NULL; +} + +static hids_device_report_t * +hids_device_get_report_for_id_and_type(hids_device_t *device, uint16_t report_id, hid_report_type_t type) { + uint8_t pos; + uint8_t total_reports = device->hid_input_reports_num + device->hid_output_reports_num + device->hid_feature_reports_num; + for (pos = 0 ; pos < total_reports ; pos++) { + if ((device->hid_reports[pos].type == type) && (device->hid_reports[pos].id == report_id)) { + return &device->hid_reports[pos]; + } + } + return NULL; +} + +static void hids_device_emit_event_with_uint8(uint8_t event, hci_con_handle_t con_handle, uint8_t value) { + hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle); + if (!instance) { + log_error("no instance for handle 0x%02x", con_handle); + return; + } + + if (!packet_handler) { + return; + } + uint8_t buffer[6]; + buffer[0] = HCI_EVENT_HIDS_META; + buffer[1] = 4; + buffer[2] = event; + little_endian_store_16(buffer, 3, (uint16_t) con_handle); + buffer[5] = value; + (*packet_handler)(HCI_EVENT_PACKET, 0, buffer, sizeof(buffer)); +} + +static void hids_device_emit_event_with_uint8_uint8_t(uint8_t event, hci_con_handle_t con_handle, uint8_t value_1, uint8_t value_2) { + hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle); + if (!instance) { + log_error("no instance for handle 0x%02x", con_handle); + return; + } + + if (!packet_handler) { + return; + } + uint8_t buffer[7]; + buffer[0] = HCI_EVENT_HIDS_META; + buffer[1] = 4; + buffer[2] = event; + little_endian_store_16(buffer, 3, (uint16_t) con_handle); + buffer[5] = value_1; + buffer[6] = value_2; + (*packet_handler)(HCI_EVENT_PACKET, 0, buffer, sizeof(buffer)); +} + +static void hids_device_emit_event(uint8_t event, hci_con_handle_t con_handle) { + hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle); + if (!instance) { + log_error("no instance for handle 0x%02x", con_handle); + return; + } + + if (!packet_handler) { + return; + } + uint8_t buffer[5]; + buffer[0] = HCI_EVENT_HIDS_META; + buffer[1] = 4; + buffer[2] = event; + little_endian_store_16(buffer, 3, (uint16_t) con_handle); + (*packet_handler)(HCI_EVENT_PACKET, 0, buffer, sizeof(buffer)); +} + +static void hids_device_can_send_now(void * context) { + hci_con_handle_t con_handle = (hci_con_handle_t)(uintptr_t) context; + // notify client + hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle); + if (!instance) { + log_error("no instance for handle 0x%02x", con_handle); + return; + } + + if (!packet_handler) { + return; + } + uint8_t buffer[5]; + buffer[0] = HCI_EVENT_HIDS_META; + buffer[1] = 3; + buffer[2] = HIDS_SUBEVENT_CAN_SEND_NOW; + little_endian_store_16(buffer, 3, (uint16_t) con_handle); + (*packet_handler)(HCI_EVENT_PACKET, 0, buffer, sizeof(buffer)); +} + +// ATT Client Read Callback for Dynamic Data +// - if buffer == NULL, don't copy data, just return size of value +// - if buffer != NULL, copy data and return number bytes copied +static uint16_t att_read_callback(hci_con_handle_t con_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size) { + hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle); + if (!instance) { + log_error("no instance for handle 0x%02x", con_handle); + return HIDS_DEVICE_ERROR_CODE_INAPPROPRIATE_CONNECTION_PARAMETERS; + } + + if (att_handle == instance->hid_protocol_mode_value_handle) { + log_info("Read protocol mode"); + return att_read_callback_handle_byte(instance->hid_protocol_mode, offset, buffer, buffer_size); + } + + if (att_handle == instance->hid_report_map_handle) { + log_info("Read report map"); + return att_read_callback_handle_blob(instance->hid_descriptor, instance->hid_descriptor_size, offset, buffer, buffer_size); + } + + if (att_handle == instance->hid_boot_mouse_input_client_configuration_handle) { + return att_read_callback_handle_little_endian_16(instance->hid_boot_mouse_input_client_configuration_value, offset, buffer, buffer_size); + } + + if (att_handle == instance->hid_boot_keyboard_input_client_configuration_handle) { + return att_read_callback_handle_little_endian_16(instance->hid_boot_keyboard_input_client_configuration_value, offset, buffer, buffer_size); + } + + if (att_handle == instance->hid_control_point_value_handle) { + if (buffer && (buffer_size >= 1u)) { + buffer[0] = instance->hid_control_point_suspend; + } + return 1; + } + + hids_device_report_t * report = hids_device_get_report_for_client_configuration_handle(instance, att_handle); + if (report != NULL) { + return att_read_callback_handle_little_endian_16(report->client_configuration_value, offset, buffer, buffer_size); + } + return 0; +} + +static int att_write_callback(hci_con_handle_t con_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) { + UNUSED(transaction_mode); + UNUSED(buffer_size); + UNUSED(offset); + + hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle); + if (!instance) { + log_error("no instance for handle 0x%02x", con_handle); + return HIDS_DEVICE_ERROR_CODE_INAPPROPRIATE_CONNECTION_PARAMETERS; + } + + if (att_handle == instance->hid_boot_mouse_input_client_configuration_handle) { + uint16_t new_value = little_endian_read_16(buffer, 0); + instance->hid_boot_mouse_input_client_configuration_value = new_value; + hids_device_emit_event_with_uint8(HIDS_SUBEVENT_BOOT_MOUSE_INPUT_REPORT_ENABLE, con_handle, (uint8_t) new_value); + } + if (att_handle == instance->hid_boot_keyboard_input_client_configuration_handle) { + uint16_t new_value = little_endian_read_16(buffer, 0); + instance->hid_boot_keyboard_input_client_configuration_value = new_value; + hids_device_emit_event_with_uint8(HIDS_SUBEVENT_BOOT_KEYBOARD_INPUT_REPORT_ENABLE, con_handle, (uint8_t) new_value); + } + + if (att_handle == instance->hid_protocol_mode_value_handle) { + instance->hid_protocol_mode = buffer[0]; + log_info("Set protocol mode: %u", instance->hid_protocol_mode); + hids_device_emit_event_with_uint8(HIDS_SUBEVENT_PROTOCOL_MODE, con_handle, instance->hid_protocol_mode); + } + + if (att_handle == instance->hid_control_point_value_handle) { + if (buffer_size < 1u) { + return ATT_ERROR_INVALID_OFFSET; + } + instance->hid_control_point_suspend = buffer[0]; + instance->con_handle = con_handle; + log_info("Set suspend tp: %u", instance->hid_control_point_suspend); + if (instance->hid_control_point_suspend == 0u) { + hids_device_emit_event(HIDS_SUBEVENT_SUSPEND, con_handle); + } else if (instance->hid_control_point_suspend == 1u) { + hids_device_emit_event(HIDS_SUBEVENT_EXIT_SUSPEND, con_handle); + } + } + + hids_device_report_t * report = hids_device_get_report_for_client_configuration_handle(instance, att_handle); + if (report != NULL) { + uint16_t new_value = little_endian_read_16(buffer, 0); + report->client_configuration_value = new_value; + log_info("Enable Report (type %u) notifications: %x", (uint8_t) report->type, new_value); + + switch (report->type) { + case HID_REPORT_TYPE_INPUT: + hids_device_emit_event_with_uint8_uint8_t(HIDS_SUBEVENT_INPUT_REPORT_ENABLE, con_handle, report->id, (uint8_t) new_value); + break; + case HID_REPORT_TYPE_OUTPUT: + hids_device_emit_event_with_uint8_uint8_t(HIDS_SUBEVENT_OUTPUT_REPORT_ENABLE, con_handle, report->id, (uint8_t) new_value); + break; + case HID_REPORT_TYPE_FEATURE: + hids_device_emit_event_with_uint8_uint8_t(HIDS_SUBEVENT_FEATURE_REPORT_ENABLE, con_handle, report->id, (uint8_t) new_value); + break; + default: + btstack_unreachable(); + break; + } + } + return 0; +} + +void hids_device_init_with_storage(uint8_t hid_country_code, const uint8_t * hid_descriptor, uint16_t hid_descriptor_size, + uint16_t num_reports, hids_device_report_t * report_storage) { + + hids_device_t * instance = hids_device_create_instance(); + + btstack_assert(num_reports > 0); + btstack_assert(report_storage != NULL); + + instance->hid_country_code = hid_country_code; + instance->hid_descriptor = hid_descriptor; + instance->hid_descriptor_size = hid_descriptor_size; + + // default + instance->hid_protocol_mode = 1; + + // get service handle range + uint16_t start_handle = 0; + uint16_t end_handle = 0xffff; + int service_found = gatt_server_get_handle_range_for_service_with_uuid16(ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE, &start_handle, &end_handle); + btstack_assert(service_found != 0); + UNUSED(service_found); + + // get report map handle + instance->hid_report_map_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP); + + // get report map handle + instance->hid_protocol_mode_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE); + + // get value and client configuration handles for boot mouse input, boot keyboard input and report input + instance->hid_boot_mouse_input_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT); + instance->hid_boot_mouse_input_client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT); + + instance->hid_boot_keyboard_input_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT); + instance->hid_boot_keyboard_input_client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT); + + instance->hid_control_point_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT); + + log_info("hid_report_map_handle 0x%02x", instance->hid_report_map_handle); + log_info("hid_protocol_mode_value_handle 0x%02x", instance->hid_protocol_mode_value_handle); + log_info("hid_boot_mouse_input_value_handle 0x%02x", instance->hid_boot_mouse_input_value_handle); + log_info("hid_boot_mouse_input_client_configuration_handle 0x%02x", instance->hid_boot_mouse_input_client_configuration_handle); + log_info("hid_boot_keyboard_input_value_handle 0x%02x", instance->hid_boot_keyboard_input_value_handle); + log_info("hid_boot_keyboard_input_client_configuration_handle 0x%02x", instance->hid_boot_keyboard_input_client_configuration_handle); + log_info("hid_control_point_value_handle 0x%02x", instance->hid_control_point_value_handle); + + instance->hid_reports = report_storage; + + uint16_t hid_reports_num = num_reports; + uint16_t assigned_reports_num = 0; + uint16_t start_chr_handle = start_handle; + + while ((start_chr_handle < end_handle) && (assigned_reports_num < hid_reports_num)) { + // mandatory + uint16_t chr_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_chr_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_REPORT); + if (chr_value_handle == 0) { + break; + } + + // optional + uint16_t chr_client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_chr_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_REPORT); + + // mandatory + uint16_t report_reference_handle = gatt_server_get_descriptor_handle_for_characteristic_with_uuid16(start_chr_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, ORG_BLUETOOTH_DESCRIPTOR_REPORT_REFERENCE); + if (report_reference_handle == 0) { + break; + } + + // get report id and type from report reference + uint16_t report_reference_value_len; + const uint8_t * report_reference_value = gatt_server_get_const_value_for_handle(report_reference_handle, &report_reference_value_len); + if (report_reference_value == NULL) { + break; + } + if (report_reference_value_len != 2) { + break; + } + uint8_t report_id = report_reference_value[0]; + hid_report_type_t report_type = (hid_report_type_t) report_reference_value[1]; + + // store report info + hids_device_report_t * report = &report_storage[assigned_reports_num]; + report->value_handle = chr_value_handle; + report->client_configuration_handle = chr_client_configuration_handle; + report->client_configuration_value = 0; + report->id = report_id; + report->type = report_type; + + switch (report->type) { + case HID_REPORT_TYPE_INPUT: + instance->hid_input_reports_num++; + break; + case HID_REPORT_TYPE_OUTPUT: + instance->hid_output_reports_num++; + break; + case HID_REPORT_TYPE_FEATURE: + instance->hid_feature_reports_num++; + break; + default: + btstack_unreachable(); + return; + } + log_info("hid_report_value_handle 0x%02x, id %u, type %u", report->value_handle, report->id, (uint8_t)report->type); + if (report->client_configuration_handle != 0) { + log_info("hid_report_client_configuration_handle 0x%02x", report->client_configuration_handle); + } + + assigned_reports_num++; + start_chr_handle = report_reference_handle + 1; + } + + // register service with ATT Server + hid_service.start_handle = start_handle; + hid_service.end_handle = end_handle; + hid_service.read_callback = &att_read_callback; + hid_service.write_callback = &att_write_callback; + att_server_register_service_handler(&hid_service); +} + +/** + @brief Set up HIDS Device +*/ +void hids_device_init(uint8_t country_code, const uint8_t * descriptor, uint16_t descriptor_size) { + uint16_t hid_reports_num = sizeof(hid_reports_generic_storage) / sizeof(hids_device_report_t); + hids_device_init_with_storage(country_code, descriptor, descriptor_size, hid_reports_num, hid_reports_generic_storage); +} + +/** + @brief Register callback for the HIDS Device client. + @param callback +*/ +void hids_device_register_packet_handler(btstack_packet_handler_t callback) { + packet_handler = callback; +} + +/** + @brief Request can send now event to send HID Report + Generates an HIDS_SUBEVENT_CAN_SEND_NOW subevent + @param hid_cid +*/ +void hids_device_request_can_send_now_event(hci_con_handle_t con_handle) { + hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle); + if (!instance) { + log_error("no instance for handle 0x%02x", con_handle); + return; + } + + instance->battery_callback.callback = &hids_device_can_send_now; + instance->battery_callback.context = (void*)(uintptr_t) con_handle; + att_server_register_can_send_now_callback(&instance->battery_callback, con_handle); +} + +uint8_t hids_device_send_input_report_for_id(hci_con_handle_t con_handle, uint16_t report_id, const uint8_t * report, uint16_t report_len) { + hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle); + if (!instance) { + log_error("no instance for handle 0x%02x", con_handle); + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + + hids_device_report_t * report_storage = hids_device_get_report_for_id_and_type(instance, report_id, + HID_REPORT_TYPE_INPUT); + if (report_storage == NULL) { + return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE; + } + + return att_server_notify(con_handle, report_storage->value_handle, report, report_len); +} + +uint8_t hids_device_send_input_report(hci_con_handle_t con_handle, const uint8_t * report, uint16_t report_len) { + hids_device_t * device = hids_device_get_instance_for_con_handle(con_handle); + if (!device) { + log_error("no instance for handle 0x%02x", con_handle); + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + + uint8_t pos; + uint8_t total_reports = device->hid_input_reports_num + device->hid_output_reports_num + device->hid_feature_reports_num; + for (pos = 0 ; pos < total_reports ; pos++) { + hids_device_report_t * report_storage = &device->hid_reports[pos]; + if (report_storage->type == HID_REPORT_TYPE_INPUT) { + return att_server_notify(con_handle, report_storage->value_handle, report, report_len); + } + } + return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE; +} + +/** + @brief Send HID Boot Mouse Input Report +*/ +uint8_t hids_device_send_boot_mouse_input_report(hci_con_handle_t con_handle, const uint8_t * report, uint16_t report_len) { + hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle); + if (!instance) { + log_error("no instance for handle 0x%02x", con_handle); + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + return att_server_notify(con_handle, instance->hid_boot_mouse_input_value_handle, report, report_len); +} + +/** + @brief Send HID Boot Mouse Input Report +*/ +uint8_t hids_device_send_boot_keyboard_input_report(hci_con_handle_t con_handle, const uint8_t * report, uint16_t report_len) { + hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle); + if (!instance) { + log_error("no instance for handle 0x%02x", con_handle); + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + return att_server_notify(con_handle, instance->hid_boot_keyboard_input_value_handle, report, report_len); +} + +#endif diff --git a/cores/rp2040/sdkoverride/hids_device.h b/cores/rp2040/sdkoverride/hids_device.h new file mode 100644 index 000000000..f98b5c3bb --- /dev/null +++ b/cores/rp2040/sdkoverride/hids_device.h @@ -0,0 +1,143 @@ +/* + Copyright (C) 2014 BlueKitchen GmbH + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the names of + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + 4. Any redistribution, use, or modification is done solely for + personal benefit and not for any commercial purpose or for + monetary gain. + + THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Please inquire about commercial licensing options at + contact@bluekitchen-gmbh.com + +*/ + +/** + @title HID Service Server + +*/ + +#ifndef HIDS_DEVICE_H +#define HIDS_DEVICE_H + +#if defined ENABLE_CLASSIC + +#include +#include +#include "btstack_defines.h" +#include "btstack_hid.h" +#include "bluetooth.h" + +#if defined __cplusplus +extern "C" { +#endif + + /* API_START */ + + typedef struct { + uint16_t value_handle; + uint16_t client_configuration_handle; + uint16_t client_configuration_value; + + hid_report_type_t type; + uint16_t id; + } hids_device_report_t; + + /** + @text Implementation of the GATT HIDS Device + To use with your application, add '#import ' to your .gatt file + */ + + /** + @brief Set up HIDS Device with single INPUT, OUTPUT and FEATURE report + */ + void hids_device_init(uint8_t hid_country_code, const uint8_t * hid_descriptor, uint16_t hid_descriptor_size); + + /** + @brief Set up HIDS Device for multiple instances of INPUT, OUTPUT and FEATURE reports + */ + void hids_device_init_with_storage(uint8_t hid_country_code, const uint8_t * hid_descriptor, uint16_t hid_descriptor_size, + uint16_t num_reports, hids_device_report_t * report_storage); + + /** + @brief Register callback for the HIDS Device client. + @param callback + */ + void hids_device_register_packet_handler(btstack_packet_handler_t callback); + + /** + @brief Request can send now event to send HID Report + Generates an HIDS_SUBEVENT_CAN_SEND_NOW subevent + @param hid_cid + */ + void hids_device_request_can_send_now_event(hci_con_handle_t con_handle); + + /** + @brief Send HID Input Report for Report ID + @param con_handle + @param report_id + @param report + @param report_len + @returns status + */ + uint8_t hids_device_send_input_report_for_id(hci_con_handle_t con_handle, uint16_t report_id, const uint8_t * report, uint16_t report_len); + + /** + @brief Send HID Input Report for first Input Report + @param con_handle + @param report + @param report_len + @returns status + */ + uint8_t hids_device_send_input_report(hci_con_handle_t con_handle, const uint8_t * report, uint16_t report_len); + + /** + @brief Send HID Boot Mouse Input Report + @param con_handle + @param report + @param report_len + @returns status + */ + uint8_t hids_device_send_boot_mouse_input_report(hci_con_handle_t con_handle, const uint8_t * report, uint16_t report_len); + + /** + @brief Send HID Boot Mouse Input Report + @param con_handle + @param report + @param report_len + @returns status + */ + uint8_t hids_device_send_boot_keyboard_input_report(hci_con_handle_t con_handle, const uint8_t * report, uint16_t report_len); + + /* API_END */ + +#if defined __cplusplus +} +#endif + +#endif + +#endif diff --git a/libraries/HID_Bluetooth/src/HID_Bluetooth.cpp b/libraries/HID_Bluetooth/src/HID_Bluetooth.cpp new file mode 100644 index 000000000..a40f42b5d --- /dev/null +++ b/libraries/HID_Bluetooth/src/HID_Bluetooth.cpp @@ -0,0 +1,224 @@ +#include "HID_Bluetooth.h" + +//setup the report map. +//more generic function to be used with BLE & BT Classis +void __SetupHIDreportmap(void (*WeakMouse)(), void (*WeakKeyboard)(), void (*WeakJoystick)(), bool absMouse, uint16_t *report_size, uint8_t **reportmap) { + //allocate memory for the HID report descriptors. We don't use them, but need the size here. + uint8_t desc_hid_report_mouse[] = { TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(1)) }; + uint8_t desc_hid_report_absmouse[] = { TUD_HID_REPORT_DESC_ABSMOUSE(HID_REPORT_ID(1)) }; + uint8_t desc_hid_report_joystick[] = { TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(1)) }; + uint8_t desc_hid_report_keyboard[] = { TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(1)), TUD_HID_REPORT_DESC_CONSUMER(HID_REPORT_ID(2)) }; + int size = 0; + + //enable to debug the individual report maps +#if 0 + Serial.printf("Report mouse: %d bytes\n", sizeof(desc_hid_report_mouse)); + for (uint16_t i = 0; i < sizeof(desc_hid_report_mouse); i++) { + Serial.print(desc_hid_report_mouse[i], HEX); + Serial.print(" "); + if (i % 4 == 3) { + Serial.print("\n"); + } + } + Serial.printf("Report absmouse: %d bytes\n", sizeof(desc_hid_report_absmouse)); + for (uint16_t i = 0; i < sizeof(desc_hid_report_absmouse); i++) { + Serial.print(desc_hid_report_absmouse[i], HEX); + Serial.print(" "); + if (i % 4 == 3) { + Serial.print("\n"); + } + } + Serial.printf("Report kbd: %d bytes\n", sizeof(desc_hid_report_keyboard)); + for (uint16_t i = 0; i < sizeof(desc_hid_report_keyboard); i++) { + Serial.print(desc_hid_report_keyboard[i], HEX); + Serial.print(" "); + if (i % 4 == 3) { + Serial.print("\n"); + } + } + Serial.printf("Report joystick: %d bytes\n", sizeof(desc_hid_report_joystick)); + for (uint16_t i = 0; i < sizeof(desc_hid_report_joystick); i++) { + Serial.print(desc_hid_report_joystick[i], HEX); + Serial.print(" "); + if (i % 4 == 3) { + Serial.print("\n"); + } + } +#endif + + //accumulate the size of all used HID report descriptors + if (WeakKeyboard) { + size += sizeof(desc_hid_report_keyboard); + } + if (WeakMouse && absMouse == false) { + size += sizeof(desc_hid_report_mouse); + } else if (WeakMouse && absMouse == true) { + size += sizeof(desc_hid_report_absmouse); + } + if (WeakJoystick) { + size += sizeof(desc_hid_report_joystick); + } + + //no HID used at all + if (size == 0) { + *report_size = 0; + return; + } + + //allocate the "real" HID report descriptor + *reportmap = (uint8_t *)malloc(size); + if (*reportmap) { + *report_size = size; + + //now copy the descriptors + + //1.) keyboard descriptor, if requested + if (WeakKeyboard) { + memcpy(*reportmap, desc_hid_report_keyboard, sizeof(desc_hid_report_keyboard)); + } + + //2.) mouse descriptor, if necessary. Additional offset & new array is necessary if there is a keyboard. + if (WeakMouse && absMouse == false) { + //determine if we need an offset (USB keyboard is installed) + if (WeakKeyboard) { + uint8_t desc_local[] = { TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(3)) }; + memcpy(*reportmap + sizeof(desc_hid_report_keyboard), desc_local, sizeof(desc_local)); + } else { + memcpy(*reportmap, desc_hid_report_mouse, sizeof(desc_hid_report_mouse)); + } + } else if (WeakMouse && absMouse == true) { + //determine if we need an offset (USB keyboard is installed) + if (WeakKeyboard) { + uint8_t desc_local[] = { TUD_HID_REPORT_DESC_ABSMOUSE(HID_REPORT_ID(3)) }; + memcpy(*reportmap + sizeof(desc_hid_report_keyboard), desc_local, sizeof(desc_local)); + } else { + memcpy(*reportmap, desc_hid_report_absmouse, sizeof(desc_hid_report_absmouse)); + } + } + + //3.) joystick descriptor. 2 additional checks are necessary for mouse and/or keyboard + if (WeakJoystick) { + uint8_t reportid = 1; + int offset = 0; + if (WeakKeyboard) { + reportid += 2; + offset += sizeof(desc_hid_report_keyboard); + } + if (WeakMouse && absMouse == false) { + reportid++; + offset += sizeof(desc_hid_report_mouse); + } else if (WeakMouse && absMouse == true) { + reportid++; + offset += sizeof(desc_hid_report_absmouse); + } + uint8_t desc_local[] = { TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(reportid)) }; + memcpy(*reportmap + offset, desc_local, sizeof(desc_local)); + } + + //enable for debugging the final report map +#if 0 + Serial.begin(115200); + Serial.printf("Final map: %d bytes\n", size); + for (uint16_t i = 0; i < size; i++) { + Serial.print(*reportmap[i], HEX); + Serial.print(" "); + if (i % 4 == 3) { + Serial.print("\n"); + } + } +#endif + } else { + Serial.println("No report map pointer provided!"); + } +} + + +//get Class of Device number for starting HID, type depends on activated libraries +uint16_t __BTGetCOD() { + //mouse only + if (__BTInstallMouse && !__BTInstallKeyboard && !__BTInstallJoystick) { + return 0x2580; + } + //keyboard only + if (__BTInstallKeyboard && !__BTInstallMouse && !__BTInstallJoystick) { + return 0x2540; + } + //joystick only + if (__BTInstallJoystick && !__BTInstallKeyboard && !__BTInstallMouse) { + return 0x2508; + } + //any other combination will return "combo device" + return 0x25C0; +} + +//get Class of Device number for starting HID, type depends on activated libraries +uint16_t __BLEGetAppearance() { + //mouse only + if (__BLEInstallMouse && !__BLEInstallKeyboard && !__BLEInstallJoystick) { + return 0x03C2; + } + //keyboard only + if (__BLEInstallKeyboard && !__BLEInstallMouse && !__BLEInstallJoystick) { + return 0x03C1; + } + //joystick only + if (__BLEInstallJoystick && !__BLEInstallMouse && !__BLEInstallKeyboard) { + return 0x03C4; + } + //any other combination will return "generic HID" + return 0x03C0; +} + +//keyboard report id is always 1 (compatibility with iOS) +int __BTGetKeyboardReportID() { + return 1; +} + +// +int __BTGetMouseReportID() { + return __BTInstallKeyboard ? 3 : 1; +} + +int __BTGetJoystickReportID() { + int i = 1; + if (__BTInstallKeyboard) { + i += 2; + } + if (__BTInstallMouse) { + i++; + } + return i; +} + +int __BLEGetKeyboardReportID() { + return 1; +} + +int __BLEGetMouseReportID() { + return __BLEInstallKeyboard ? 3 : 1; +} + +int __BLEGetFeatureReportID() { + int feature = 1; + if (__BLEInstallKeyboard) { + feature += 2; + } + if (__BLEInstallMouse) { + feature ++; + } + if (__BLEInstallJoystick) { + feature ++; + } + return feature; +} + +int __BLEGetJoystickReportID() { + int i = 1; + if (__BLEInstallKeyboard) { + i += 2; + } + if (__BLEInstallMouse) { + i++; + } + return i; +} diff --git a/libraries/HID_Bluetooth/src/HID_Bluetooth.h b/libraries/HID_Bluetooth/src/HID_Bluetooth.h index 58e0434dc..ddb90bf08 100644 --- a/libraries/HID_Bluetooth/src/HID_Bluetooth.h +++ b/libraries/HID_Bluetooth/src/HID_Bluetooth.h @@ -5,3 +5,39 @@ #ifdef ENABLE_BLE #include "PicoBluetoothBLEHID.h" #endif + +#pragma once + +//necessary to implement the absolute mouse descriptor define, +//remove if merged into TinyUSB +#include + + +//override weak declarations to include HID report to report map. +//done in each library (KeyboardBT,...) +extern void __BTInstallKeyboard() __attribute__((weak)); +extern void __BTInstallJoystick() __attribute__((weak)); +extern void __BTInstallMouse() __attribute__((weak)); + +//override weak declarations to include HID report to report map. +//done in each library (KeyboardBLE,...) +extern void __BLEInstallKeyboard() __attribute__((weak)); +extern void __BLEInstallJoystick() __attribute__((weak)); +extern void __BLEInstallMouse() __attribute__((weak)); + +//setup the report map. +//more generic function to be used with BLE & BT Classis +void __SetupHIDreportmap(void (*WeakMouse)(), void (*WeakKeyboard)(), void (*WeakJoystick)(), bool absMouse, uint16_t *report_size, uint8_t **reportmap); + +//get Class of Device number for starting HID, type depends on activated libraries +uint16_t __BTGetCOD(); +//get Class of Device number for starting HID, type depends on activated libraries +uint16_t __BLEGetAppearance(); +int __BTGetKeyboardReportID(); +int __BTGetMouseReportID(); +int __BTGetJoystickReportID(); + +int __BLEGetKeyboardReportID(); +int __BLEGetMouseReportID(); +int __BLEGetJoystickReportID(); +int __BLEGetFeatureReportID(); diff --git a/libraries/HID_Bluetooth/src/PicoBluetoothBLEHID.h b/libraries/HID_Bluetooth/src/PicoBluetoothBLEHID.h index d755c3e33..0bfebc77c 100644 --- a/libraries/HID_Bluetooth/src/PicoBluetoothBLEHID.h +++ b/libraries/HID_Bluetooth/src/PicoBluetoothBLEHID.h @@ -21,17 +21,22 @@ #pragma once +#include #include <_needsbt.h> #include #include #include #include +#include "HID_Bluetooth.h" + // The BTStack has redefinitions of this USB enum (same values, just redefined), so hide it to allow easy compilation #define HID_REPORT_TYPE_INPUT HID_REPORT_TYPE_INPUT_BT #define HID_REPORT_TYPE_OUTPUT HID_REPORT_TYPE_OUTPUT_BT #define HID_REPORT_TYPE_FEATURE HID_REPORT_TYPE_FEATURE_BT #define hid_report_type_t hid_report_type_t_bt +#include +#include #include #undef hid_report_type_t #undef HID_REPORT_TYPE_FEATURE @@ -43,7 +48,9 @@ #include #include #include -#include +//#include + + class PicoBluetoothBLEHID_; extern PicoBluetoothBLEHID_ PicoBluetoothBLEHID; @@ -100,8 +107,20 @@ class PicoBluetoothBLEHID_ { // Setup device information service device_information_service_server_init(); - // Setup HID Device service - hids_device_init(0, hidDescriptor, hidDescriptorSize); + // Setup HID Device service, depending on activated reports + uint8_t numreports = 1; //start with 1 (feature report) + if (__BLEInstallKeyboard) { + numreports += 2; //add keycodes + consumer keys + } + if (__BLEInstallMouse) { + numreports += 1; + } + if (__BLEInstallJoystick) { + numreports += 1; + } + //allocate memory for hid reports + _reportStorage = (hids_device_report_t *) malloc(sizeof(hids_device_report_t) * numreports); + hids_device_init_with_storage(0, hidDescriptor, hidDescriptorSize, numreports, _reportStorage); // Setup advertisements uint16_t adv_int_min = 0x0030; @@ -134,6 +153,9 @@ class PicoBluetoothBLEHID_ { } void packetHandler(uint8_t type, uint16_t channel, uint8_t *packet, uint16_t size) { + uint8_t result; + uint8_t reportID; + if (type != HCI_EVENT_PACKET) { return; } @@ -174,10 +196,32 @@ class PicoBluetoothBLEHID_ { case HIDS_SUBEVENT_CAN_SEND_NOW: switch (_protocol_mode) { case 0: - hids_device_send_boot_keyboard_input_report(_con_handle, (const uint8_t *)_sendReport, _sendReportLen); + //We cannot distinguish between kbd & mouse in boot mode. + //If both are activated, we cannot send + if (__BLEInstallKeyboard && !__BLEInstallMouse) { + hids_device_send_boot_keyboard_input_report(_con_handle, &(((const uint8_t *)_sendReport)[1]), _sendReportLen); + } + if (__BLEInstallMouse && !__BLEInstallKeyboard) { + hids_device_send_boot_mouse_input_report(_con_handle, &(((const uint8_t *)_sendReport)[1]), _sendReportLen); + } + if (__BLEInstallMouse && __BLEInstallJoystick) { + printf("Error: BLE HID in boot mode, but mouse & keyboard are active\n"); + } break; case 1: - hids_device_send_input_report(_con_handle, (const uint8_t *)_sendReport, _sendReportLen); + reportID = ((const uint8_t *)_sendReport)[0]; + result = hids_device_send_input_report_for_id(_con_handle, (uint16_t)reportID, &(((const uint8_t *)_sendReport)[1]), _sendReportLen - 1); + if (result) { + Serial.printf("Error sending %d - report ID: %d\n", result, reportID); + } + //else Serial.printf("Sent report for ID: %d\n",reportID); +#if 0 + Serial.printf("Sending report for ID %d, len: %d:\n", reportID, _sendReportLen); + for (uint8_t i = 0; i < _sendReportLen; i++) { + Serial.printf("0x%02X - ", ((const uint8_t *)_sendReport)[i]); + } + Serial.println(""); +#endif break; default: break; @@ -208,6 +252,8 @@ class PicoBluetoothBLEHID_ { } bool send(void *rpt, int len) { + //wait for another report to be sent + while (_needToSend); _needToSend = true; _sendReport = rpt; _sendReportLen = len; @@ -236,6 +282,9 @@ class PicoBluetoothBLEHID_ { async_context_release_lock(cyw43_arch_async_context()); } + uint8_t *_attdb = nullptr; + int _attdbLen = 0; + private: bool _running = false; @@ -280,10 +329,17 @@ class PicoBluetoothBLEHID_ { } uint8_t *_advData = nullptr; uint8_t _advDataLen = 0; + hids_device_report_t *_reportStorage = nullptr; void _buildAttdb(const char *hidName) { free(_attdb); - _attdbLen = sizeof(_attdb_head) + 8 + strlen(hidName) + sizeof(_attdb_tail); + //add up all different parts of ATT DB + _attdbLen = sizeof(_attdb_head) + 8 + strlen(hidName) + sizeof(_attdb_tail) + sizeof(_attdb_batt_hidhead) + sizeof(_attdb_char); + //reports + _attdbLen += sizeof(_attdb_kbd_report) + sizeof(_attdb_mouse_report) + sizeof(_attdb_joystick_report); + //additional boot characteristics + _attdbLen += sizeof(_attdb_kbd_boot) + sizeof(_attdb_mouse_boot); + _attdb = (uint8_t *) malloc(_attdbLen); memcpy(_attdb, _attdb_head, sizeof(_attdb_head)); // 0x0003 VALUE CHARACTERISTIC-GAP_DEVICE_NAME - READ -'HID Mouse' @@ -300,10 +356,35 @@ class PicoBluetoothBLEHID_ { _attdb[i++] = 0x2a; memcpy(_attdb + i, hidName, strlen(hidName)); i += strlen(hidName); + + memcpy(_attdb + i, _attdb_batt_hidhead, sizeof(_attdb_batt_hidhead)); + i += sizeof(_attdb_batt_hidhead); + + //1.) KBD report mode + memcpy(_attdb + i, _attdb_kbd_report, sizeof(_attdb_kbd_report)); + i += sizeof(_attdb_kbd_report); + + //2.) mouse report mode + memcpy(_attdb + i, _attdb_mouse_report, sizeof(_attdb_mouse_report)); + i += sizeof(_attdb_mouse_report); + + //3.) joystick report mode + memcpy(_attdb + i, _attdb_joystick_report, sizeof(_attdb_joystick_report)); + i += sizeof(_attdb_joystick_report); + //4.) report characteristics + memcpy(_attdb + i, _attdb_char, sizeof(_attdb_char)); + i += sizeof(_attdb_char); + + //5.) KBD boot mode (always included) + memcpy(_attdb + i, _attdb_kbd_boot, sizeof(_attdb_kbd_boot)); + i += sizeof(_attdb_kbd_boot); + + //6.) mouse boot mode (always included) + memcpy(_attdb + i, _attdb_mouse_boot, sizeof(_attdb_mouse_boot)); + i += sizeof(_attdb_mouse_boot); + //7.) tail (report) memcpy(_attdb + i, _attdb_tail, sizeof(_attdb_tail)); } - uint8_t *_attdb = nullptr; - int _attdbLen = 0; static constexpr const uint8_t _attdb_head[] = { // ATT DB Version @@ -315,7 +396,7 @@ class PicoBluetoothBLEHID_ { 0x0d, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x28, 0x02, 0x03, 0x00, 0x00, 0x2a, }; - static constexpr const uint8_t _attdb_tail[] = { + static constexpr const uint8_t _attdb_batt_hidhead[] = { // #import -- BEGIN // Specification Type org.bluetooth.service.battery_service // https://www.bluetooth.com/api/gatt/xmlfile?xmlFileName=org.bluetooth.service.battery_service.xml @@ -333,6 +414,7 @@ class PicoBluetoothBLEHID_ { // #import -- END // add Device ID Service + // #import -- BEGIN // Specification Type org.bluetooth.service.device_information // https://www.bluetooth.com/api/gatt/xmlfile?xmlFileName=org.bluetooth.service.device_information.xml @@ -386,7 +468,6 @@ class PicoBluetoothBLEHID_ { 0x08, 0x00, 0x02, 0x01, 0x1a, 0x00, 0x50, 0x2a, // #import -- END - // #import -- BEGIN // Specification Type org.bluetooth.service.human_interface_device // https://www.bluetooth.com/api/gatt/xmlfile?xmlFileName=org.bluetooth.service.human_interface_device.xml // Human Interface Device 1812 @@ -397,6 +478,9 @@ class PicoBluetoothBLEHID_ { // 0x001d VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE - DYNAMIC | READ | WRITE_WITHOUT_RESPONSE // READ_ANYBODY, WRITE_ANYBODY 0x08, 0x00, 0x06, 0x01, 0x1d, 0x00, 0x4e, 0x2a, + }; + + static constexpr const uint8_t _attdb_kbd_report[] = { // 0x001e CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16 0x0d, 0x00, 0x02, 0x00, 0x1e, 0x00, 0x03, 0x28, 0x1a, 0x1f, 0x00, 0x4d, 0x2a, // 0x001f VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16 @@ -405,9 +489,10 @@ class PicoBluetoothBLEHID_ { // 0x0020 CLIENT_CHARACTERISTIC_CONFIGURATION // READ_ANYBODY, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16 0x0a, 0x00, 0x0f, 0xf1, 0x20, 0x00, 0x02, 0x29, 0x00, 0x00, - // fixed report id = 1, type = Input (1) + // fixed report id = 1, type = Input (1); keycodes // 0x0021 REPORT_REFERENCE-READ-1-1 0x0a, 0x00, 0x02, 0x00, 0x21, 0x00, 0x08, 0x29, 0x1, 0x1, + // 0x0022 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16 0x0d, 0x00, 0x02, 0x00, 0x22, 0x00, 0x03, 0x28, 0x1a, 0x23, 0x00, 0x4d, 0x2a, // 0x0023 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16 @@ -416,9 +501,12 @@ class PicoBluetoothBLEHID_ { // 0x0024 CLIENT_CHARACTERISTIC_CONFIGURATION // READ_ANYBODY, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16 0x0a, 0x00, 0x0f, 0xf1, 0x24, 0x00, 0x02, 0x29, 0x00, 0x00, - // fixed report id = 2, type = Output (2) - // 0x0025 REPORT_REFERENCE-READ-2-2 - 0x0a, 0x00, 0x02, 0x00, 0x25, 0x00, 0x08, 0x29, 0x2, 0x2, + // fixed report id = 2, type = Input (1) consumer + // 0x0025 REPORT_REFERENCE-READ-2-1 + 0x0a, 0x00, 0x02, 0x00, 0x25, 0x00, 0x08, 0x29, 0x2, 0x1, + }; + + static constexpr const uint8_t _attdb_mouse_report[] = { // 0x0026 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16 0x0d, 0x00, 0x02, 0x00, 0x26, 0x00, 0x03, 0x28, 0x1a, 0x27, 0x00, 0x4d, 0x2a, // 0x0027 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16 @@ -427,59 +515,88 @@ class PicoBluetoothBLEHID_ { // 0x0028 CLIENT_CHARACTERISTIC_CONFIGURATION // READ_ANYBODY, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16 0x0a, 0x00, 0x0f, 0xf1, 0x28, 0x00, 0x02, 0x29, 0x00, 0x00, - // fixed report id = 3, type = Feature (3) - // 0x0029 REPORT_REFERENCE-READ-3-3 - 0x0a, 0x00, 0x02, 0x00, 0x29, 0x00, 0x08, 0x29, 0x3, 0x3, - // 0x002a CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP - DYNAMIC | READ - 0x0d, 0x00, 0x02, 0x00, 0x2a, 0x00, 0x03, 0x28, 0x02, 0x2b, 0x00, 0x4b, 0x2a, - // 0x002b VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP - DYNAMIC | READ + // fixed report id = 3, type = Input (1) mouse + // 0x0029 REPORT_REFERENCE-READ-3-1 + 0x0a, 0x00, 0x02, 0x00, 0x29, 0x00, 0x08, 0x29, 0x3, 0x1, + }; + static constexpr const uint8_t _attdb_joystick_report[] = { + + // 0x002a CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16 + 0x0d, 0x00, 0x02, 0x00, 0x2a, 0x00, 0x03, 0x28, 0x1a, 0x2b, 0x00, 0x4d, 0x2a, + // 0x002b VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16 + // READ_ENCRYPTED, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16 + 0x08, 0x00, 0x0b, 0xf5, 0x2b, 0x00, 0x4d, 0x2a, + // 0x002c CLIENT_CHARACTERISTIC_CONFIGURATION + // READ_ANYBODY, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16 + 0x0a, 0x00, 0x0f, 0xf1, 0x2c, 0x00, 0x02, 0x29, 0x00, 0x00, + // fixed report id = 4, type = Input (1) gamepad + // 0x002d REPORT_REFERENCE-READ-4-1 + 0x0a, 0x00, 0x02, 0x00, 0x2d, 0x00, 0x08, 0x29, 0x4, 0x1, + }; + + static constexpr const uint8_t _attdb_char[] = { + // 0x002e CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | ENCRYPTION_KEY_SIZE_16 + 0x0d, 0x00, 0x02, 0x00, 0x2e, 0x00, 0x03, 0x28, 0x0a, 0x2f, 0x00, 0x4d, 0x2a, + // 0x002f VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | ENCRYPTION_KEY_SIZE_16 + // READ_ENCRYPTED, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16 + 0x08, 0x00, 0x0b, 0xf5, 0x2f, 0x00, 0x4d, 0x2a, + // fixed report id = 5, type = Feature (3) + // 0x0030 REPORT_REFERENCE-READ-5-3 + 0x0a, 0x00, 0x02, 0x00, 0x30, 0x00, 0x08, 0x29, 0x5, 0x3, + // 0x0031 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP - DYNAMIC | READ + 0x0d, 0x00, 0x02, 0x00, 0x31, 0x00, 0x03, 0x28, 0x02, 0x32, 0x00, 0x4b, 0x2a, + // 0x0032 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP - DYNAMIC | READ // READ_ANYBODY - 0x08, 0x00, 0x02, 0x01, 0x2b, 0x00, 0x4b, 0x2a, - // 0x002c CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY - 0x0d, 0x00, 0x02, 0x00, 0x2c, 0x00, 0x03, 0x28, 0x1a, 0x2d, 0x00, 0x22, 0x2a, - // 0x002d VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY + 0x08, 0x00, 0x02, 0x01, 0x32, 0x00, 0x4b, 0x2a, + }; + + static constexpr const uint8_t _attdb_kbd_boot[] = { + // 0x0033 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY + 0x0d, 0x00, 0x02, 0x00, 0x33, 0x00, 0x03, 0x28, 0x1a, 0x34, 0x00, 0x22, 0x2a, + // 0x0034 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY // READ_ANYBODY, WRITE_ANYBODY - 0x08, 0x00, 0x0a, 0x01, 0x2d, 0x00, 0x22, 0x2a, - // 0x002e CLIENT_CHARACTERISTIC_CONFIGURATION + 0x08, 0x00, 0x0a, 0x01, 0x34, 0x00, 0x22, 0x2a, + // 0x0035 CLIENT_CHARACTERISTIC_CONFIGURATION // READ_ANYBODY, WRITE_ANYBODY - 0x0a, 0x00, 0x0e, 0x01, 0x2e, 0x00, 0x02, 0x29, 0x00, 0x00, - // 0x002f CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT - DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE - 0x0d, 0x00, 0x02, 0x00, 0x2f, 0x00, 0x03, 0x28, 0x0e, 0x30, 0x00, 0x32, 0x2a, - // 0x0030 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT - DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE + 0x0a, 0x00, 0x0e, 0x01, 0x35, 0x00, 0x02, 0x29, 0x00, 0x00, + + + // 0x0036 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT - DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE + 0x0d, 0x00, 0x02, 0x00, 0x36, 0x00, 0x03, 0x28, 0x0e, 0x37, 0x00, 0x32, 0x2a, + // 0x0037 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT - DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE // READ_ANYBODY, WRITE_ANYBODY - 0x08, 0x00, 0x0e, 0x01, 0x30, 0x00, 0x32, 0x2a, - // 0x0031 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY - 0x0d, 0x00, 0x02, 0x00, 0x31, 0x00, 0x03, 0x28, 0x1a, 0x32, 0x00, 0x33, 0x2a, - // 0x0032 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY + 0x08, 0x00, 0x0e, 0x01, 0x37, 0x00, 0x32, 0x2a, + }; + + static constexpr const uint8_t _attdb_mouse_boot[] = { + // 0x0038 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY + 0x0d, 0x00, 0x02, 0x00, 0x38, 0x00, 0x03, 0x28, 0x1a, 0x39, 0x00, 0x33, 0x2a, + // 0x0039 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY // READ_ANYBODY, WRITE_ANYBODY - 0x08, 0x00, 0x0a, 0x01, 0x32, 0x00, 0x33, 0x2a, - // 0x0033 CLIENT_CHARACTERISTIC_CONFIGURATION + 0x08, 0x00, 0x0a, 0x01, 0x39, 0x00, 0x33, 0x2a, + // 0x003a CLIENT_CHARACTERISTIC_CONFIGURATION // READ_ANYBODY, WRITE_ANYBODY - 0x0a, 0x00, 0x0e, 0x01, 0x33, 0x00, 0x02, 0x29, 0x00, 0x00, + 0x0a, 0x00, 0x0e, 0x01, 0x3a, 0x00, 0x02, 0x29, 0x00, 0x00, + }; + + static constexpr const uint8_t _attdb_tail[] = { // bcdHID = 0x101 (v1.0.1), bCountryCode 0, remote wakeable = 0 | normally connectable 2 - // 0x0034 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION - READ - 0x0d, 0x00, 0x02, 0x00, 0x34, 0x00, 0x03, 0x28, 0x02, 0x35, 0x00, 0x4a, 0x2a, - // 0x0035 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION - READ -'01 01 00 02' + // 0x003b CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION - READ + 0x0d, 0x00, 0x02, 0x00, 0x3b, 0x00, 0x03, 0x28, 0x02, 0x3c, 0x00, 0x4a, 0x2a, + // 0x003c VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION - READ -'01 01 00 02' // READ_ANYBODY - 0x0c, 0x00, 0x02, 0x00, 0x35, 0x00, 0x4a, 0x2a, 0x01, 0x01, 0x00, 0x02, - // 0x0036 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT - DYNAMIC | WRITE_WITHOUT_RESPONSE - 0x0d, 0x00, 0x02, 0x00, 0x36, 0x00, 0x03, 0x28, 0x04, 0x37, 0x00, 0x4c, 0x2a, - // 0x0037 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT - DYNAMIC | WRITE_WITHOUT_RESPONSE + 0x0c, 0x00, 0x02, 0x00, 0x3c, 0x00, 0x4a, 0x2a, 0x01, 0x01, 0x00, 0x02, + // 0x003d CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT - DYNAMIC | WRITE_WITHOUT_RESPONSE + 0x0d, 0x00, 0x02, 0x00, 0x3d, 0x00, 0x03, 0x28, 0x04, 0x3e, 0x00, 0x4c, 0x2a, + // 0x003e VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT - DYNAMIC | WRITE_WITHOUT_RESPONSE // WRITE_ANYBODY - 0x08, 0x00, 0x04, 0x01, 0x37, 0x00, 0x4c, 0x2a, - // #import -- END - // 0x0038 PRIMARY_SERVICE-GATT_SERVICE - 0x0a, 0x00, 0x02, 0x00, 0x38, 0x00, 0x00, 0x28, 0x01, 0x18, - // 0x0039 CHARACTERISTIC-GATT_DATABASE_HASH - READ - 0x0d, 0x00, 0x02, 0x00, 0x39, 0x00, 0x03, 0x28, 0x02, 0x3a, 0x00, 0x2a, 0x2b, - // 0x003a VALUE CHARACTERISTIC-GATT_DATABASE_HASH - READ -'' - // READ_ANYBODY - 0x18, 0x00, 0x02, 0x00, 0x3a, 0x00, 0x2a, 0x2b, 0xc2, 0x10, 0xf5, 0x75, 0xf3, 0x9f, 0x50, 0xb6, 0x83, 0xc7, 0xfa, 0xac, 0xa6, 0x1b, 0x7d, 0x32, + 0x08, 0x00, 0x04, 0x01, 0x3e, 0x00, 0x4c, 0x2a, // END - 0x00, 0x00 + 0x00, 0x00, }; volatile bool _needToSend = false; void *_sendReport; + uint8_t _sendReportID; int _sendReportLen; }; diff --git a/libraries/HID_Bluetooth/src/PicoBluetoothHID.h b/libraries/HID_Bluetooth/src/PicoBluetoothHID.h index 227bd793a..b3bbdc49d 100644 --- a/libraries/HID_Bluetooth/src/PicoBluetoothHID.h +++ b/libraries/HID_Bluetooth/src/PicoBluetoothHID.h @@ -21,6 +21,7 @@ #pragma once +#include #include <_needsbt.h> #include #include @@ -32,6 +33,8 @@ #define HID_REPORT_TYPE_OUTPUT HID_REPORT_TYPE_OUTPUT_BT #define HID_REPORT_TYPE_FEATURE HID_REPORT_TYPE_FEATURE_BT #define hid_report_type_t hid_report_type_t_bt +#include +#include #include #undef hid_report_type_t #undef HID_REPORT_TYPE_FEATURE diff --git a/libraries/JoystickBLE/src/JoystickBLE.cpp b/libraries/JoystickBLE/src/JoystickBLE.cpp index 356227fad..32bd12a88 100644 --- a/libraries/JoystickBLE/src/JoystickBLE.cpp +++ b/libraries/JoystickBLE/src/JoystickBLE.cpp @@ -25,18 +25,22 @@ #include "JoystickBLE.h" #include +#include #include //================================================================================ //================================================================================ // Joystick/Gamepad +// Weak function override to add our descriptor to the list +void __BLEInstallJoystick() { /* noop */ } + JoystickBLE_::JoystickBLE_(void) { // Member vars set in base constructor } -#define REPORT_ID 0x01 -static const uint8_t desc_joystick[] = {TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(REPORT_ID))}; +uint8_t *desc_joystickBLE; +uint16_t desc_joystickBLE_length; void JoystickBLE_::begin(const char *localName, const char *hidName) { if (!localName) { @@ -45,7 +49,10 @@ void JoystickBLE_::begin(const char *localName, const char *hidName) { if (!hidName) { hidName = localName; } - PicoBluetoothBLEHID.startHID(localName, hidName, 0x03c4, desc_joystick, sizeof(desc_joystick)); + + __SetupHIDreportmap(__BLEInstallMouse, __BLEInstallKeyboard, __BLEInstallJoystick, false, &desc_joystickBLE_length, &desc_joystickBLE); + + PicoBluetoothBLEHID.startHID(localName, hidName, __BLEGetAppearance(), desc_joystickBLE, desc_joystickBLE_length); } void JoystickBLE_::end() { @@ -57,7 +64,13 @@ void JoystickBLE_::setBattery(int lvl) { } void JoystickBLE_::send_now() { - PicoBluetoothBLEHID.send(&data, sizeof(data)); + //insert report ID; not part of the hid_gamepad_report_t + uint8_t *report = (uint8_t *)malloc(sizeof(hid_gamepad_report_t) +1); + if (report) { + report[0] = __BLEGetJoystickReportID(); + memcpy(&report[1], (uint8_t*)&data, sizeof(data)); + PicoBluetoothBLEHID.send(report, sizeof(data) + 1); + } } JoystickBLE_ JoystickBLE; diff --git a/libraries/JoystickBT/src/JoystickBT.cpp b/libraries/JoystickBT/src/JoystickBT.cpp index b7e3aee67..5065ee891 100644 --- a/libraries/JoystickBT/src/JoystickBT.cpp +++ b/libraries/JoystickBT/src/JoystickBT.cpp @@ -25,18 +25,22 @@ #include "JoystickBT.h" #include +#include #include //================================================================================ //================================================================================ // Joystick/Gamepad +// Weak function override to add our descriptor to the list +void __BTInstallJoystick() { /* noop */ } + JoystickBT_::JoystickBT_() { // HID_Joystick sets up all the member vars } -#define REPORT_ID 0x01 -static const uint8_t desc_joystick[] = {TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(REPORT_ID))}; +uint8_t *desc_joystickBT; +uint16_t desc_joystickBT_length; void JoystickBT_::begin(const char *localName, const char *hidName) { if (!localName) { @@ -45,7 +49,10 @@ void JoystickBT_::begin(const char *localName, const char *hidName) { if (!hidName) { hidName = localName; } - PicoBluetoothHID.startHID(localName, hidName, 0x2508, 33, desc_joystick, sizeof(desc_joystick)); + + __SetupHIDreportmap(__BTInstallMouse, __BTInstallKeyboard, __BTInstallJoystick, false, &desc_joystickBT_length, &desc_joystickBT); + + PicoBluetoothHID.startHID(localName, hidName, __BTGetCOD(), 33, desc_joystickBT, desc_joystickBT_length); } void JoystickBT_::end() { @@ -54,7 +61,7 @@ void JoystickBT_::end() { //immediately send an HID report void JoystickBT_::send_now() { - PicoBluetoothHID.send(REPORT_ID, &data, sizeof(data)); + PicoBluetoothHID.send(__BTGetJoystickReportID(), &data, sizeof(data)); } JoystickBT_ JoystickBT; diff --git a/libraries/KeyboardBLE/src/KeyboardBLE.cpp b/libraries/KeyboardBLE/src/KeyboardBLE.cpp index cdf4cc1e2..117e32a2a 100644 --- a/libraries/KeyboardBLE/src/KeyboardBLE.cpp +++ b/libraries/KeyboardBLE/src/KeyboardBLE.cpp @@ -22,19 +22,22 @@ #include "KeyboardBLE.h" #include "KeyboardLayout.h" +#include #include //================================================================================ //================================================================================ // Keyboard +// Weak function override to add our descriptor to the list +void __BLEInstallKeyboard() { /* noop */ } + KeyboardBLE_::KeyboardBLE_(void) { // Base class clears the members we care about } -#define REPORT_ID 0x01 - -static const uint8_t desc_keyboard[] = {TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(REPORT_ID))}; +uint8_t *desc_keyboardBLE; +uint16_t desc_keyboardBLE_length; void KeyboardBLE_::begin(const char *localName, const char *hidName, const uint8_t *layout) { if (!localName) { @@ -44,7 +47,10 @@ void KeyboardBLE_::begin(const char *localName, const char *hidName, const uint8 hidName = localName; } _asciimap = layout; - PicoBluetoothBLEHID.startHID(localName, hidName, 0x03c1, desc_keyboard, sizeof(desc_keyboard)); + + __SetupHIDreportmap(__BLEInstallMouse, __BLEInstallKeyboard, __BLEInstallJoystick, false, &desc_keyboardBLE_length, &desc_keyboardBLE); + + PicoBluetoothBLEHID.startHID(localName, hidName, __BLEGetAppearance(), desc_keyboardBLE, desc_keyboardBLE_length); } void KeyboardBLE_::end(void) { @@ -60,12 +66,21 @@ void KeyboardBLE_::sendReport(KeyReport* keys) { data.modifier = keys->modifiers; data.reserved = 0; memcpy(data.keycode, keys->keys, sizeof(data.keycode)); - PicoBluetoothBLEHID.send(&data, sizeof(data)); + + //stitch in report id + static uint8_t report[sizeof(hid_keyboard_report_t) +1]; + report[0] = __BLEGetKeyboardReportID(); + memcpy(&report[1], (uint8_t*)&data, sizeof(hid_keyboard_report_t)); + PicoBluetoothBLEHID.send(&report, sizeof(hid_keyboard_report_t) +1); } void KeyboardBLE_::sendConsumerReport(uint16_t key) { - (void) key; - // TODO - Need some BLE-specific code to send 2nd report + uint8_t report[3]; + + report[0] = __BLEGetKeyboardReportID() + 1; //consumer report id + report[1] = key & 0xFF; + report[2] = (key >> 8) & 0xFF; + PicoBluetoothBLEHID.send(&report, 3); } KeyboardBLE_ KeyboardBLE; diff --git a/libraries/KeyboardBT/src/KeyboardBT.cpp b/libraries/KeyboardBT/src/KeyboardBT.cpp index de5c7de18..66a1f8953 100644 --- a/libraries/KeyboardBT/src/KeyboardBT.cpp +++ b/libraries/KeyboardBT/src/KeyboardBT.cpp @@ -22,19 +22,22 @@ #include "KeyboardBT.h" #include "KeyboardLayout.h" +#include #include //================================================================================ //================================================================================ // Keyboard +// Weak function override to add our descriptor to the list +void __BTInstallKeyboard() { /* noop */ } + KeyboardBT_::KeyboardBT_(void) { // Base class clears the members we care about } -#define REPORT_ID 0x01 - -static const uint8_t desc_keyboard[] = {TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(REPORT_ID)), TUD_HID_REPORT_DESC_CONSUMER(HID_REPORT_ID(REPORT_ID + 1))}; +uint8_t *desc_keyboardBT; +uint16_t desc_keyboardBT_length; static void _hidReportCB(uint16_t cid, hid_report_type_t report_type, uint16_t report_id, int report_size, uint8_t *report) { (void) cid; @@ -56,7 +59,10 @@ void KeyboardBT_::begin(const char *localName, const char *hidName, const uint8_ // Required because the hid_report_type_t overlap in BTStack and TUSB auto *fcn = (void (*)(short unsigned int, hid_report_type_t_bt, short unsigned int, int, unsigned char*))_hidReportCB; hid_device_register_report_data_callback(fcn); - PicoBluetoothHID.startHID(localName, hidName, 0x2540, 33, desc_keyboard, sizeof(desc_keyboard)); + + __SetupHIDreportmap(__BTInstallMouse, __BTInstallKeyboard, __BTInstallJoystick, false, &desc_keyboardBT_length, &desc_keyboardBT); + + PicoBluetoothHID.startHID(localName, hidName, __BTGetCOD(), 33, desc_keyboardBT, desc_keyboardBT_length); } void KeyboardBT_::end(void) { @@ -68,11 +74,11 @@ void KeyboardBT_::sendReport(KeyReport* keys) { data.modifier = keys->modifiers; data.reserved = 0; memcpy(data.keycode, keys->keys, sizeof(data.keycode)); - PicoBluetoothHID.send(REPORT_ID, &data, sizeof(data)); + PicoBluetoothHID.send(__BLEGetKeyboardReportID(), &data, sizeof(data)); } void KeyboardBT_::sendConsumerReport(uint16_t key) { - PicoBluetoothHID.send(REPORT_ID + 1, &key, sizeof(key)); + PicoBluetoothHID.send(__BLEGetKeyboardReportID() + 1, &key, sizeof(key)); } KeyboardBT_ KeyboardBT; diff --git a/libraries/MouseBLE/examples/BLECircleKeysJoystick/BLECircleKeysJoystick.ino b/libraries/MouseBLE/examples/BLECircleKeysJoystick/BLECircleKeysJoystick.ino new file mode 100644 index 000000000..e549d62e8 --- /dev/null +++ b/libraries/MouseBLE/examples/BLECircleKeysJoystick/BLECircleKeysJoystick.ino @@ -0,0 +1,95 @@ +/* Earle F. Philhower, III + Benjamin Aigner */ +/* Released to the public domain */ + +#define ENABLE_LOG_INFO +#define ENABLE_LOG_DEBUG + +#define USE_MOUSE +#define USE_KBD +#define USE_JOYSTICK + +#ifdef USE_MOUSE +#include +#endif +#ifdef USE_KBD +#include +#endif +#ifdef USE_JOYSTICK +#include +#endif + +void setup() { + Serial.begin(115200); + + //If activated nothing happens until the serial port is opened + //while(!Serial); + + +#if (defined(USE_KBD) || defined(USE_JOYSTICK)) && defined(USE_MOUSE) + MouseBLE.begin("BLE Composite"); +#elif defined(USE_MOUSE) + MouseBLE.begin("BLE Mouse"); +#endif + +#ifdef USE_KBD + KeyboardBLE.begin("BLE KBD"); +#endif + +#ifdef USE_JOYSTICK + JoystickBLE.begin("BLE JOY"); +#endif + + Serial.printf("Press BOOTSEL to start action\n"); +#ifdef USE_MOUSE + Serial.println("First the mouse moves"); +#endif +#ifdef USE_KBD + Serial.println("Then \"Hi\" will be printed"); +#endif +#ifdef USE_JOYSTICK + Serial.println("Then joystick buttons & axis are changed"); +#endif +} + +void loop() { + if (BOOTSEL) { +#ifdef USE_MOUSE + Serial.println("ACTION!!!"); + float r = 100; + float ox = 0.0; + float oy = 0.0; + for (float a = 0; a < 2.0 * 3.14159; a += 0.1) { + float ax = r * cos(a); + float ay = r * sin(a); + float dx = ax - ox; + float dy = ay - oy; + MouseBLE.move(dx, dy, 0); + ox = ax; + oy = ay; + delay(10); + } + MouseBLE.setBattery(random(0, 101)); // Set between 0...100% + delay(1000); +#endif + +#ifdef USE_KBD + KeyboardBLE.print("Hi"); +#endif + +#ifdef USE_JOYSTICK + JoystickBLE.button(1, true); + JoystickBLE.X(0); + JoystickBLE.send_now(); + delay(1000); + + JoystickBLE.button(1, false); + JoystickBLE.X(512); + JoystickBLE.send_now(); +#endif + + while (BOOTSEL) { + delay(1); + } + } +} diff --git a/libraries/MouseBLE/src/MouseBLE.cpp b/libraries/MouseBLE/src/MouseBLE.cpp index d29949f45..f14531175 100644 --- a/libraries/MouseBLE/src/MouseBLE.cpp +++ b/libraries/MouseBLE/src/MouseBLE.cpp @@ -21,15 +21,19 @@ #include "MouseBLE.h" #include +#include #include +// Weak function override to add our descriptor to the list +void __BLEInstallMouse() { /* noop */ } + MouseBLE_::MouseBLE_(bool absolute) : HID_Mouse(absolute) { _running = false; } -#define REPORT_ID 0x01 -const uint8_t desc_mouse[] = {TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(REPORT_ID))}; -const uint8_t desc_absmouse[] = {TUD_HID_REPORT_DESC_ABSMOUSE(HID_REPORT_ID(REPORT_ID))}; +uint8_t *desc_mouseBLE; +uint16_t desc_mouseBLE_length; + void MouseBLE_::begin(const char *localName, const char *hidName) { if (!localName) { localName = "PicoW BLE Mouse"; @@ -37,7 +41,10 @@ void MouseBLE_::begin(const char *localName, const char *hidName) { if (!hidName) { hidName = localName; } - PicoBluetoothBLEHID.startHID(localName, hidName, 0x03c2, _absolute ? desc_absmouse : desc_mouse, _absolute ? sizeof(desc_absmouse) : sizeof(desc_mouse)); + + __SetupHIDreportmap(__BLEInstallMouse, __BLEInstallKeyboard, __BLEInstallJoystick, _absolute, &desc_mouseBLE_length, &desc_mouseBLE); + + PicoBluetoothBLEHID.startHID(localName, hidName, __BLEGetAppearance(), desc_mouseBLE, desc_mouseBLE_length); _running = true; } @@ -61,6 +68,8 @@ void MouseBLE_::setAbsolute(bool absolute) { } void MouseBLE_::move(int x, int y, signed char wheel) { + static uint8_t report[sizeof(hid_abs_mouse_report_t) +1]; + if (!_absolute) { hid_mouse_report_t data; data.buttons = _buttons; @@ -68,7 +77,10 @@ void MouseBLE_::move(int x, int y, signed char wheel) { data.y = limit_xy(y); data.wheel = wheel; data.pan = 0; - PicoBluetoothBLEHID.send(&data, sizeof(data)); + + report[0] = __BLEGetMouseReportID(); + memcpy(&report[1], (uint8_t*)&data, sizeof(data)); + PicoBluetoothBLEHID.send(report, sizeof(data) + 1); } else { hid_abs_mouse_report_t data; data.buttons = _buttons; @@ -76,7 +88,10 @@ void MouseBLE_::move(int x, int y, signed char wheel) { data.y = limit_xy(y); data.wheel = wheel; data.pan = 0; - PicoBluetoothBLEHID.send(&data, sizeof(data)); + + report[0] = __BLEGetMouseReportID(); + memcpy(&report[1], (uint8_t*)&data, sizeof(data)); + PicoBluetoothBLEHID.send(report, sizeof(data) + 1); } } diff --git a/libraries/MouseBT/src/MouseBT.cpp b/libraries/MouseBT/src/MouseBT.cpp index 2a3de9e27..f0519f179 100644 --- a/libraries/MouseBT/src/MouseBT.cpp +++ b/libraries/MouseBT/src/MouseBT.cpp @@ -21,15 +21,18 @@ #include "MouseBT.h" #include +#include #include +// Weak function override to add our descriptor to the list +void __BTInstallMouse() { /* noop */ } + MouseBT_::MouseBT_(bool absolute) : HID_Mouse(absolute) { _running = false; } -#define REPORT_ID 0x01 -const uint8_t desc_mouse[] = {TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(REPORT_ID))}; -const uint8_t desc_absmouse[] = {TUD_HID_REPORT_DESC_ABSMOUSE(HID_REPORT_ID(REPORT_ID))}; +uint8_t *desc_mouseBT; +uint16_t desc_mouseBT_length; void MouseBT_::begin(const char *localName, const char *hidName) { if (!localName) { @@ -38,7 +41,10 @@ void MouseBT_::begin(const char *localName, const char *hidName) { if (!hidName) { hidName = localName; } - PicoBluetoothHID.startHID(localName, hidName, 0x2580, 33, _absolute ? desc_absmouse : desc_mouse, _absolute ? sizeof(desc_absmouse) : sizeof(desc_mouse)); + + __SetupHIDreportmap(__BTInstallMouse, __BTInstallKeyboard, __BTInstallJoystick, _absolute, &desc_mouseBT_length, &desc_mouseBT); + + PicoBluetoothHID.startHID(localName, hidName, __BTGetCOD(), 33, desc_mouseBT, desc_mouseBT_length); _running = true; } @@ -63,7 +69,7 @@ void MouseBT_::move(int x, int y, signed char wheel) { data.y = limit_xy(y); data.wheel = wheel; data.pan = 0; - PicoBluetoothHID.send(REPORT_ID, &data, sizeof(data)); + PicoBluetoothHID.send(__BTGetMouseReportID(), &data, sizeof(data)); } else { hid_abs_mouse_report_t data; data.buttons = _buttons; @@ -71,7 +77,7 @@ void MouseBT_::move(int x, int y, signed char wheel) { data.y = limit_xy(y); data.wheel = wheel; data.pan = 0; - PicoBluetoothHID.send(REPORT_ID, &data, sizeof(data)); + PicoBluetoothHID.send(__BTGetMouseReportID(), &data, sizeof(data)); } }