xref: /btstack/src/ble/gatt-service/hids_client.c (revision 7e1e6e7d327fa6a94e28f924ceff9042b4beca88)
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 void 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;
118     }
119 
120     if (client->active_index < HIDS_CLIENT_NUM_REPORTS) {
121         client->reports[client->active_index].value_handle = characteristic->value_handle;
122         client->reports[client->active_index].end_handle = characteristic->end_handle;
123         client->reports[client->active_index].properties = characteristic->properties;
124 
125         client->reports[client->active_index].service_index = client->service_index;
126         client->reports[client->active_index].report_id = report_id;
127         client->reports[client->active_index].report_type = report_type;
128 
129         // printf("add index %d, id %d, type %d, value handle 0x%02x\n", client->active_index, report_id, report_type, characteristic->value_handle);
130         client->active_index++;
131         client->num_reports++;
132     } else {
133         log_info("not enough storage, increase HIDS_CLIENT_NUM_REPORTS");
134     }
135 }
136 
137 static void hids_client_get_characteristic_for_report_index(hids_client_t * client, uint8_t report_index, gatt_client_characteristic_t * characteristic){
138     characteristic->value_handle = client->reports[report_index].value_handle;
139     characteristic->end_handle = client->reports[report_index].end_handle;
140     characteristic->properties = client->reports[report_index].properties;
141 }
142 
143 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){
144     uint8_t report_index = find_report_index_for_report_id_and_type(client, report_id, report_type);
145     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
146         return report_index;
147     }
148     hids_client_get_characteristic_for_report_index(client, report_index, characteristic);
149     return report_index;
150 }
151 
152 
153 static uint8_t hids_client_get_next_active_report_map_index(hids_client_t * client){
154     uint8_t i;
155     uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX;
156     for (i = client->active_index; i < client->num_reports; i++){
157         hids_client_report_t report = client->reports[i];
158         if (report.report_type == HID_REPORT_TYPE_RESERVED && report.report_id == HID_REPORT_MODE_REPORT_MAP_ID){
159             index = i;
160             break;
161         }
162     }
163     client->active_index = index;
164     return index;
165 }
166 
167 static bool hids_client_report_query_next_report_map(hids_client_t * client){
168     client->active_index++;
169     if (hids_client_get_next_active_report_map_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
170         client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_DISCOVER_CHARACTERISTIC_DESCRIPTORS;
171         return true;
172     }
173     return false;
174 }
175 
176 static bool hids_client_report_map_query_init(hids_client_t * client){
177     client->service_index = 0;
178     client->active_index = 0;
179 
180     if (hids_client_get_next_active_report_map_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
181         client->state = HIDS_CLIENT_STATE_W2_READ_REPORT_MAP_CHARACTERISTIC_VALUE;
182         return true;
183     }
184     return false;
185 }
186 
187 static uint8_t hids_client_get_next_report_index(hids_client_t * client){
188     uint8_t i;
189     uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX;
190     for (i = client->active_index; i < client->num_reports; i++){
191         hids_client_report_t report = client->reports[i];
192         if (report.report_type == HID_REPORT_TYPE_RESERVED && report.report_id == HID_REPORT_MODE_REPORT_ID){
193             index = i;
194             break;
195         }
196     }
197     client->active_index = index;
198     return index;
199 }
200 
201 static bool hids_client_report_query_next_report(hids_client_t * client){
202     client->active_index++;
203     if (hids_client_get_next_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
204         client->state = HIDS_CLIENT_STATE_W2_REPORT_QUERY_CHARACTERISTIC_DESCRIPTORS;
205         return true;
206     }
207     return false;
208 }
209 
210 static bool hids_client_report_query_init(hids_client_t * client){
211     client->service_index = 0;
212     client->active_index = 0;
213 
214     if (hids_client_get_next_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
215         client->state = HIDS_CLIENT_STATE_W2_REPORT_QUERY_CHARACTERISTIC_DESCRIPTORS;
216         return true;
217     }
218     return false;
219 }
220 
221 static hids_client_t * hids_create_client(hci_con_handle_t con_handle, uint16_t cid){
222     hids_client_t * client = btstack_memory_hids_client_get();
223     if (!client){
224         log_error("Not enough memory to create client");
225         return NULL;
226     }
227     client->state = HIDS_CLIENT_STATE_IDLE;
228     client->cid = cid;
229     client->con_handle = con_handle;
230 
231     btstack_linked_list_add(&clients, (btstack_linked_item_t *) client);
232     return client;
233 }
234 
235 static void hids_finalize_client(hids_client_t * client){
236     btstack_linked_list_remove(&clients, (btstack_linked_item_t *) client);
237     btstack_memory_hids_client_free(client);
238 }
239 
240 static hids_client_t * hids_get_client_for_con_handle(hci_con_handle_t con_handle){
241     btstack_linked_list_iterator_t it;
242     btstack_linked_list_iterator_init(&it, &clients);
243     while (btstack_linked_list_iterator_has_next(&it)){
244         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
245         if (client->con_handle != con_handle) continue;
246         return client;
247     }
248     return NULL;
249 }
250 
251 static hids_client_t * hids_get_client_for_cid(uint16_t hids_cid){
252     btstack_linked_list_iterator_t it;
253     btstack_linked_list_iterator_init(&it, &clients);
254     while (btstack_linked_list_iterator_has_next(&it)){
255         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
256         if (client->cid != hids_cid) continue;
257         return client;
258     }
259     return NULL;
260 }
261 
262 static void hids_emit_connection_established(hids_client_t * client, uint8_t status){
263     uint8_t event[8];
264     int pos = 0;
265     event[pos++] = HCI_EVENT_GATTSERVICE_META;
266     event[pos++] = sizeof(event) - 2;
267     event[pos++] = GATTSERVICE_SUBEVENT_HID_SERVICE_CONNECTED;
268     little_endian_store_16(event, pos, client->cid);
269     pos += 2;
270     event[pos++] = status;
271     event[pos++] = client->protocol_mode;
272     event[pos++] = client->num_instances;
273     (*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
274 }
275 
276 
277 static void hids_run_for_client(hids_client_t * client){
278     uint8_t att_status;
279     gatt_client_service_t service;
280     gatt_client_characteristic_t characteristic;
281     uint8_t report_index;
282 
283     switch (client->state){
284         case HIDS_CLIENT_STATE_W2_QUERY_SERVICE:
285             client->state = HIDS_CLIENT_STATE_W4_SERVICE_RESULT;
286             att_status = gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, client->con_handle, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE);
287             // TODO handle status
288             UNUSED(att_status);
289             break;
290 
291         case HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC:
292             client->state = HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT;
293 
294             service.start_group_handle = client->services[client->service_index].start_handle;
295             service.end_group_handle = client->services[client->service_index].end_handle;
296             att_status = gatt_client_discover_characteristics_for_service(&handle_gatt_client_event, client->con_handle, &service);
297 
298             UNUSED(att_status);
299             break;
300 
301         case HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD:
302             client->state = HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED;
303 
304             report_index = hids_client_get_characteristic(client, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT, &characteristic);
305             if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
306                 att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
307             }
308 
309             if ((report_index == HIDS_CLIENT_INVALID_REPORT_INDEX) || (att_status != ERROR_CODE_SUCCESS)){
310                 client->reports[report_index].value_handle = 0;
311                 client->state = HIDS_CLIENT_STATE_CONNECTED;
312             }
313             break;
314 
315         case HIDS_CLIENT_STATE_W2_ENABLE_MOUSE:
316             client->state = HIDS_CLIENT_STATE_W4_MOUSE_ENABLED;
317 
318             report_index = hids_client_get_characteristic(client, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT, &characteristic);
319 
320             if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
321                 att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
322             }
323 
324             if ((report_index == HIDS_CLIENT_INVALID_REPORT_INDEX) || (att_status != ERROR_CODE_SUCCESS)){
325                 client->reports[report_index].value_handle = 0;
326                 client->state = HIDS_CLIENT_STATE_CONNECTED;
327             }
328 
329             break;
330 
331         case HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE:
332             client->state = HIDS_CLIENT_STATE_W4_SET_PROTOCOL_MODE;
333             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);
334             UNUSED(att_status);
335 
336             client->protocol_mode = client->required_protocol_mode;
337             client->state = HIDS_CLIENT_STATE_CONNECTED;
338             hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
339             break;
340 
341         case HIDS_CLIENT_W2_SEND_REPORT:{
342             client->state = HIDS_CLIENT_STATE_CONNECTED;
343 
344             att_status = gatt_client_write_value_of_characteristic_without_response(client->con_handle,
345                 client->reports[client->active_index].value_handle,
346                 client->report_len, (uint8_t *)client->report);
347             UNUSED(att_status);
348             break;
349         }
350 
351         case HIDS_CLIENT_STATE_W2_READ_REPORT_MAP_CHARACTERISTIC_VALUE:
352             client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_VALUE_RESULT;
353 
354             att_status = gatt_client_read_value_of_characteristic_using_value_handle(&handle_gatt_client_event, client->con_handle, client->reports[client->active_index].value_handle);
355             UNUSED(att_status);
356             break;
357 
358         case HIDS_CLIENT_STATE_W2_REPORT_MAP_DISCOVER_CHARACTERISTIC_DESCRIPTORS:
359             client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTORS_RESULT;
360             client->descriptor_handle = 0;
361 
362             hids_client_get_characteristic_for_report_index(client, client->active_index, &characteristic);
363             att_status = gatt_client_discover_characteristic_descriptors(&handle_gatt_client_event, client->con_handle, &characteristic);
364             UNUSED(att_status);
365             break;
366 
367         case HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_CHARACTERISTIC_DESCRIPTOR_VALUE:
368             client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTOR_VALUE_RESULT;
369 
370             att_status = gatt_client_read_characteristic_descriptor_using_descriptor_handle(&handle_gatt_client_event, client->con_handle, client->descriptor_handle);
371             client->descriptor_handle = 0;
372             UNUSED(att_status);
373             break;
374 
375         case HIDS_CLIENT_STATE_W2_REPORT_MAP_DISCOVER_EXTERNAL_REPORT_CHARACTERISTIC:
376             client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_EXTERNAL_REPORT_CHARACTERISTIC_RESULT;
377             // printf("discover external uuid 0x%02x\n", client->reports[client->active_index].external_report_reference_uuid);
378 
379             att_status = gatt_client_discover_characteristics_for_handle_range_by_uuid16(&handle_gatt_client_event, client->con_handle, 0, 0xffff,
380                 client->reports[client->active_index].external_report_reference_uuid);
381             UNUSED(att_status);
382             break;
383 
384         case HIDS_CLIENT_STATE_W2_REPORT_QUERY_CHARACTERISTIC_DESCRIPTORS:
385             client->state = HIDS_CLIENT_STATE_W4_REPORT_CHARACTERISTIC_DESCRIPTORS_RESULT;
386             client->descriptor_handle = 0;
387             hids_client_get_characteristic_for_report_index(client, client->active_index, &characteristic);
388             att_status = gatt_client_discover_characteristic_descriptors(&handle_gatt_client_event, client->con_handle, &characteristic);
389             UNUSED(att_status);
390             break;
391 
392         case HIDS_CLIENT_STATE_W2_REPORT_READ_CHARACTERISTIC_DESCRIPTOR_VALUE:
393             client->state = HIDS_CLIENT_STATE_W4_REPORT_CHARACTERISTIC_DESCRIPTOR_VALUE_RESULT;
394 
395             att_status = gatt_client_read_characteristic_descriptor_using_descriptor_handle(&handle_gatt_client_event, client->con_handle, client->descriptor_handle);
396             client->descriptor_handle = 0;
397             UNUSED(att_status);
398             break;
399 
400         default:
401             break;
402     }
403 }
404 
405 static void hids_client_setup_report_event(hids_client_t * client, uint8_t report_id, uint8_t *buffer, uint16_t report_len){
406     uint16_t pos = 0;
407     buffer[pos++] = HCI_EVENT_GATTSERVICE_META;
408     pos++;  // skip len
409     buffer[pos++] = GATTSERVICE_SUBEVENT_HID_REPORT;
410     little_endian_store_16(buffer, pos, client->cid);
411     pos += 2;
412     buffer[pos++] = report_id;
413     little_endian_store_16(buffer, pos, report_len);
414     pos += 2;
415     buffer[1] = pos + report_len - 2;
416 }
417 
418 static void handle_boot_keyboard_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
419     UNUSED(packet_type);
420     UNUSED(channel);
421     UNUSED(size);
422 
423     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
424 
425     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
426     btstack_assert(client != NULL);
427 
428     uint8_t * in_place_event = packet;
429     hids_client_setup_report_event(client, HID_BOOT_MODE_KEYBOARD_ID, in_place_event, gatt_event_notification_get_value_length(packet));
430     (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
431 }
432 
433 static void handle_boot_mouse_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
434     UNUSED(packet_type);
435     UNUSED(channel);
436     UNUSED(size);
437 
438     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
439 
440     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
441     btstack_assert(client != NULL);
442 
443     uint8_t * in_place_event = packet;
444     hids_client_setup_report_event(client, HID_BOOT_MODE_MOUSE_ID, in_place_event, gatt_event_notification_get_value_length(packet));
445     (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
446 }
447 
448 
449 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
450     UNUSED(packet_type);
451     UNUSED(channel);
452     UNUSED(size);
453 
454     hids_client_t * client = NULL;
455     uint8_t att_status;
456     gatt_client_service_t service;
457     gatt_client_characteristic_t characteristic;
458     gatt_client_characteristic_descriptor_t characteristic_descriptor;
459 
460     hids_client_report_t * boot_keyboard_report;
461     hids_client_report_t * boot_mouse_report;
462     const uint8_t * characteristic_descriptor_value;
463 
464     switch(hci_event_packet_get_type(packet)){
465         case GATT_EVENT_SERVICE_QUERY_RESULT:
466             client = hids_get_client_for_con_handle(gatt_event_service_query_result_get_handle(packet));
467             btstack_assert(client != NULL);
468 
469             if (client->state != HIDS_CLIENT_STATE_W4_SERVICE_RESULT) {
470                 hids_emit_connection_established(client, GATT_CLIENT_IN_WRONG_STATE);
471                 hids_finalize_client(client);
472                 break;
473             }
474 
475             if (client->num_instances < MAX_NUM_HID_SERVICES){
476                 gatt_event_service_query_result_get_service(packet, &service);
477                 client->services[client->num_instances].start_handle = service.start_group_handle;
478                 client->services[client->num_instances].end_handle = service.end_group_handle;
479             }
480             client->num_instances++;
481             break;
482 
483         case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
484             client = hids_get_client_for_con_handle(gatt_event_characteristic_query_result_get_handle(packet));
485             btstack_assert(client != NULL);
486 
487             gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
488             switch (characteristic.uuid16){
489                 case ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE:
490                     client->protocol_mode_value_handle = characteristic.value_handle;
491                     break;
492 
493                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT:
494                     hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT);
495                     break;
496 
497                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT:
498                     hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT);
499                     break;
500 
501                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT:
502                     hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_OUTPUT);
503                     break;
504 
505                 case ORG_BLUETOOTH_CHARACTERISTIC_REPORT:
506                     // printf("ORG_BLUETOOTH_CHARACTERISTIC_REPORT\n");
507                     hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_ID, HID_REPORT_TYPE_RESERVED);
508                     break;
509 
510                 case ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP:
511                     // printf("ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP\n");
512                     hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_MAP_ID, HID_REPORT_TYPE_RESERVED);
513                     break;
514 
515                 case ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION:
516                     // printf("ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION\n");
517                     hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_HID_INFORMATION_ID, HID_REPORT_TYPE_INPUT);
518                     break;
519 
520                 case ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT:
521                     // printf("ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT\n");
522                     hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_HID_CONTROL_POINT_ID, HID_REPORT_TYPE_INPUT);
523                     break;
524                 default:
525                     break;
526             }
527             break;
528 
529         case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT:
530             // Map Report characteristic value == HID Descriptor
531             client = hids_get_client_for_con_handle(gatt_event_characteristic_value_query_result_get_handle(packet));
532             btstack_assert(client != NULL);
533             // TODO get value and store it
534             break;
535 
536         case GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT:
537             client = hids_get_client_for_con_handle(gatt_event_all_characteristic_descriptors_query_result_get_handle(packet));
538             btstack_assert(client != NULL);
539             gatt_event_all_characteristic_descriptors_query_result_get_characteristic_descriptor(packet, &characteristic_descriptor);
540 
541             switch (client->state) {
542                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTORS_RESULT:
543                     // setup for descriptor value query
544                     if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_EXTERNAL_REPORT_REFERENCE){
545                         client->descriptor_handle = characteristic_descriptor.handle;
546                     }
547                     break;
548                 case HIDS_CLIENT_STATE_W4_REPORT_CHARACTERISTIC_DESCRIPTORS_RESULT:
549                     // setup for descriptor value query
550                     if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_REPORT_REFERENCE){
551                         client->descriptor_handle = characteristic_descriptor.handle;
552                     }
553                     break;
554                 default:
555                     break;
556             }
557             break;
558 
559         case GATT_EVENT_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT:
560             client = hids_get_client_for_con_handle(gatt_event_characteristic_descriptor_query_result_get_handle(packet));
561             btstack_assert(client != NULL);
562 
563             if (gatt_event_characteristic_descriptor_query_result_get_descriptor_length(packet) != 2){
564                 break;
565             }
566 
567             characteristic_descriptor_value = gatt_event_characteristic_descriptor_query_result_get_descriptor(packet);
568 
569             switch (client->state) {
570                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTOR_VALUE_RESULT:
571                     // get external report characteristic uuid
572                     client->reports[client->active_index].external_report_reference_uuid = little_endian_read_16(characteristic_descriptor_value, 0);
573                     // printf("add external report characteristic uuid 0x%02x\n", client->reports[client->active_index].external_report_reference_uuid);
574                     break;
575 
576                 case HIDS_CLIENT_STATE_W4_REPORT_CHARACTERISTIC_DESCRIPTOR_VALUE_RESULT:
577                     client->reports[client->active_index].report_id = characteristic_descriptor_value[0];
578                     client->reports[client->active_index].report_type = (hid_report_type_t)characteristic_descriptor_value[1];
579                     // printf("update index %d, id %d, type %d, value handle 0x%02x\n",
580                     //     client->active_index,
581                     //     client->reports[client->active_index].report_id,
582                     //     client->reports[client->active_index].report_type,
583                     //     client->reports[client->active_index].value_handle);
584 
585                     break;
586 
587                 default:
588                     break;
589             }
590             break;
591 
592         case GATT_EVENT_QUERY_COMPLETE:
593             client = hids_get_client_for_con_handle(gatt_event_query_complete_get_handle(packet));
594             btstack_assert(client != NULL);
595 
596             att_status = gatt_event_query_complete_get_att_status(packet);
597 
598             switch (client->state){
599                 case HIDS_CLIENT_STATE_W4_SERVICE_RESULT:
600                     if (att_status != ATT_ERROR_SUCCESS){
601                         hids_emit_connection_established(client, att_status);
602                         hids_finalize_client(client);
603                         break;
604                     }
605 
606                     if (client->num_instances == 0){
607                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
608                         hids_finalize_client(client);
609                         break;
610                     }
611 
612                     if (client->num_instances > MAX_NUM_HID_SERVICES) {
613                         log_info("%d hid services found, only first %d can be stored, increase MAX_NUM_HID_SERVICES", client->num_instances, MAX_NUM_HID_SERVICES);
614                         client->num_instances = MAX_NUM_HID_SERVICES;
615                     }
616 
617                     client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
618                     client->service_index = 0;
619                     break;
620 
621                 case HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT:
622                     if (att_status != ATT_ERROR_SUCCESS){
623                         hids_emit_connection_established(client, att_status);
624                         hids_finalize_client(client);
625                         break;
626                     }
627 
628                     switch (client->required_protocol_mode){
629                         case HID_PROTOCOL_MODE_BOOT:
630                             if (get_boot_keyboard_input_report(client) != NULL){
631                                 client->state = HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD;
632                                 break;
633                             }
634                             if (get_boot_mouse_input_report(client) != NULL){
635                                 client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
636                                 break;
637                             }
638                             hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
639                             hids_finalize_client(client);
640                             break;
641 
642                         case HID_PROTOCOL_MODE_REPORT:
643                             if ((client->service_index + 1) < client->num_instances){
644                                 // discover characteristics of next service
645                                 client->service_index++;
646                                 client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
647                             } else {
648                                 // 1. we need to get HID Descriptor and
649                                 // 2. get external Report characteristics if referenced from Report Map
650                                 if (hids_client_report_map_query_init(client)){
651                                     break;
652                                 }
653                                 hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
654                                 hids_finalize_client(client);
655                             }
656                             break;
657                         default:
658                             break;
659                     }
660                     break;
661 
662 
663                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_VALUE_RESULT:
664                     if (att_status != ATT_ERROR_SUCCESS){
665                         hids_emit_connection_established(client, att_status);
666                         hids_finalize_client(client);
667                         break;
668                     }
669                     // printf("HID Descriptor found, check for external Report IDs (read report map descriptor)\n");
670                     client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_DISCOVER_CHARACTERISTIC_DESCRIPTORS;
671                     break;
672 
673                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTORS_RESULT:
674                     if (client->descriptor_handle != 0){
675                         client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_CHARACTERISTIC_DESCRIPTOR_VALUE;
676                         break;
677                     }
678 
679                     // go for next report map
680                     if (hids_client_report_query_next_report_map(client)){
681                         break;
682                     }
683 
684                     // discover characteristic descriptor for all Report characteristics,
685                     // then read value of characteristic descriptor to get Report ID
686                     if (hids_client_report_query_init(client)){
687                         break;
688                     }
689 
690                     hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
691                     hids_finalize_client(client);
692                     break;
693 
694                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTOR_VALUE_RESULT:
695                     if (client->descriptor_handle != 0){
696                         // descriptor found
697                         client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_DISCOVER_EXTERNAL_REPORT_CHARACTERISTIC;
698                         break;
699                     }
700 
701                     // go for next map report
702                     if (hids_client_report_query_next_report_map(client)){
703                         break;
704                     }
705 
706                     // discover characteristic descriptor for all Report characteristics,
707                     // then read value of characteristic descriptor to get Report ID
708                     if (hids_client_report_query_init(client)){
709                         break;
710                     }
711 
712                     hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
713                     hids_finalize_client(client);
714                     break;
715 
716                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_EXTERNAL_REPORT_CHARACTERISTIC_RESULT:
717                     // go for next map report
718                     if (hids_client_report_query_next_report_map(client)){
719                         break;
720                     }
721 
722                     // discover characteristic descriptor for all Report characteristics,
723                     // then read value of characteristic descriptor to get Report ID
724                     if (hids_client_report_query_init(client)){
725                         break;
726                     }
727 
728                     hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
729                     hids_finalize_client(client);
730                     break;
731 
732                 case HIDS_CLIENT_STATE_W4_REPORT_CHARACTERISTIC_DESCRIPTORS_RESULT:
733                     if (client->descriptor_handle != 0){
734                         // descriptor found
735                         client->state = HIDS_CLIENT_STATE_W2_REPORT_READ_CHARACTERISTIC_DESCRIPTOR_VALUE;
736                         break;
737                     }
738 
739                     // go for next report
740                     if (hids_client_report_query_next_report(client)){
741                         break;
742                     }
743 
744                     // TODO continue with report mode
745                     hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
746                     break;
747 
748                 case HIDS_CLIENT_STATE_W4_REPORT_CHARACTERISTIC_DESCRIPTOR_VALUE_RESULT:
749                     // go for next report
750                     if (hids_client_report_query_next_report(client)){
751                         break;
752                     }
753 
754                     // TODO continue with report mode
755                     hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
756                     break;
757 
758                 case HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED:
759                     boot_keyboard_report = get_boot_keyboard_input_report(client);
760                     if (boot_keyboard_report != NULL){
761                         // setup listener
762                         characteristic.value_handle = boot_keyboard_report->value_handle;
763                         gatt_client_listen_for_characteristic_value_updates(&boot_keyboard_report->notifications, &handle_boot_keyboard_hid_event, client->con_handle, &characteristic);
764                     }
765 
766                     // check if there is mouse input report
767                     boot_mouse_report = get_boot_mouse_input_report(client);
768                     if (boot_mouse_report != NULL){
769                         client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
770                         break;
771                     }
772 
773                     // if none of the reports is found bail out
774                     if (!boot_mouse_report && !boot_keyboard_report){
775                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
776                         hids_finalize_client(client);
777                         break;
778                     }
779 
780                     // set protocol
781                     if (client->protocol_mode_value_handle != 0){
782                         client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
783                     } else {
784                         client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
785                         client->state = HIDS_CLIENT_STATE_CONNECTED;
786                         hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
787                     }
788                     hids_run_for_client(client);
789                     break;
790 
791                 case HIDS_CLIENT_STATE_W4_MOUSE_ENABLED:
792                     boot_mouse_report = get_boot_mouse_input_report(client);
793                     if (boot_mouse_report != NULL){
794                         // setup listener
795                         characteristic.value_handle = boot_mouse_report->value_handle;
796                         gatt_client_listen_for_characteristic_value_updates(&boot_mouse_report->notifications, &handle_boot_mouse_hid_event, client->con_handle, &characteristic);
797                     }
798 
799                     boot_keyboard_report = get_boot_keyboard_input_report(client);
800                     if (!boot_mouse_report && !boot_keyboard_report){
801                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
802                         hids_finalize_client(client);
803                         break;
804                     }
805 
806                     if (client->protocol_mode_value_handle != 0){
807                         client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
808                     } else {
809                         client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
810                         client->state = HIDS_CLIENT_STATE_CONNECTED;
811                         hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
812                     }
813                     break;
814                 default:
815                     break;
816             }
817             break;
818 
819         default:
820             break;
821     }
822 
823     if (client != NULL){
824         hids_run_for_client(client);
825     }
826 }
827 
828 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){
829     btstack_assert(packet_handler != NULL);
830 
831     hids_client_t * client = hids_get_client_for_con_handle(con_handle);
832     if (client != NULL){
833         return ERROR_CODE_COMMAND_DISALLOWED;
834     }
835 
836     uint16_t cid = hids_get_next_cid();
837     if (hids_cid != NULL) {
838         *hids_cid = cid;
839     }
840 
841     client = hids_create_client(con_handle, cid);
842     if (client == NULL) {
843         return BTSTACK_MEMORY_ALLOC_FAILED;
844     }
845 
846     client->required_protocol_mode = protocol_mode;
847     client->client_handler = packet_handler;
848     client->state = HIDS_CLIENT_STATE_W2_QUERY_SERVICE;
849 
850     hids_run_for_client(client);
851     return ERROR_CODE_SUCCESS;
852 }
853 
854 uint8_t hids_client_disconnect(uint16_t hids_cid){
855     hids_client_t * client = hids_get_client_for_cid(hids_cid);
856     if (client == NULL){
857         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
858     }
859     // finalize connection
860     hids_finalize_client(client);
861     return ERROR_CODE_SUCCESS;
862 }
863 
864 uint8_t hids_client_send_report(uint16_t hids_cid, uint8_t report_id, const uint8_t * report, uint8_t report_len){
865     hids_client_t * client = hids_get_client_for_cid(hids_cid);
866     if (client == NULL){
867         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
868     }
869 
870     if (client->state != HIDS_CLIENT_STATE_CONNECTED) {
871         return ERROR_CODE_COMMAND_DISALLOWED;
872     }
873 
874     uint8_t report_index = find_report_index_for_report_id_and_type(client, report_id, HID_REPORT_TYPE_OUTPUT);
875     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
876         return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE;
877     }
878 
879     uint16_t mtu;
880     uint8_t status = gatt_client_get_mtu(client->con_handle, &mtu);
881 
882     if (status != ERROR_CODE_SUCCESS){
883         return status;
884     }
885 
886     if (mtu - 2 < report_len){
887         return ERROR_CODE_PARAMETER_OUT_OF_MANDATORY_RANGE;
888     }
889 
890     client->state = HIDS_CLIENT_W2_SEND_REPORT;
891     client->active_index = report_index;
892     client->report = report;
893     client->report_len = report_len;
894 
895     hids_run_for_client(client);
896     return ERROR_CODE_SUCCESS;
897 }
898 
899 void hids_client_init(void){}
900 
901 void hids_client_deinit(void){}
902