xref: /btstack/src/ble/gatt-service/hids_client.c (revision 3322b222ec1b74be99aede76b31280a1aff58786)
1 /*
2  * Copyright (C) 2021 BlueKitchen GmbH
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the copyright holders nor the names of
14  *    contributors may be used to endorse or promote products derived
15  *    from this software without specific prior written permission.
16  * 4. Any redistribution, use, or modification is done solely for
17  *    personal benefit and not for any commercial purpose or for
18  *    monetary gain.
19  *
20  * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
24  * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
30  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * Please inquire about commercial licensing options at
34  * [email protected]
35  *
36  */
37 
38 #define BTSTACK_FILE__ "hids_client.c"
39 
40 #include "btstack_config.h"
41 
42 #include <stdint.h>
43 #include <string.h>
44 
45 #include "ble/gatt-service/hids_client.h"
46 
47 #include "btstack_memory.h"
48 #include "ble/att_db.h"
49 #include "ble/core.h"
50 #include "ble/gatt_client.h"
51 #include "ble/sm.h"
52 #include "bluetooth_gatt.h"
53 #include "btstack_debug.h"
54 #include "btstack_event.h"
55 #include "btstack_run_loop.h"
56 #include "gap.h"
57 
58 static btstack_linked_list_t clients;
59 static uint16_t hids_cid_counter = 0;
60 
61 #define HID_REPORT_MODE_REPORT_ID               3
62 #define HID_REPORT_MODE_REPORT_MAP_ID           4
63 #define HID_REPORT_MODE_HID_INFORMATION_ID      5
64 #define HID_REPORT_MODE_HID_CONTROL_POINT_ID    6
65 
66 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
67 
68 static uint16_t hids_get_next_cid(void){
69     if (hids_cid_counter == 0xffff) {
70         hids_cid_counter = 1;
71     } else {
72         hids_cid_counter++;
73     }
74     return hids_cid_counter;
75 }
76 
77 static uint8_t find_report_index_for_value_handle(hids_client_t * client, uint16_t value_handle){
78     uint8_t i;
79     for (i = 0; i < client->num_reports; i++){
80         if (client->reports[i].value_handle == value_handle){
81             return i;
82         }
83     }
84     return HIDS_CLIENT_INVALID_REPORT_INDEX;
85 }
86 
87 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){
88     uint8_t i;
89     for (i = 0; i < client->num_reports; i++){
90         if ( (client->reports[i].report_id == report_id) && (client->reports[i].report_type == report_type)){
91             return i;
92         }
93     }
94     return HIDS_CLIENT_INVALID_REPORT_INDEX;
95 }
96 
97 static hids_client_report_t * find_report_for_report_id_and_type(hids_client_t * client, uint8_t report_id, hid_report_type_t report_type){
98     uint8_t i;
99     for (i = 0; i < client->num_reports; i++){
100         if ( (client->reports[i].report_id == report_id) && (client->reports[i].report_type == report_type)){
101             return &client->reports[i];
102         }
103     }
104     return NULL;
105 }
106 
107 static hids_client_report_t * get_boot_mouse_input_report(hids_client_t * client){
108     return find_report_for_report_id_and_type(client, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT);
109 }
110 static hids_client_report_t * get_boot_keyboard_input_report(hids_client_t * client){
111     return find_report_for_report_id_and_type(client, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT);
112 }
113 
114 static uint8_t hids_client_add_characteristic(hids_client_t * client, gatt_client_characteristic_t * characteristic, uint8_t report_id, hid_report_type_t report_type){
115     uint8_t report_index = find_report_index_for_value_handle(client, characteristic->value_handle);
116     if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
117         return report_index;
118     }
119     report_index = client->num_reports;
120 
121     if (report_index < HIDS_CLIENT_NUM_REPORTS) {
122         client->reports[report_index].value_handle = characteristic->value_handle;
123         client->reports[report_index].end_handle = characteristic->end_handle;
124         client->reports[report_index].properties = characteristic->properties;
125 
126         client->reports[report_index].service_index = client->service_index;
127         client->reports[report_index].report_id = report_id;
128         client->reports[report_index].report_type = report_type;
129 
130         // log_info("add index %d, id %d, type %d, value handle 0x%02x", report_index, report_id, report_type, characteristic->value_handle);
131         log_info("add index %d, id %d, type %d, value handle 0x%02x", report_index, report_id, report_type, characteristic->value_handle);
132         client->num_reports++;
133         return report_index;
134     } else {
135         log_info("not enough storage, increase HIDS_CLIENT_NUM_REPORTS");
136         return HIDS_CLIENT_INVALID_REPORT_INDEX;
137     }
138 }
139 
140 static void hids_client_get_characteristic_for_report_index(hids_client_t * client, uint8_t report_index, gatt_client_characteristic_t * characteristic){
141     characteristic->value_handle = client->reports[report_index].value_handle;
142     characteristic->end_handle = client->reports[report_index].end_handle;
143     characteristic->properties = client->reports[report_index].properties;
144 }
145 
146 static uint8_t hids_client_get_characteristic(hids_client_t * client, uint8_t report_id, hid_report_type_t report_type, gatt_client_characteristic_t * characteristic){
147     uint8_t report_index = find_report_index_for_report_id_and_type(client, report_id, report_type);
148     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
149         return report_index;
150     }
151     hids_client_get_characteristic_for_report_index(client, report_index, characteristic);
152     return report_index;
153 }
154 
155 static uint8_t hids_client_get_next_active_report_map_index(hids_client_t * client){
156     uint8_t i;
157     uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX;
158 
159     for (i = client->service_index; i < client->num_instances; i++){
160         if (client->services[i].report_map_value_handle != 0){
161             index = i;
162             break;
163         }
164     }
165     client->service_index = index;
166     return index;
167 }
168 
169 static bool hids_client_report_query_next_report_map(hids_client_t * client){
170     client->service_index++;
171     if (hids_client_get_next_active_report_map_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
172         client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_DISCOVER_CHARACTERISTIC_DESCRIPTORS;
173         return true;
174     }
175     return false;
176 }
177 
178 static bool hids_client_report_map_query_init(hids_client_t * client){
179     client->service_index = 0;
180 
181     if (hids_client_get_next_active_report_map_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
182         client->state = HIDS_CLIENT_STATE_W2_READ_REPORT_MAP_HID_DESCRIPTOR;
183         return true;
184     }
185     return false;
186 }
187 
188 
189 static uint8_t hids_client_get_next_active_report_map_uuid_index(hids_client_t * client){
190     uint8_t i;
191     uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX;
192 
193     for (i = client->active_index; i < client->num_reports; i++){
194         hids_client_report_t report = client->reports[i];
195         if (report.report_type == HID_REPORT_TYPE_RESERVED && report.report_id == HID_REPORT_MODE_REPORT_MAP_ID){
196             index = i;
197             break;
198         }
199     }
200     client->active_index = index;
201     return index;
202 }
203 
204 static bool hids_client_report_query_next_report_map_uuid(hids_client_t * client){
205     client->active_index++;
206     if (hids_client_get_next_active_report_map_uuid_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
207         client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID;
208         return true;
209     }
210     return false;
211 }
212 
213 
214 
215 static bool hids_client_report_map_uuid_query_init(hids_client_t * client){
216     client->active_index = 0;
217 
218     if (hids_client_get_next_active_report_map_uuid_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
219         client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID;
220         return true;
221     }
222     return false;
223 }
224 
225 static uint8_t hids_client_get_next_report_index(hids_client_t * client){
226     uint8_t i;
227     uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX;
228     for (i = client->active_index; i < client->num_reports; i++){
229         hids_client_report_t report = client->reports[i];
230         if (report.report_type == HID_REPORT_TYPE_RESERVED && report.report_id == HID_REPORT_MODE_REPORT_ID){
231             index = i;
232             break;
233         }
234     }
235     client->active_index = index;
236     return index;
237 }
238 
239 static bool hids_client_report_query_next_report(hids_client_t * client){
240     client->active_index++;
241     if (hids_client_get_next_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
242         client->state = HIDS_CLIENT_STATE_W2_FIND_REPORT;
243         return true;
244     }
245     return false;
246 }
247 
248 static bool hids_client_report_query_init(hids_client_t * client){
249     client->service_index = 0;
250     client->active_index = 0;
251 
252     if (hids_client_get_next_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
253         client->state = HIDS_CLIENT_STATE_W2_FIND_REPORT;
254         return true;
255     }
256     return false;
257 }
258 
259 static hids_client_t * hids_create_client(hci_con_handle_t con_handle, uint16_t cid){
260     hids_client_t * client = btstack_memory_hids_client_get();
261     if (!client){
262         log_error("Not enough memory to create client");
263         return NULL;
264     }
265     client->state = HIDS_CLIENT_STATE_IDLE;
266     client->cid = cid;
267     client->con_handle = con_handle;
268 
269     btstack_linked_list_add(&clients, (btstack_linked_item_t *) client);
270     return client;
271 }
272 
273 static void hids_finalize_client(hids_client_t * client){
274     btstack_linked_list_remove(&clients, (btstack_linked_item_t *) client);
275     btstack_memory_hids_client_free(client);
276 }
277 
278 static hids_client_t * hids_get_client_for_con_handle(hci_con_handle_t con_handle){
279     btstack_linked_list_iterator_t it;
280     btstack_linked_list_iterator_init(&it, &clients);
281     while (btstack_linked_list_iterator_has_next(&it)){
282         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
283         if (client->con_handle != con_handle) continue;
284         return client;
285     }
286     return NULL;
287 }
288 
289 static hids_client_t * hids_get_client_for_cid(uint16_t hids_cid){
290     btstack_linked_list_iterator_t it;
291     btstack_linked_list_iterator_init(&it, &clients);
292     while (btstack_linked_list_iterator_has_next(&it)){
293         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
294         if (client->cid != hids_cid) continue;
295         return client;
296     }
297     return NULL;
298 }
299 
300 static void hids_emit_connection_established(hids_client_t * client, uint8_t status){
301     uint8_t event[8];
302     int pos = 0;
303     event[pos++] = HCI_EVENT_GATTSERVICE_META;
304     event[pos++] = sizeof(event) - 2;
305     event[pos++] = GATTSERVICE_SUBEVENT_HID_SERVICE_CONNECTED;
306     little_endian_store_16(event, pos, client->cid);
307     pos += 2;
308     event[pos++] = status;
309     event[pos++] = client->protocol_mode;
310     event[pos++] = client->num_instances;
311     (*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
312 }
313 
314 
315 static void hids_run_for_client(hids_client_t * client){
316     uint8_t att_status;
317     gatt_client_service_t service;
318     gatt_client_characteristic_t characteristic;
319     uint8_t report_index;
320 
321     switch (client->state){
322         case HIDS_CLIENT_STATE_W2_QUERY_SERVICE:
323             client->state = HIDS_CLIENT_STATE_W4_SERVICE_RESULT;
324             att_status = gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, client->con_handle, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE);
325             // TODO handle status
326             UNUSED(att_status);
327             break;
328 
329         case HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC:
330             client->state = HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT;
331 
332             service.start_group_handle = client->services[client->service_index].start_handle;
333             service.end_group_handle = client->services[client->service_index].end_handle;
334             att_status = gatt_client_discover_characteristics_for_service(&handle_gatt_client_event, client->con_handle, &service);
335 
336             UNUSED(att_status);
337             break;
338 
339         case HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD:
340             client->state = HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED;
341 
342             report_index = hids_client_get_characteristic(client, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT, &characteristic);
343             if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
344                 att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
345             }
346 
347             if ((report_index == HIDS_CLIENT_INVALID_REPORT_INDEX) || (att_status != ERROR_CODE_SUCCESS)){
348                 client->reports[report_index].value_handle = 0;
349                 client->state = HIDS_CLIENT_STATE_CONNECTED;
350             }
351             break;
352 
353         case HIDS_CLIENT_STATE_W2_ENABLE_MOUSE:
354             client->state = HIDS_CLIENT_STATE_W4_MOUSE_ENABLED;
355 
356             report_index = hids_client_get_characteristic(client, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT, &characteristic);
357 
358             if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
359                 att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
360             }
361 
362             if ((report_index == HIDS_CLIENT_INVALID_REPORT_INDEX) || (att_status != ERROR_CODE_SUCCESS)){
363                 client->reports[report_index].value_handle = 0;
364                 client->state = HIDS_CLIENT_STATE_CONNECTED;
365             }
366 
367             break;
368 
369         case HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE:
370             client->state = HIDS_CLIENT_STATE_W4_SET_PROTOCOL_MODE;
371             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);
372             UNUSED(att_status);
373 
374             client->protocol_mode = client->required_protocol_mode;
375             client->state = HIDS_CLIENT_STATE_CONNECTED;
376             hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
377             break;
378 
379         case HIDS_CLIENT_W2_SEND_REPORT:{
380             client->state = HIDS_CLIENT_STATE_CONNECTED;
381 
382             att_status = gatt_client_write_value_of_characteristic_without_response(client->con_handle,
383                 client->reports[client->active_index].value_handle,
384                 client->report_len, (uint8_t *)client->report);
385             UNUSED(att_status);
386             break;
387         }
388 
389         case HIDS_CLIENT_STATE_W2_READ_REPORT_MAP_HID_DESCRIPTOR:
390             client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_HID_DESCRIPTOR;
391             att_status = gatt_client_read_value_of_characteristic_using_value_handle(&handle_gatt_client_event, client->con_handle, client->services[client->service_index].report_map_value_handle);
392             UNUSED(att_status);
393             break;
394 
395         case HIDS_CLIENT_STATE_W2_REPORT_MAP_DISCOVER_CHARACTERISTIC_DESCRIPTORS:
396             client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTORS_RESULT;
397 
398             characteristic.value_handle = client->services[client->service_index].report_map_value_handle;
399             characteristic.end_handle = client->services[client->service_index].report_map_end_handle;
400 
401             att_status = gatt_client_discover_characteristic_descriptors(&handle_gatt_client_event, client->con_handle, &characteristic);
402             UNUSED(att_status);
403             break;
404 
405         case HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID:
406             client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_EXTERNAL_REPORT_REFERENCE_UUID;
407 
408             att_status = gatt_client_read_characteristic_descriptor_using_descriptor_handle(&handle_gatt_client_event, client->con_handle, client->reports[client->active_index].value_handle);
409             UNUSED(att_status);
410             break;
411 
412         case HIDS_CLIENT_STATE_W2_DISCOVER_EXTERNAL_REPORT_CHARACTERISTIC:
413             client->state = HIDS_CLIENT_STATE_W4_EXTERNAL_REPORT_CHARACTERISTIC_RESULT;
414 
415             service.start_group_handle = 0x0001;
416             service.end_group_handle = 0xffff;
417             att_status = gatt_client_discover_characteristics_for_service(&handle_gatt_client_event, client->con_handle, &service);
418             UNUSED(att_status);
419             break;
420 
421         case HIDS_CLIENT_STATE_W2_FIND_REPORT:
422             client->state = HIDS_CLIENT_STATE_W4_REPORT_FOUND;
423             client->descriptor_handle = 0;
424             hids_client_get_characteristic_for_report_index(client, client->active_index, &characteristic);
425             att_status = gatt_client_discover_characteristic_descriptors(&handle_gatt_client_event, client->con_handle, &characteristic);
426             UNUSED(att_status);
427             break;
428 
429         case HIDS_CLIENT_STATE_W2_READ_REPORT_ID_AND_TYPE:
430             client->state = HIDS_CLIENT_STATE_W4_REPORT_ID_AND_TYPE;
431 
432             att_status = gatt_client_read_characteristic_descriptor_using_descriptor_handle(&handle_gatt_client_event, client->con_handle, client->descriptor_handle);
433             client->descriptor_handle = 0;
434             UNUSED(att_status);
435             break;
436 
437         default:
438             break;
439     }
440 }
441 
442 static void hids_client_setup_report_event(hids_client_t * client, uint8_t report_id, uint8_t *buffer, uint16_t report_len){
443     uint16_t pos = 0;
444     buffer[pos++] = HCI_EVENT_GATTSERVICE_META;
445     pos++;  // skip len
446     buffer[pos++] = GATTSERVICE_SUBEVENT_HID_REPORT;
447     little_endian_store_16(buffer, pos, client->cid);
448     pos += 2;
449     buffer[pos++] = report_id;
450     little_endian_store_16(buffer, pos, report_len);
451     pos += 2;
452     buffer[1] = pos + report_len - 2;
453 }
454 
455 static void handle_boot_keyboard_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
456     UNUSED(packet_type);
457     UNUSED(channel);
458     UNUSED(size);
459 
460     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
461 
462     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
463     btstack_assert(client != NULL);
464 
465     uint8_t * in_place_event = packet;
466     hids_client_setup_report_event(client, HID_BOOT_MODE_KEYBOARD_ID, in_place_event, gatt_event_notification_get_value_length(packet));
467     (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
468 }
469 
470 static void handle_boot_mouse_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
471     UNUSED(packet_type);
472     UNUSED(channel);
473     UNUSED(size);
474 
475     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
476 
477     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
478     btstack_assert(client != NULL);
479 
480     uint8_t * in_place_event = packet;
481     hids_client_setup_report_event(client, HID_BOOT_MODE_MOUSE_ID, in_place_event, gatt_event_notification_get_value_length(packet));
482     (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
483 }
484 
485 
486 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
487     UNUSED(packet_type);
488     UNUSED(channel);
489     UNUSED(size);
490 
491     hids_client_t * client = NULL;
492     uint8_t att_status;
493     gatt_client_service_t service;
494     gatt_client_characteristic_t characteristic;
495     gatt_client_characteristic_descriptor_t characteristic_descriptor;
496 
497     hids_client_report_t * boot_keyboard_report;
498     hids_client_report_t * boot_mouse_report;
499     const uint8_t * characteristic_descriptor_value;
500     uint8_t i;
501     uint8_t report_index;
502 
503     switch(hci_event_packet_get_type(packet)){
504         case GATT_EVENT_SERVICE_QUERY_RESULT:
505             client = hids_get_client_for_con_handle(gatt_event_service_query_result_get_handle(packet));
506             btstack_assert(client != NULL);
507 
508             if (client->state != HIDS_CLIENT_STATE_W4_SERVICE_RESULT) {
509                 hids_emit_connection_established(client, GATT_CLIENT_IN_WRONG_STATE);
510                 hids_finalize_client(client);
511                 break;
512             }
513 
514             if (client->num_instances < MAX_NUM_HID_SERVICES){
515                 gatt_event_service_query_result_get_service(packet, &service);
516                 client->services[client->num_instances].start_handle = service.start_group_handle;
517                 client->services[client->num_instances].end_handle = service.end_group_handle;
518             }
519             client->num_instances++;
520             break;
521 
522         case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
523             client = hids_get_client_for_con_handle(gatt_event_characteristic_query_result_get_handle(packet));
524             btstack_assert(client != NULL);
525             gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
526 
527             switch (client->state){
528                 case HIDS_CLIENT_STATE_W4_EXTERNAL_REPORT_CHARACTERISTIC_RESULT:
529                     // update value handle od external reports, set type to reserved so that will be inlcuded in ID,TYPE update in hte next step
530                     for (i = 0; i < client->num_reports; i++){
531                         if (client->reports[i].external_report_reference_uuid == characteristic.uuid16){
532                             client->reports[i].report_id = HID_REPORT_MODE_REPORT_ID;
533                             client->reports[i].report_type = HID_REPORT_TYPE_RESERVED;
534                             client->reports[i].properties = characteristic.properties;
535                             client->reports[i].value_handle = characteristic.value_handle;
536                             client->reports[i].end_handle = characteristic.end_handle;
537                         }
538                     }
539                     break;
540 
541                 default:
542                     switch (characteristic.uuid16){
543                         case ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE:
544                             client->protocol_mode_value_handle = characteristic.value_handle;
545                             break;
546 
547                         case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT:
548                             hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT);
549                             break;
550 
551                         case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT:
552                             hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT);
553                             break;
554 
555                         case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT:
556                             hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_OUTPUT);
557                             break;
558 
559                         case ORG_BLUETOOTH_CHARACTERISTIC_REPORT:
560                             hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_ID, HID_REPORT_TYPE_RESERVED);
561                             break;
562 
563                         case ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP:
564                             client->services[client->service_index].report_map_value_handle = characteristic.value_handle;
565                             client->services[client->service_index].report_map_end_handle = characteristic.end_handle;
566                             break;
567 
568                         case ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION:
569                             // printf("ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION\n");
570                             hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_HID_INFORMATION_ID, HID_REPORT_TYPE_INPUT);
571                             break;
572 
573                         case ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT:
574                             // printf("ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT\n");
575                             hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_HID_CONTROL_POINT_ID, HID_REPORT_TYPE_INPUT);
576                             break;
577                         default:
578                             break;
579                 }
580             }
581 
582             break;
583 
584         case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT:
585             // Map Report characteristic value == HID Descriptor
586             client = hids_get_client_for_con_handle(gatt_event_characteristic_value_query_result_get_handle(packet));
587             btstack_assert(client != NULL);
588             // TODO get value and store it
589             // printf("HID descriptor found\n");
590             break;
591 
592         case GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT:
593             client = hids_get_client_for_con_handle(gatt_event_all_characteristic_descriptors_query_result_get_handle(packet));
594             btstack_assert(client != NULL);
595             gatt_event_all_characteristic_descriptors_query_result_get_characteristic_descriptor(packet, &characteristic_descriptor);
596 
597             switch (client->state) {
598                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTORS_RESULT:
599                     // setup for descriptor value query
600                     if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_EXTERNAL_REPORT_REFERENCE){
601                         report_index = hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_MAP_ID, HID_REPORT_TYPE_RESERVED);
602 
603                         if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
604                             client->reports[report_index].value_handle = characteristic_descriptor.handle;
605                             client->reports[report_index].service_index = client->service_index;
606                         }
607 
608                     }
609                     break;
610                 case HIDS_CLIENT_STATE_W4_REPORT_FOUND:
611                     // setup for descriptor value query
612                     if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_REPORT_REFERENCE){
613                         client->descriptor_handle = characteristic_descriptor.handle;
614                     }
615                     break;
616                 default:
617                     break;
618             }
619             break;
620 
621         case GATT_EVENT_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT:
622             client = hids_get_client_for_con_handle(gatt_event_characteristic_descriptor_query_result_get_handle(packet));
623             btstack_assert(client != NULL);
624 
625             if (gatt_event_characteristic_descriptor_query_result_get_descriptor_length(packet) != 2){
626                 break;
627             }
628 
629             characteristic_descriptor_value = gatt_event_characteristic_descriptor_query_result_get_descriptor(packet);
630             switch (client->state) {
631                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_EXTERNAL_REPORT_REFERENCE_UUID:
632                     // get external report characteristic uuid
633                     report_index = find_report_index_for_value_handle(client, gatt_event_characteristic_descriptor_query_result_get_descriptor_handle(packet));
634                     if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
635                         client->reports[report_index].external_report_reference_uuid = little_endian_read_16(characteristic_descriptor_value, 0);
636                     }
637                     break;
638 
639                 case HIDS_CLIENT_STATE_W4_REPORT_ID_AND_TYPE:
640                     client->reports[client->active_index].report_id = characteristic_descriptor_value[0];
641                     client->reports[client->active_index].report_type = (hid_report_type_t)characteristic_descriptor_value[1];
642                     log_info("update index %d, id %d, type %d, value handle 0x%02x",
643                         client->active_index,
644                         client->reports[client->active_index].report_id,
645                         client->reports[client->active_index].report_type,
646                         client->reports[client->active_index].value_handle);
647 
648                     break;
649 
650                 default:
651                     break;
652             }
653             break;
654 
655         case GATT_EVENT_QUERY_COMPLETE:
656             client = hids_get_client_for_con_handle(gatt_event_query_complete_get_handle(packet));
657             btstack_assert(client != NULL);
658 
659             att_status = gatt_event_query_complete_get_att_status(packet);
660 
661             switch (client->state){
662                 case HIDS_CLIENT_STATE_W4_SERVICE_RESULT:
663                     if (att_status != ATT_ERROR_SUCCESS){
664                         hids_emit_connection_established(client, att_status);
665                         hids_finalize_client(client);
666                         break;
667                     }
668 
669                     if (client->num_instances == 0){
670                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
671                         hids_finalize_client(client);
672                         break;
673                     }
674 
675                     if (client->num_instances > MAX_NUM_HID_SERVICES) {
676                         log_info("%d hid services found, only first %d can be stored, increase MAX_NUM_HID_SERVICES", client->num_instances, MAX_NUM_HID_SERVICES);
677                         client->num_instances = MAX_NUM_HID_SERVICES;
678                     }
679 
680                     client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
681                     client->service_index = 0;
682                     break;
683 
684                 case HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT:
685                     if (att_status != ATT_ERROR_SUCCESS){
686                         hids_emit_connection_established(client, att_status);
687                         hids_finalize_client(client);
688                         break;
689                     }
690 
691                     switch (client->required_protocol_mode){
692                         case HID_PROTOCOL_MODE_BOOT:
693                             if (get_boot_keyboard_input_report(client) != NULL){
694                                 client->state = HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD;
695                                 break;
696                             }
697                             if (get_boot_mouse_input_report(client) != NULL){
698                                 client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
699                                 break;
700                             }
701                             hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
702                             hids_finalize_client(client);
703                             break;
704 
705                         case HID_PROTOCOL_MODE_REPORT:
706                             if ((client->service_index + 1) < client->num_instances){
707                                 // discover characteristics of next service
708                                 client->service_index++;
709                                 client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
710                             } else {
711 
712                                 // 1. we need to get HID Descriptor and
713                                 // 2. get external Report characteristics if referenced from Report Map
714                                 if (hids_client_report_map_query_init(client)){
715                                     break;
716                                 }
717                                 hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
718                                 hids_finalize_client(client);
719                             }
720                             break;
721                         default:
722                             break;
723                     }
724                     break;
725 
726 
727                 // HID descriptor found
728                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_HID_DESCRIPTOR:
729                     if (att_status != ATT_ERROR_SUCCESS){
730                         hids_emit_connection_established(client, att_status);
731                         hids_finalize_client(client);
732                         break;
733                     }
734                     client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_DISCOVER_CHARACTERISTIC_DESCRIPTORS;
735                     break;
736 
737                 // found all descriptors, check if there is one with EXTERNAL_REPORT_REFERENCE
738                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTORS_RESULT:
739                     // go for next report map
740                     if (hids_client_report_query_next_report_map(client)){
741                         break;
742                     }
743 
744                     // read UUIDS for external characteristics
745                     if (hids_client_report_map_uuid_query_init(client)){
746                         break;
747                     }
748 
749                     // discover characteristic descriptor for all Report characteristics,
750                     // then read value of characteristic descriptor to get Report ID
751                     if (hids_client_report_query_init(client)){
752                         break;
753                     }
754 
755                     hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
756                     hids_finalize_client(client);
757                     break;
758 
759                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_EXTERNAL_REPORT_REFERENCE_UUID:
760                     // go for next map report
761                     if (hids_client_report_query_next_report_map_uuid(client)){
762                         break;
763                     }
764 
765                     // update external characteristics with correct value handle and end handle
766                     client->state = HIDS_CLIENT_STATE_W2_DISCOVER_EXTERNAL_REPORT_CHARACTERISTIC;
767                     break;
768 
769                 case HIDS_CLIENT_STATE_W4_EXTERNAL_REPORT_CHARACTERISTIC_RESULT:
770 
771                     // discover characteristic descriptor for all Report characteristics,
772                     // then read value of characteristic descriptor to get Report ID
773                     if (hids_client_report_query_init(client)){
774                         break;
775                     }
776 
777                     hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
778                     hids_finalize_client(client);
779                     break;
780 
781                 case HIDS_CLIENT_STATE_W4_REPORT_FOUND:
782                     if (client->descriptor_handle != 0){
783                         // descriptor found
784                         client->state = HIDS_CLIENT_STATE_W2_READ_REPORT_ID_AND_TYPE;
785                         break;
786                     }
787 
788                     // go for next report
789                     if (hids_client_report_query_next_report(client)){
790                         break;
791                     }
792 
793                     // TODO continue with report mode
794                     hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
795                     break;
796 
797                 case HIDS_CLIENT_STATE_W4_REPORT_ID_AND_TYPE:
798                     // go for next report
799                     if (hids_client_report_query_next_report(client)){
800                         break;
801                     }
802 
803                     // TODO continue with report mode
804                     hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
805                     break;
806 
807                 case HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED:
808                     boot_keyboard_report = get_boot_keyboard_input_report(client);
809                     if (boot_keyboard_report != NULL){
810                         // setup listener
811                         characteristic.value_handle = boot_keyboard_report->value_handle;
812                         gatt_client_listen_for_characteristic_value_updates(&boot_keyboard_report->notifications, &handle_boot_keyboard_hid_event, client->con_handle, &characteristic);
813                     }
814 
815                     // check if there is mouse input report
816                     boot_mouse_report = get_boot_mouse_input_report(client);
817                     if (boot_mouse_report != NULL){
818                         client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
819                         break;
820                     }
821 
822                     // if none of the reports is found bail out
823                     if (!boot_mouse_report && !boot_keyboard_report){
824                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
825                         hids_finalize_client(client);
826                         break;
827                     }
828 
829                     // set protocol
830                     if (client->protocol_mode_value_handle != 0){
831                         client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
832                     } else {
833                         client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
834                         client->state = HIDS_CLIENT_STATE_CONNECTED;
835                         hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
836                     }
837                     hids_run_for_client(client);
838                     break;
839 
840                 case HIDS_CLIENT_STATE_W4_MOUSE_ENABLED:
841                     boot_mouse_report = get_boot_mouse_input_report(client);
842                     if (boot_mouse_report != NULL){
843                         // setup listener
844                         characteristic.value_handle = boot_mouse_report->value_handle;
845                         gatt_client_listen_for_characteristic_value_updates(&boot_mouse_report->notifications, &handle_boot_mouse_hid_event, client->con_handle, &characteristic);
846                     }
847 
848                     boot_keyboard_report = get_boot_keyboard_input_report(client);
849                     if (!boot_mouse_report && !boot_keyboard_report){
850                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
851                         hids_finalize_client(client);
852                         break;
853                     }
854 
855                     if (client->protocol_mode_value_handle != 0){
856                         client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
857                     } else {
858                         client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
859                         client->state = HIDS_CLIENT_STATE_CONNECTED;
860                         hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
861                     }
862                     break;
863                 default:
864                     break;
865             }
866             break;
867 
868         default:
869             break;
870     }
871 
872     if (client != NULL){
873         hids_run_for_client(client);
874     }
875 }
876 
877 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){
878     btstack_assert(packet_handler != NULL);
879 
880     hids_client_t * client = hids_get_client_for_con_handle(con_handle);
881     if (client != NULL){
882         return ERROR_CODE_COMMAND_DISALLOWED;
883     }
884 
885     uint16_t cid = hids_get_next_cid();
886     if (hids_cid != NULL) {
887         *hids_cid = cid;
888     }
889 
890     client = hids_create_client(con_handle, cid);
891     if (client == NULL) {
892         return BTSTACK_MEMORY_ALLOC_FAILED;
893     }
894 
895     client->required_protocol_mode = protocol_mode;
896     client->client_handler = packet_handler;
897     client->state = HIDS_CLIENT_STATE_W2_QUERY_SERVICE;
898 
899     hids_run_for_client(client);
900     return ERROR_CODE_SUCCESS;
901 }
902 
903 uint8_t hids_client_disconnect(uint16_t hids_cid){
904     hids_client_t * client = hids_get_client_for_cid(hids_cid);
905     if (client == NULL){
906         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
907     }
908     // finalize connection
909     hids_finalize_client(client);
910     return ERROR_CODE_SUCCESS;
911 }
912 
913 uint8_t hids_client_send_report(uint16_t hids_cid, uint8_t report_id, const uint8_t * report, uint8_t report_len){
914     hids_client_t * client = hids_get_client_for_cid(hids_cid);
915     if (client == NULL){
916         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
917     }
918 
919     if (client->state != HIDS_CLIENT_STATE_CONNECTED) {
920         return ERROR_CODE_COMMAND_DISALLOWED;
921     }
922 
923     uint8_t report_index = find_report_index_for_report_id_and_type(client, report_id, HID_REPORT_TYPE_OUTPUT);
924     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
925         return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE;
926     }
927 
928     uint16_t mtu;
929     uint8_t status = gatt_client_get_mtu(client->con_handle, &mtu);
930 
931     if (status != ERROR_CODE_SUCCESS){
932         return status;
933     }
934 
935     if (mtu - 2 < report_len){
936         return ERROR_CODE_PARAMETER_OUT_OF_MANDATORY_RANGE;
937     }
938 
939     client->state = HIDS_CLIENT_W2_SEND_REPORT;
940     client->active_index = report_index;
941     client->report = report;
942     client->report_len = report_len;
943 
944     hids_run_for_client(client);
945     return ERROR_CODE_SUCCESS;
946 }
947 
948 void hids_client_init(void){}
949 
950 void hids_client_deinit(void){}
951