xref: /btstack/src/ble/gatt-service/hids_client.c (revision 2fe59e007d262aaa7dd912048003c98c33149895)
1fc975d0eSMilanka Ringwald /*
2fc975d0eSMilanka Ringwald  * Copyright (C) 2021 BlueKitchen GmbH
3fc975d0eSMilanka Ringwald  *
4fc975d0eSMilanka Ringwald  * Redistribution and use in source and binary forms, with or without
5fc975d0eSMilanka Ringwald  * modification, are permitted provided that the following conditions
6fc975d0eSMilanka Ringwald  * are met:
7fc975d0eSMilanka Ringwald  *
8fc975d0eSMilanka Ringwald  * 1. Redistributions of source code must retain the above copyright
9fc975d0eSMilanka Ringwald  *    notice, this list of conditions and the following disclaimer.
10fc975d0eSMilanka Ringwald  * 2. Redistributions in binary form must reproduce the above copyright
11fc975d0eSMilanka Ringwald  *    notice, this list of conditions and the following disclaimer in the
12fc975d0eSMilanka Ringwald  *    documentation and/or other materials provided with the distribution.
13fc975d0eSMilanka Ringwald  * 3. Neither the name of the copyright holders nor the names of
14fc975d0eSMilanka Ringwald  *    contributors may be used to endorse or promote products derived
15fc975d0eSMilanka Ringwald  *    from this software without specific prior written permission.
16fc975d0eSMilanka Ringwald  * 4. Any redistribution, use, or modification is done solely for
17fc975d0eSMilanka Ringwald  *    personal benefit and not for any commercial purpose or for
18fc975d0eSMilanka Ringwald  *    monetary gain.
19fc975d0eSMilanka Ringwald  *
20fc975d0eSMilanka Ringwald  * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
21fc975d0eSMilanka Ringwald  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22fc975d0eSMilanka Ringwald  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23fc975d0eSMilanka Ringwald  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
24fc975d0eSMilanka Ringwald  * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25fc975d0eSMilanka Ringwald  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26fc975d0eSMilanka Ringwald  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27fc975d0eSMilanka Ringwald  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28fc975d0eSMilanka Ringwald  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29fc975d0eSMilanka Ringwald  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
30fc975d0eSMilanka Ringwald  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31fc975d0eSMilanka Ringwald  * SUCH DAMAGE.
32fc975d0eSMilanka Ringwald  *
33fc975d0eSMilanka Ringwald  * Please inquire about commercial licensing options at
34fc975d0eSMilanka Ringwald  * [email protected]
35fc975d0eSMilanka Ringwald  *
36fc975d0eSMilanka Ringwald  */
37fc975d0eSMilanka Ringwald 
38fc975d0eSMilanka Ringwald #define BTSTACK_FILE__ "hids_client.c"
39fc975d0eSMilanka Ringwald 
40fc975d0eSMilanka Ringwald #include "btstack_config.h"
41fc975d0eSMilanka Ringwald 
42fc975d0eSMilanka Ringwald #include <stdint.h>
43fc975d0eSMilanka Ringwald #include <string.h>
44fc975d0eSMilanka Ringwald 
45cf26c8fbSMilanka Ringwald #include "ble/gatt-service/hids_client.h"
46fc975d0eSMilanka Ringwald 
47cf26c8fbSMilanka Ringwald #include "btstack_memory.h"
48fc975d0eSMilanka Ringwald #include "ble/att_db.h"
49fc975d0eSMilanka Ringwald #include "ble/core.h"
50fc975d0eSMilanka Ringwald #include "ble/gatt_client.h"
51fc975d0eSMilanka Ringwald #include "ble/sm.h"
52cf26c8fbSMilanka Ringwald #include "bluetooth_gatt.h"
53fc975d0eSMilanka Ringwald #include "btstack_debug.h"
54fc975d0eSMilanka Ringwald #include "btstack_event.h"
55fc975d0eSMilanka Ringwald #include "btstack_run_loop.h"
56fc975d0eSMilanka Ringwald #include "gap.h"
57fc975d0eSMilanka Ringwald 
58cf26c8fbSMilanka Ringwald static btstack_linked_list_t clients;
59cf26c8fbSMilanka Ringwald static uint16_t hids_cid_counter = 0;
601624214bSMilanka Ringwald static uint16_t hids_report_counter = 0;
61fc975d0eSMilanka Ringwald 
62cf26c8fbSMilanka Ringwald static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
63cf26c8fbSMilanka Ringwald 
64cf26c8fbSMilanka Ringwald static uint16_t hids_get_next_cid(void){
65cf26c8fbSMilanka Ringwald     if (hids_cid_counter == 0xffff) {
66cf26c8fbSMilanka Ringwald         hids_cid_counter = 1;
67cf26c8fbSMilanka Ringwald     } else {
68cf26c8fbSMilanka Ringwald         hids_cid_counter++;
69cf26c8fbSMilanka Ringwald     }
70cf26c8fbSMilanka Ringwald     return hids_cid_counter;
71fc975d0eSMilanka Ringwald }
72fc975d0eSMilanka Ringwald 
731624214bSMilanka Ringwald static uint8_t hids_get_next_report_index(void){
741624214bSMilanka Ringwald     if (hids_report_counter < HIDS_CLIENT_NUM_REPORTS) {
751624214bSMilanka Ringwald         hids_report_counter++;
761624214bSMilanka Ringwald     } else {
771624214bSMilanka Ringwald         hids_report_counter = HIDS_CLIENT_INVALID_REPORT_INDEX;
781624214bSMilanka Ringwald     }
791624214bSMilanka Ringwald     return hids_report_counter;
801624214bSMilanka Ringwald }
811624214bSMilanka Ringwald 
82cf26c8fbSMilanka Ringwald static hids_client_t * hids_create_client(hci_con_handle_t con_handle, uint16_t cid){
83cf26c8fbSMilanka Ringwald     hids_client_t * client = btstack_memory_hids_client_get();
84cf26c8fbSMilanka Ringwald     if (!client){
85cf26c8fbSMilanka Ringwald         log_error("Not enough memory to create client");
86cf26c8fbSMilanka Ringwald         return NULL;
87cf26c8fbSMilanka Ringwald     }
88cf26c8fbSMilanka Ringwald     client->state = HIDS_CLIENT_STATE_IDLE;
89cf26c8fbSMilanka Ringwald     client->cid = cid;
90cf26c8fbSMilanka Ringwald     client->con_handle = con_handle;
91fc975d0eSMilanka Ringwald 
92cf26c8fbSMilanka Ringwald     btstack_linked_list_add(&clients, (btstack_linked_item_t *) client);
93cf26c8fbSMilanka Ringwald     return client;
94fc975d0eSMilanka Ringwald }
95fc975d0eSMilanka Ringwald 
96cf26c8fbSMilanka Ringwald static void hids_finalize_client(hids_client_t * client){
97cf26c8fbSMilanka Ringwald     btstack_linked_list_remove(&clients, (btstack_linked_item_t *) client);
98cf26c8fbSMilanka Ringwald     btstack_memory_hids_client_free(client);
99fc975d0eSMilanka Ringwald }
100cf26c8fbSMilanka Ringwald 
101cf26c8fbSMilanka Ringwald static hids_client_t * hids_get_client_for_con_handle(hci_con_handle_t con_handle){
102cf26c8fbSMilanka Ringwald     btstack_linked_list_iterator_t it;
103cf26c8fbSMilanka Ringwald     btstack_linked_list_iterator_init(&it, &clients);
104cf26c8fbSMilanka Ringwald     while (btstack_linked_list_iterator_has_next(&it)){
105cf26c8fbSMilanka Ringwald         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
106cf26c8fbSMilanka Ringwald         if (client->con_handle != con_handle) continue;
107cf26c8fbSMilanka Ringwald         return client;
108cf26c8fbSMilanka Ringwald     }
109cf26c8fbSMilanka Ringwald     return NULL;
110cf26c8fbSMilanka Ringwald }
111cf26c8fbSMilanka Ringwald 
112cf26c8fbSMilanka Ringwald static hids_client_t * hids_get_client_for_cid(uint16_t hids_cid){
113cf26c8fbSMilanka Ringwald     btstack_linked_list_iterator_t it;
114cf26c8fbSMilanka Ringwald     btstack_linked_list_iterator_init(&it, &clients);
115cf26c8fbSMilanka Ringwald     while (btstack_linked_list_iterator_has_next(&it)){
116cf26c8fbSMilanka Ringwald         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
117cf26c8fbSMilanka Ringwald         if (client->cid != hids_cid) continue;
118cf26c8fbSMilanka Ringwald         return client;
119cf26c8fbSMilanka Ringwald     }
120cf26c8fbSMilanka Ringwald     return NULL;
121cf26c8fbSMilanka Ringwald }
122cf26c8fbSMilanka Ringwald 
123cf26c8fbSMilanka Ringwald static void hids_emit_connection_established(hids_client_t * client, uint8_t status){
1246bcfb631SMilanka Ringwald     uint8_t event[8];
125cf26c8fbSMilanka Ringwald     int pos = 0;
126cf26c8fbSMilanka Ringwald     event[pos++] = HCI_EVENT_GATTSERVICE_META;
127cf26c8fbSMilanka Ringwald     event[pos++] = sizeof(event) - 2;
128cf26c8fbSMilanka Ringwald     event[pos++] = GATTSERVICE_SUBEVENT_HID_SERVICE_CONNECTED;
129cf26c8fbSMilanka Ringwald     little_endian_store_16(event, pos, client->cid);
130cf26c8fbSMilanka Ringwald     pos += 2;
131cf26c8fbSMilanka Ringwald     event[pos++] = status;
1326bcfb631SMilanka Ringwald     event[pos++] = client->protocol_mode;
133cf26c8fbSMilanka Ringwald     event[pos++] = client->num_instances;
134cf26c8fbSMilanka Ringwald     (*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
135cf26c8fbSMilanka Ringwald }
136cf26c8fbSMilanka Ringwald 
137cf26c8fbSMilanka Ringwald 
138cf26c8fbSMilanka Ringwald static void hids_run_for_client(hids_client_t * client){
139cf26c8fbSMilanka Ringwald     uint8_t att_status;
1406bcfb631SMilanka Ringwald     gatt_client_service_t service;
1416bcfb631SMilanka Ringwald     gatt_client_characteristic_t characteristic;
142cf26c8fbSMilanka Ringwald 
143cf26c8fbSMilanka Ringwald     switch (client->state){
144cf26c8fbSMilanka Ringwald         case HIDS_CLIENT_STATE_W2_QUERY_SERVICE:
145cf26c8fbSMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_SERVICE_RESULT;
146cf26c8fbSMilanka Ringwald             att_status = gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, client->con_handle, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE);
147cf26c8fbSMilanka Ringwald             // TODO handle status
148cf26c8fbSMilanka Ringwald             UNUSED(att_status);
149cf26c8fbSMilanka Ringwald             break;
150cf26c8fbSMilanka Ringwald 
151cf26c8fbSMilanka Ringwald         case HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC:
1526bcfb631SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT;
1536bcfb631SMilanka Ringwald 
1546bcfb631SMilanka Ringwald             service.start_group_handle = client->services[client->service_index].start_handle;
1556bcfb631SMilanka Ringwald             service.end_group_handle = client->services[client->service_index].end_handle;
1566bcfb631SMilanka Ringwald             att_status = gatt_client_discover_characteristics_for_service(&handle_gatt_client_event, client->con_handle, &service);
1576bcfb631SMilanka Ringwald 
1586bcfb631SMilanka Ringwald             UNUSED(att_status);
1596bcfb631SMilanka Ringwald             break;
1606bcfb631SMilanka Ringwald 
1612901a9b7SMilanka Ringwald         case HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD:
1622901a9b7SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED;
1636bcfb631SMilanka Ringwald             characteristic.value_handle = client->boot_keyboard_input_value_handle;
1646bcfb631SMilanka Ringwald             characteristic.end_handle = client->boot_keyboard_input_end_handle;
1652901a9b7SMilanka Ringwald             characteristic.properties = client->boot_keyboard_input_properties;
1666bcfb631SMilanka Ringwald             att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
1672901a9b7SMilanka Ringwald 
1682901a9b7SMilanka Ringwald             if (att_status != ERROR_CODE_SUCCESS){
1692901a9b7SMilanka Ringwald                 client->boot_keyboard_input_value_handle = 0;
1702901a9b7SMilanka Ringwald             }
1716bcfb631SMilanka Ringwald             break;
1726bcfb631SMilanka Ringwald 
1732901a9b7SMilanka Ringwald         case HIDS_CLIENT_STATE_W2_ENABLE_MOUSE:
1742901a9b7SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_MOUSE_ENABLED;
1756bcfb631SMilanka Ringwald             characteristic.value_handle = client->boot_mouse_input_value_handle;
1766bcfb631SMilanka Ringwald             characteristic.end_handle = client->boot_mouse_input_end_handle;
1772901a9b7SMilanka Ringwald             characteristic.properties = client->boot_mouse_input_properties;
1786bcfb631SMilanka Ringwald             att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
1792901a9b7SMilanka Ringwald 
1802901a9b7SMilanka Ringwald             if (att_status != ERROR_CODE_SUCCESS){
1812901a9b7SMilanka Ringwald                 client->boot_mouse_input_value_handle = 0;
1822901a9b7SMilanka Ringwald             }
1836bcfb631SMilanka Ringwald             break;
1846bcfb631SMilanka Ringwald 
1852901a9b7SMilanka Ringwald         case HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE:
1862901a9b7SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_SET_PROTOCOL_MODE;
1872901a9b7SMilanka Ringwald             att_status = gatt_client_write_value_of_characteristic_without_response(client->con_handle, client->protocol_mode_value_handle, 1, (uint8_t *)&client->required_protocol_mode);
1886bcfb631SMilanka Ringwald             UNUSED(att_status);
1892901a9b7SMilanka Ringwald 
1902901a9b7SMilanka Ringwald             client->protocol_mode = client->required_protocol_mode;
1916bcfb631SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_CONNECTED;
1926bcfb631SMilanka Ringwald             hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
193cf26c8fbSMilanka Ringwald             break;
194cf26c8fbSMilanka Ringwald 
1951624214bSMilanka Ringwald         case HIDS_CLIENT_W2_SEND_REPORT:{
1961624214bSMilanka Ringwald             client->state = HIDS_CLIENT_STATE_CONNECTED;
1971624214bSMilanka Ringwald 
1981624214bSMilanka Ringwald             att_status = gatt_client_write_value_of_characteristic_without_response(client->con_handle,
1991624214bSMilanka Ringwald                 client->reports[client->active_report_index].value_handle,
2001624214bSMilanka Ringwald                 client->report_len, (uint8_t *)client->report);
2011624214bSMilanka Ringwald             UNUSED(att_status);
2021624214bSMilanka Ringwald             break;
2031624214bSMilanka Ringwald         }
2046bcfb631SMilanka Ringwald         default:
2056bcfb631SMilanka Ringwald             break;
2066bcfb631SMilanka Ringwald     }
2076bcfb631SMilanka Ringwald }
2086bcfb631SMilanka Ringwald 
20972a8858fSMilanka Ringwald static void hids_client_setup_report_event(hids_client_t * client, uint8_t report_id, uint8_t *buffer, uint16_t report_len){
21072a8858fSMilanka Ringwald     uint16_t pos = 0;
21172a8858fSMilanka Ringwald     buffer[pos++] = HCI_EVENT_GATTSERVICE_META;
21272a8858fSMilanka Ringwald     pos++;  // skip len
21372a8858fSMilanka Ringwald     buffer[pos++] = GATTSERVICE_SUBEVENT_HID_REPORT;
21472a8858fSMilanka Ringwald     little_endian_store_16(buffer, pos, client->cid);
21572a8858fSMilanka Ringwald     pos += 2;
21672a8858fSMilanka Ringwald     buffer[pos++] = report_id;
21772a8858fSMilanka Ringwald     little_endian_store_16(buffer, pos, report_len);
21872a8858fSMilanka Ringwald     pos += 2;
21972a8858fSMilanka Ringwald     buffer[1] = pos + report_len - 2;
22072a8858fSMilanka Ringwald }
22172a8858fSMilanka Ringwald 
22272a8858fSMilanka Ringwald static void handle_boot_keyboard_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
2236bcfb631SMilanka Ringwald     UNUSED(packet_type);
2246bcfb631SMilanka Ringwald     UNUSED(channel);
2256bcfb631SMilanka Ringwald     UNUSED(size);
22672a8858fSMilanka Ringwald 
22772a8858fSMilanka Ringwald     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
22872a8858fSMilanka Ringwald 
22972a8858fSMilanka Ringwald     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
23072a8858fSMilanka Ringwald     btstack_assert(client != NULL);
23172a8858fSMilanka Ringwald 
23272a8858fSMilanka Ringwald     uint8_t * in_place_event = packet;
23372a8858fSMilanka Ringwald     hids_client_setup_report_event(client, HID_BOOT_MODE_KEYBOARD_ID, in_place_event, gatt_event_notification_get_value_length(packet));
23472a8858fSMilanka Ringwald     (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
23572a8858fSMilanka Ringwald }
23672a8858fSMilanka Ringwald 
23772a8858fSMilanka Ringwald static void handle_boot_mouse_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
23872a8858fSMilanka Ringwald     UNUSED(packet_type);
23972a8858fSMilanka Ringwald     UNUSED(channel);
24072a8858fSMilanka Ringwald     UNUSED(size);
24172a8858fSMilanka Ringwald 
24272a8858fSMilanka Ringwald     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
24372a8858fSMilanka Ringwald 
24472a8858fSMilanka Ringwald     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
24572a8858fSMilanka Ringwald     btstack_assert(client != NULL);
24672a8858fSMilanka Ringwald 
24772a8858fSMilanka Ringwald     uint8_t * in_place_event = packet;
24872a8858fSMilanka Ringwald     hids_client_setup_report_event(client, HID_BOOT_MODE_MOUSE_ID, in_place_event, gatt_event_notification_get_value_length(packet));
24972a8858fSMilanka Ringwald     (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
250cf26c8fbSMilanka Ringwald }
251cf26c8fbSMilanka Ringwald 
2521624214bSMilanka Ringwald static uint8_t find_report_index_for_value_handle(hids_client_t * client, uint16_t value_handle){
2531624214bSMilanka Ringwald     uint8_t i;
2541624214bSMilanka Ringwald     for (i = 0; i < client->num_reports; client++){
2551624214bSMilanka Ringwald         if (client->reports[i].value_handle == value_handle){
2561624214bSMilanka Ringwald             return i;
2571624214bSMilanka Ringwald         }
2581624214bSMilanka Ringwald     }
2591624214bSMilanka Ringwald     return HIDS_CLIENT_INVALID_REPORT_INDEX;
2601624214bSMilanka Ringwald }
2611624214bSMilanka Ringwald 
262*2fe59e00SMilanka Ringwald static uint8_t find_report_index_for_report_id_and_type(hids_client_t * client, uint8_t report_id, hid_report_type_t report_type){
2631624214bSMilanka Ringwald     uint8_t i;
2641624214bSMilanka Ringwald     for (i = 0; i < client->num_reports; client++){
265*2fe59e00SMilanka Ringwald         if ( (client->reports[i].report_id == report_id) && (client->reports[i].report_type == report_type)){
2661624214bSMilanka Ringwald             return i;
2671624214bSMilanka Ringwald         }
2681624214bSMilanka Ringwald     }
2691624214bSMilanka Ringwald     return HIDS_CLIENT_INVALID_REPORT_INDEX;
2701624214bSMilanka Ringwald }
2711624214bSMilanka Ringwald 
272cf26c8fbSMilanka Ringwald static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
273cf26c8fbSMilanka Ringwald     UNUSED(packet_type);
274cf26c8fbSMilanka Ringwald     UNUSED(channel);
275cf26c8fbSMilanka Ringwald     UNUSED(size);
276cf26c8fbSMilanka Ringwald 
2776bcfb631SMilanka Ringwald     hids_client_t * client = NULL;
278cf26c8fbSMilanka Ringwald     uint8_t att_status;
2796bcfb631SMilanka Ringwald     gatt_client_service_t service;
2806bcfb631SMilanka Ringwald     gatt_client_characteristic_t characteristic;
2811624214bSMilanka Ringwald     uint8_t report_index;
282cf26c8fbSMilanka Ringwald 
283cf26c8fbSMilanka Ringwald     switch(hci_event_packet_get_type(packet)){
284cf26c8fbSMilanka Ringwald         case GATT_EVENT_SERVICE_QUERY_RESULT:
285cf26c8fbSMilanka Ringwald             client = hids_get_client_for_con_handle(gatt_event_service_query_result_get_handle(packet));
286cf26c8fbSMilanka Ringwald             btstack_assert(client != NULL);
287cf26c8fbSMilanka Ringwald 
2886bcfb631SMilanka Ringwald             if (client->state != HIDS_CLIENT_STATE_W4_SERVICE_RESULT) {
2896bcfb631SMilanka Ringwald                 hids_emit_connection_established(client, GATT_CLIENT_IN_WRONG_STATE);
2906bcfb631SMilanka Ringwald                 hids_finalize_client(client);
2916bcfb631SMilanka Ringwald                 break;
2926bcfb631SMilanka Ringwald             }
2936bcfb631SMilanka Ringwald 
2946bcfb631SMilanka Ringwald             if (client->num_instances < MAX_NUM_HID_SERVICES){
2956bcfb631SMilanka Ringwald                 gatt_event_service_query_result_get_service(packet, &service);
2966bcfb631SMilanka Ringwald                 client->services[client->num_instances].start_handle = service.start_group_handle;
2976bcfb631SMilanka Ringwald                 client->services[client->num_instances].end_handle = service.end_group_handle;
2986bcfb631SMilanka Ringwald             }
2996bcfb631SMilanka Ringwald             client->num_instances++;
3006bcfb631SMilanka Ringwald             break;
3016bcfb631SMilanka Ringwald 
3026bcfb631SMilanka Ringwald         case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
3036bcfb631SMilanka Ringwald             client = hids_get_client_for_con_handle(gatt_event_characteristic_query_result_get_handle(packet));
3046bcfb631SMilanka Ringwald             btstack_assert(client != NULL);
3056bcfb631SMilanka Ringwald 
3066bcfb631SMilanka Ringwald             gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
3076bcfb631SMilanka Ringwald             switch (characteristic.uuid16){
3086bcfb631SMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT:
3096bcfb631SMilanka Ringwald                     client->boot_keyboard_input_value_handle = characteristic.value_handle;
3102901a9b7SMilanka Ringwald                     client->boot_keyboard_input_end_handle   = characteristic.end_handle;
3112901a9b7SMilanka Ringwald                     client->boot_keyboard_input_properties   = characteristic.properties;
3126bcfb631SMilanka Ringwald                     break;
3131624214bSMilanka Ringwald 
3146bcfb631SMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT:
3156bcfb631SMilanka Ringwald                     client->boot_mouse_input_value_handle = characteristic.value_handle;
3162901a9b7SMilanka Ringwald                     client->boot_mouse_input_end_handle   = characteristic.end_handle;
3172901a9b7SMilanka Ringwald                     client->boot_mouse_input_properties   = characteristic.properties;
3186bcfb631SMilanka Ringwald                     break;
3191624214bSMilanka Ringwald 
3206bcfb631SMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE:
3216bcfb631SMilanka Ringwald                     client->protocol_mode_value_handle = characteristic.value_handle;
3226bcfb631SMilanka Ringwald                     break;
3231624214bSMilanka Ringwald 
3241624214bSMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT:
3251624214bSMilanka Ringwald                     report_index = find_report_index_for_value_handle(client, characteristic.value_handle);
3261624214bSMilanka Ringwald                     if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
3271624214bSMilanka Ringwald                         break;
3281624214bSMilanka Ringwald                     }
3291624214bSMilanka Ringwald 
3301624214bSMilanka Ringwald                     report_index = hids_get_next_report_index();
3311624214bSMilanka Ringwald 
3321624214bSMilanka Ringwald                     if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
3331624214bSMilanka Ringwald                         client->reports[report_index].value_handle = characteristic.value_handle;
3341624214bSMilanka Ringwald                         client->reports[report_index].report_id = HID_BOOT_MODE_KEYBOARD_ID;
3351624214bSMilanka Ringwald                         client->reports[report_index].report_type = HID_REPORT_TYPE_OUTPUT;
3361624214bSMilanka Ringwald                         client->num_reports++;
3371624214bSMilanka Ringwald                     } else {
3381624214bSMilanka Ringwald                         log_info("not enough storage, increase HIDS_CLIENT_NUM_REPORTS");
3391624214bSMilanka Ringwald                     }
3401624214bSMilanka Ringwald                     break;
3411624214bSMilanka Ringwald 
3426bcfb631SMilanka Ringwald                 default:
3436bcfb631SMilanka Ringwald                     break;
3446bcfb631SMilanka Ringwald             }
345cf26c8fbSMilanka Ringwald             break;
346cf26c8fbSMilanka Ringwald 
347cf26c8fbSMilanka Ringwald         case GATT_EVENT_QUERY_COMPLETE:
348cf26c8fbSMilanka Ringwald             client = hids_get_client_for_con_handle(gatt_event_query_complete_get_handle(packet));
349cf26c8fbSMilanka Ringwald             btstack_assert(client != NULL);
350cf26c8fbSMilanka Ringwald 
351cf26c8fbSMilanka Ringwald             att_status = gatt_event_query_complete_get_att_status(packet);
352cf26c8fbSMilanka Ringwald 
353cf26c8fbSMilanka Ringwald             switch (client->state){
354cf26c8fbSMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_SERVICE_RESULT:
3556bcfb631SMilanka Ringwald                     if (att_status != ATT_ERROR_SUCCESS){
3566bcfb631SMilanka Ringwald                         hids_emit_connection_established(client, att_status);
3576bcfb631SMilanka Ringwald                         hids_finalize_client(client);
358cf26c8fbSMilanka Ringwald                         break;
3596bcfb631SMilanka Ringwald                     }
3606bcfb631SMilanka Ringwald 
3616bcfb631SMilanka Ringwald                     if (client->num_instances == 0){
3626bcfb631SMilanka Ringwald                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
3636bcfb631SMilanka Ringwald                         hids_finalize_client(client);
3646bcfb631SMilanka Ringwald                         break;
3656bcfb631SMilanka Ringwald                     }
3666bcfb631SMilanka Ringwald 
3676bcfb631SMilanka Ringwald                     if (client->num_instances > MAX_NUM_HID_SERVICES) {
3686bcfb631SMilanka Ringwald                         log_info("%d hid services found, only first %d can be stored, increase MAX_NUM_HID_SERVICES", client->num_instances, MAX_NUM_HID_SERVICES);
3696bcfb631SMilanka Ringwald                         client->num_instances = MAX_NUM_HID_SERVICES;
3706bcfb631SMilanka Ringwald                     }
3716bcfb631SMilanka Ringwald 
3726bcfb631SMilanka Ringwald                     client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
3736bcfb631SMilanka Ringwald                     client->service_index = 0;
3746bcfb631SMilanka Ringwald                     break;
3756bcfb631SMilanka Ringwald 
3766bcfb631SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT:
3776bcfb631SMilanka Ringwald                     if (att_status != ATT_ERROR_SUCCESS){
3786bcfb631SMilanka Ringwald                         hids_emit_connection_established(client, att_status);
3796bcfb631SMilanka Ringwald                         hids_finalize_client(client);
3806bcfb631SMilanka Ringwald                         break;
3816bcfb631SMilanka Ringwald                     }
3826bcfb631SMilanka Ringwald 
3836bcfb631SMilanka Ringwald                     switch (client->required_protocol_mode){
3846bcfb631SMilanka Ringwald                         case HID_PROTOCOL_MODE_BOOT:
3856bcfb631SMilanka Ringwald                             if (client->boot_keyboard_input_value_handle != 0){
3862901a9b7SMilanka Ringwald                                 client->state = HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD;
3876bcfb631SMilanka Ringwald                                 break;
3886bcfb631SMilanka Ringwald                             }
3896bcfb631SMilanka Ringwald                             if (client->boot_mouse_input_value_handle != 0){
3902901a9b7SMilanka Ringwald                                 client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
3916bcfb631SMilanka Ringwald                                 break;
3926bcfb631SMilanka Ringwald                             }
3936bcfb631SMilanka Ringwald                             hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
3946bcfb631SMilanka Ringwald                             hids_finalize_client(client);
3956bcfb631SMilanka Ringwald                             break;
3966bcfb631SMilanka Ringwald                         default:
3976bcfb631SMilanka Ringwald                             break;
3986bcfb631SMilanka Ringwald                     }
3996bcfb631SMilanka Ringwald                     break;
4006bcfb631SMilanka Ringwald 
4016bcfb631SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED:
4022901a9b7SMilanka Ringwald                     if (client->boot_keyboard_input_value_handle != 0){
4036bcfb631SMilanka Ringwald                         // setup listener
4046bcfb631SMilanka Ringwald                         characteristic.value_handle = client->boot_keyboard_input_value_handle;
40572a8858fSMilanka Ringwald                         gatt_client_listen_for_characteristic_value_updates(&client->boot_keyboard_notifications, &handle_boot_keyboard_hid_event, client->con_handle, &characteristic);
4062901a9b7SMilanka Ringwald                     } else {
4072901a9b7SMilanka Ringwald                         if (client->boot_mouse_input_value_handle == 0){
4082901a9b7SMilanka Ringwald                             hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
4092901a9b7SMilanka Ringwald                             hids_finalize_client(client);
4106bcfb631SMilanka Ringwald                             break;
4116bcfb631SMilanka Ringwald                         }
4122901a9b7SMilanka Ringwald                     }
4132901a9b7SMilanka Ringwald 
4142901a9b7SMilanka Ringwald                     if (client->boot_mouse_input_value_handle != 0){
4152901a9b7SMilanka Ringwald                         client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
4162901a9b7SMilanka Ringwald                         break;
4172901a9b7SMilanka Ringwald                     }
4182901a9b7SMilanka Ringwald 
4196bcfb631SMilanka Ringwald                     // set protocol
4206bcfb631SMilanka Ringwald                     if (client->protocol_mode_value_handle != 0){
4212901a9b7SMilanka Ringwald                         client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
4226bcfb631SMilanka Ringwald                     } else {
4232901a9b7SMilanka Ringwald                         client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
4246bcfb631SMilanka Ringwald                         client->state = HIDS_CLIENT_STATE_CONNECTED;
4256bcfb631SMilanka Ringwald                         hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
4266bcfb631SMilanka Ringwald                     }
4272901a9b7SMilanka Ringwald                     hids_run_for_client(client);
4286bcfb631SMilanka Ringwald                     break;
4296bcfb631SMilanka Ringwald 
4306bcfb631SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_MOUSE_ENABLED:
4312901a9b7SMilanka Ringwald                     if (client->boot_mouse_input_value_handle != 0){
4326bcfb631SMilanka Ringwald                         // setup listener
4336bcfb631SMilanka Ringwald                         characteristic.value_handle = client->boot_mouse_input_value_handle;
43472a8858fSMilanka Ringwald                         gatt_client_listen_for_characteristic_value_updates(&client->boot_mouse_notifications, &handle_boot_mouse_hid_event, client->con_handle, &characteristic);
4352901a9b7SMilanka Ringwald                     } else {
4362901a9b7SMilanka Ringwald                         if (client->boot_keyboard_input_value_handle == 0){
4372901a9b7SMilanka Ringwald                             hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
4382901a9b7SMilanka Ringwald                             hids_finalize_client(client);
4392901a9b7SMilanka Ringwald                             break;
4402901a9b7SMilanka Ringwald                         }
4412901a9b7SMilanka Ringwald                     }
4426bcfb631SMilanka Ringwald 
4436bcfb631SMilanka Ringwald                     if (client->protocol_mode_value_handle != 0){
4442901a9b7SMilanka Ringwald                         client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
4456bcfb631SMilanka Ringwald                     } else {
4462901a9b7SMilanka Ringwald                         client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
4476bcfb631SMilanka Ringwald                         client->state = HIDS_CLIENT_STATE_CONNECTED;
4486bcfb631SMilanka Ringwald                         hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
4496bcfb631SMilanka Ringwald                     }
4506bcfb631SMilanka Ringwald                     break;
451cf26c8fbSMilanka Ringwald                 default:
452cf26c8fbSMilanka Ringwald                     break;
453cf26c8fbSMilanka Ringwald             }
454cf26c8fbSMilanka Ringwald             break;
455cf26c8fbSMilanka Ringwald 
456cf26c8fbSMilanka Ringwald         default:
457cf26c8fbSMilanka Ringwald             break;
458cf26c8fbSMilanka Ringwald     }
4596bcfb631SMilanka Ringwald 
4606bcfb631SMilanka Ringwald     if (client != NULL){
4616bcfb631SMilanka Ringwald         hids_run_for_client(client);
4626bcfb631SMilanka Ringwald     }
463cf26c8fbSMilanka Ringwald }
464cf26c8fbSMilanka Ringwald 
4656bcfb631SMilanka Ringwald uint8_t hids_client_connect(hci_con_handle_t con_handle, btstack_packet_handler_t packet_handler, hid_protocol_mode_t protocol_mode, uint16_t * hids_cid){
466cf26c8fbSMilanka Ringwald     btstack_assert(packet_handler != NULL);
467cf26c8fbSMilanka Ringwald 
468cf26c8fbSMilanka Ringwald     hids_client_t * client = hids_get_client_for_con_handle(con_handle);
469cf26c8fbSMilanka Ringwald     if (client != NULL){
470cf26c8fbSMilanka Ringwald         return ERROR_CODE_COMMAND_DISALLOWED;
471cf26c8fbSMilanka Ringwald     }
472cf26c8fbSMilanka Ringwald 
473cf26c8fbSMilanka Ringwald     uint16_t cid = hids_get_next_cid();
474cf26c8fbSMilanka Ringwald     if (hids_cid != NULL) {
475cf26c8fbSMilanka Ringwald         *hids_cid = cid;
476cf26c8fbSMilanka Ringwald     }
477cf26c8fbSMilanka Ringwald 
478cf26c8fbSMilanka Ringwald     client = hids_create_client(con_handle, cid);
479cf26c8fbSMilanka Ringwald     if (client == NULL) {
480cf26c8fbSMilanka Ringwald         return BTSTACK_MEMORY_ALLOC_FAILED;
481cf26c8fbSMilanka Ringwald     }
482cf26c8fbSMilanka Ringwald 
4836bcfb631SMilanka Ringwald     client->required_protocol_mode = protocol_mode;
484cf26c8fbSMilanka Ringwald     client->client_handler = packet_handler;
485cf26c8fbSMilanka Ringwald     client->state = HIDS_CLIENT_STATE_W2_QUERY_SERVICE;
486cf26c8fbSMilanka Ringwald 
487cf26c8fbSMilanka Ringwald     hids_run_for_client(client);
488cf26c8fbSMilanka Ringwald     return ERROR_CODE_SUCCESS;
489cf26c8fbSMilanka Ringwald }
490cf26c8fbSMilanka Ringwald 
491cf26c8fbSMilanka Ringwald uint8_t hids_client_disconnect(uint16_t hids_cid){
492cf26c8fbSMilanka Ringwald     hids_client_t * client = hids_get_client_for_cid(hids_cid);
493cf26c8fbSMilanka Ringwald     if (client == NULL){
494cf26c8fbSMilanka Ringwald         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
495cf26c8fbSMilanka Ringwald     }
496cf26c8fbSMilanka Ringwald     // finalize connection
497cf26c8fbSMilanka Ringwald     hids_finalize_client(client);
498cf26c8fbSMilanka Ringwald     return ERROR_CODE_SUCCESS;
499cf26c8fbSMilanka Ringwald }
500cf26c8fbSMilanka Ringwald 
5011624214bSMilanka Ringwald uint8_t hids_client_send_report(uint16_t hids_cid, uint8_t report_id, const uint8_t * report, uint8_t report_len){
5021624214bSMilanka Ringwald     hids_client_t * client = hids_get_client_for_cid(hids_cid);
5031624214bSMilanka Ringwald     if (client == NULL){
5041624214bSMilanka Ringwald         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
5051624214bSMilanka Ringwald     }
5061624214bSMilanka Ringwald 
5071624214bSMilanka Ringwald     if (client->state != HIDS_CLIENT_STATE_CONNECTED) {
5081624214bSMilanka Ringwald         return ERROR_CODE_COMMAND_DISALLOWED;
5091624214bSMilanka Ringwald     }
5101624214bSMilanka Ringwald 
511*2fe59e00SMilanka Ringwald     uint8_t report_index = find_report_index_for_report_id_and_type(client, report_id, HID_REPORT_TYPE_OUTPUT);
5121624214bSMilanka Ringwald     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
5131624214bSMilanka Ringwald         return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE;
5141624214bSMilanka Ringwald     }
5151624214bSMilanka Ringwald 
5161624214bSMilanka Ringwald     uint16_t mtu;
5171624214bSMilanka Ringwald     uint8_t status = gatt_client_get_mtu(client->con_handle, &mtu);
5181624214bSMilanka Ringwald 
5191624214bSMilanka Ringwald     if (status != ERROR_CODE_SUCCESS){
5201624214bSMilanka Ringwald         return status;
5211624214bSMilanka Ringwald     }
5221624214bSMilanka Ringwald 
5231624214bSMilanka Ringwald     if (mtu - 2 < report_len){
5241624214bSMilanka Ringwald         return ERROR_CODE_PARAMETER_OUT_OF_MANDATORY_RANGE;
5251624214bSMilanka Ringwald     }
5261624214bSMilanka Ringwald 
5271624214bSMilanka Ringwald     client->state = HIDS_CLIENT_W2_SEND_REPORT;
5281624214bSMilanka Ringwald     client->active_report_index = report_index;
5291624214bSMilanka Ringwald     client->report = report;
5301624214bSMilanka Ringwald     client->report_len = report_len;
5311624214bSMilanka Ringwald 
5321624214bSMilanka Ringwald     hids_run_for_client(client);
5331624214bSMilanka Ringwald     return ERROR_CODE_SUCCESS;
5341624214bSMilanka Ringwald }
5351624214bSMilanka Ringwald 
536cf26c8fbSMilanka Ringwald void hids_client_init(void){}
537cf26c8fbSMilanka Ringwald 
538cf26c8fbSMilanka Ringwald void hids_client_deinit(void){}
539