xref: /btstack/src/ble/gatt-service/hids_client.c (revision 70093cf595852d3bfef59c327cf1eb7b83be59ea)
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_report_index < HIDS_CLIENT_NUM_REPORTS) {
121         client->reports[client->active_report_index].value_handle = characteristic->value_handle;
122         client->reports[client->active_report_index].end_handle = characteristic->end_handle;
123         client->reports[client->active_report_index].properties = characteristic->properties;
124 
125         client->reports[client->active_report_index].service_index = client->service_index;
126         client->reports[client->active_report_index].report_id = report_id;
127         client->reports[client->active_report_index].report_type = report_type;
128 
129         client->active_report_index++;
130         client->num_reports++;
131     } else {
132         log_info("not enough storage, increase HIDS_CLIENT_NUM_REPORTS");
133     }
134 }
135 
136 static void hids_client_get_characteristic_for_report_index(hids_client_t * client, uint8_t report_index, gatt_client_characteristic_t * characteristic){
137     characteristic->value_handle = client->reports[report_index].value_handle;
138     characteristic->end_handle = client->reports[report_index].end_handle;
139     characteristic->properties = client->reports[report_index].properties;
140 }
141 
142 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){
143     uint8_t report_index = find_report_index_for_report_id_and_type(client, report_id, report_type);
144     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
145         return report_index;
146     }
147     hids_client_get_characteristic_for_report_index(client, report_index, characteristic);
148     return report_index;
149 }
150 
151 static void hids_client_get_next_active_report_index(hids_client_t * client){
152     uint8_t i;
153 
154     for (i = client->active_report_index; i < client->num_reports; i++){
155         hids_client_report_t report = client->reports[i];
156         if (report.report_type == HID_REPORT_TYPE_RESERVED && report.report_id == HID_REPORT_MODE_REPORT_ID){
157             client->active_report_index = i;
158         }
159         return;
160     }
161     client->active_report_index = HIDS_CLIENT_INVALID_REPORT_INDEX;
162 }
163 
164 static hids_client_t * hids_create_client(hci_con_handle_t con_handle, uint16_t cid){
165     hids_client_t * client = btstack_memory_hids_client_get();
166     if (!client){
167         log_error("Not enough memory to create client");
168         return NULL;
169     }
170     client->state = HIDS_CLIENT_STATE_IDLE;
171     client->cid = cid;
172     client->con_handle = con_handle;
173 
174     btstack_linked_list_add(&clients, (btstack_linked_item_t *) client);
175     return client;
176 }
177 
178 static void hids_finalize_client(hids_client_t * client){
179     btstack_linked_list_remove(&clients, (btstack_linked_item_t *) client);
180     btstack_memory_hids_client_free(client);
181 }
182 
183 static hids_client_t * hids_get_client_for_con_handle(hci_con_handle_t con_handle){
184     btstack_linked_list_iterator_t it;
185     btstack_linked_list_iterator_init(&it, &clients);
186     while (btstack_linked_list_iterator_has_next(&it)){
187         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
188         if (client->con_handle != con_handle) continue;
189         return client;
190     }
191     return NULL;
192 }
193 
194 static hids_client_t * hids_get_client_for_cid(uint16_t hids_cid){
195     btstack_linked_list_iterator_t it;
196     btstack_linked_list_iterator_init(&it, &clients);
197     while (btstack_linked_list_iterator_has_next(&it)){
198         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
199         if (client->cid != hids_cid) continue;
200         return client;
201     }
202     return NULL;
203 }
204 
205 static void hids_emit_connection_established(hids_client_t * client, uint8_t status){
206     uint8_t event[8];
207     int pos = 0;
208     event[pos++] = HCI_EVENT_GATTSERVICE_META;
209     event[pos++] = sizeof(event) - 2;
210     event[pos++] = GATTSERVICE_SUBEVENT_HID_SERVICE_CONNECTED;
211     little_endian_store_16(event, pos, client->cid);
212     pos += 2;
213     event[pos++] = status;
214     event[pos++] = client->protocol_mode;
215     event[pos++] = client->num_instances;
216     (*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
217 }
218 
219 
220 static void hids_run_for_client(hids_client_t * client){
221     uint8_t att_status;
222     gatt_client_service_t service;
223     gatt_client_characteristic_t characteristic;
224     uint8_t report_index;
225 
226     switch (client->state){
227         case HIDS_CLIENT_STATE_W2_QUERY_SERVICE:
228             client->state = HIDS_CLIENT_STATE_W4_SERVICE_RESULT;
229             att_status = gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, client->con_handle, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE);
230             // TODO handle status
231             UNUSED(att_status);
232             break;
233 
234         case HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC:
235             client->state = HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT;
236 
237             service.start_group_handle = client->services[client->service_index].start_handle;
238             service.end_group_handle = client->services[client->service_index].end_handle;
239             att_status = gatt_client_discover_characteristics_for_service(&handle_gatt_client_event, client->con_handle, &service);
240 
241             UNUSED(att_status);
242             break;
243 
244         case HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD:
245             client->state = HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED;
246 
247             report_index = hids_client_get_characteristic(client, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT, &characteristic);
248             if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
249                 att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
250             }
251 
252             if ((report_index == HIDS_CLIENT_INVALID_REPORT_INDEX) || (att_status != ERROR_CODE_SUCCESS)){
253                 client->reports[report_index].value_handle = 0;
254                 client->state = HIDS_CLIENT_STATE_CONNECTED;
255             }
256             break;
257 
258         case HIDS_CLIENT_STATE_W2_ENABLE_MOUSE:
259             client->state = HIDS_CLIENT_STATE_W4_MOUSE_ENABLED;
260 
261             report_index = hids_client_get_characteristic(client, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT, &characteristic);
262 
263             if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
264                 att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
265             }
266 
267             if ((report_index == HIDS_CLIENT_INVALID_REPORT_INDEX) || (att_status != ERROR_CODE_SUCCESS)){
268                 client->reports[report_index].value_handle = 0;
269                 client->state = HIDS_CLIENT_STATE_CONNECTED;
270             }
271 
272             break;
273 
274         case HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE:
275             client->state = HIDS_CLIENT_STATE_W4_SET_PROTOCOL_MODE;
276             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);
277             UNUSED(att_status);
278 
279             client->protocol_mode = client->required_protocol_mode;
280             client->state = HIDS_CLIENT_STATE_CONNECTED;
281             hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
282             break;
283 
284         case HIDS_CLIENT_W2_SEND_REPORT:{
285             client->state = HIDS_CLIENT_STATE_CONNECTED;
286 
287             att_status = gatt_client_write_value_of_characteristic_without_response(client->con_handle,
288                 client->reports[client->active_report_index].value_handle,
289                 client->report_len, (uint8_t *)client->report);
290             UNUSED(att_status);
291             break;
292         }
293 
294         case HIDS_CLIENT_STATE_W2_REPORT_QUERY_CHARACTERISTIC_DESCRIPTORS:
295             client->state = HIDS_CLIENT_STATE_W4_REPORT_CHARACTERISTIC_DESCRIPTORS_RESULT;
296             client->descriptor_handle = 0;
297             hids_client_get_characteristic_for_report_index(client, client->active_report_index, &characteristic);
298             att_status = gatt_client_discover_characteristic_descriptors(&handle_gatt_client_event, client->con_handle, &characteristic);
299             UNUSED(att_status);
300             break;
301 
302         case HIDS_CLIENT_STATE_W2_REPORT_READ_CHARACTERISTIC_DESCRIPTOR_VALUE:
303             client->state = HIDS_CLIENT_STATE_W4_REPORT_CHARACTERISTIC_DESCRIPTOR_VALUE_RESULT;
304 
305             att_status = gatt_client_read_characteristic_descriptor_using_descriptor_handle(&handle_gatt_client_event, client->con_handle, client->descriptor_handle);
306             client->descriptor_handle = 0;
307             UNUSED(att_status);
308             break;
309 
310         default:
311             break;
312     }
313 }
314 
315 static void hids_client_setup_report_event(hids_client_t * client, uint8_t report_id, uint8_t *buffer, uint16_t report_len){
316     uint16_t pos = 0;
317     buffer[pos++] = HCI_EVENT_GATTSERVICE_META;
318     pos++;  // skip len
319     buffer[pos++] = GATTSERVICE_SUBEVENT_HID_REPORT;
320     little_endian_store_16(buffer, pos, client->cid);
321     pos += 2;
322     buffer[pos++] = report_id;
323     little_endian_store_16(buffer, pos, report_len);
324     pos += 2;
325     buffer[1] = pos + report_len - 2;
326 }
327 
328 static void handle_boot_keyboard_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
329     UNUSED(packet_type);
330     UNUSED(channel);
331     UNUSED(size);
332 
333     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
334 
335     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
336     btstack_assert(client != NULL);
337 
338     uint8_t * in_place_event = packet;
339     hids_client_setup_report_event(client, HID_BOOT_MODE_KEYBOARD_ID, in_place_event, gatt_event_notification_get_value_length(packet));
340     (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
341 }
342 
343 static void handle_boot_mouse_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
344     UNUSED(packet_type);
345     UNUSED(channel);
346     UNUSED(size);
347 
348     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
349 
350     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
351     btstack_assert(client != NULL);
352 
353     uint8_t * in_place_event = packet;
354     hids_client_setup_report_event(client, HID_BOOT_MODE_MOUSE_ID, in_place_event, gatt_event_notification_get_value_length(packet));
355     (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
356 }
357 
358 
359 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
360     UNUSED(packet_type);
361     UNUSED(channel);
362     UNUSED(size);
363 
364     hids_client_t * client = NULL;
365     uint8_t att_status;
366     gatt_client_service_t service;
367     gatt_client_characteristic_t characteristic;
368     gatt_client_characteristic_descriptor_t characteristic_descriptor;
369 
370     hids_client_report_t * boot_keyboard_report;
371     hids_client_report_t * boot_mouse_report;
372 
373     switch(hci_event_packet_get_type(packet)){
374         case GATT_EVENT_SERVICE_QUERY_RESULT:
375             client = hids_get_client_for_con_handle(gatt_event_service_query_result_get_handle(packet));
376             btstack_assert(client != NULL);
377 
378             if (client->state != HIDS_CLIENT_STATE_W4_SERVICE_RESULT) {
379                 hids_emit_connection_established(client, GATT_CLIENT_IN_WRONG_STATE);
380                 hids_finalize_client(client);
381                 break;
382             }
383 
384             if (client->num_instances < MAX_NUM_HID_SERVICES){
385                 gatt_event_service_query_result_get_service(packet, &service);
386                 client->services[client->num_instances].start_handle = service.start_group_handle;
387                 client->services[client->num_instances].end_handle = service.end_group_handle;
388             }
389             client->num_instances++;
390             break;
391 
392         case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
393             client = hids_get_client_for_con_handle(gatt_event_characteristic_query_result_get_handle(packet));
394             btstack_assert(client != NULL);
395 
396             gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
397             switch (characteristic.uuid16){
398                 case ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE:
399                     client->protocol_mode_value_handle = characteristic.value_handle;
400                     break;
401 
402                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT:
403                     hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT);
404                     break;
405 
406                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT:
407                     hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT);
408                     break;
409 
410                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT:
411                     hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_OUTPUT);
412                     break;
413 
414                 case ORG_BLUETOOTH_CHARACTERISTIC_REPORT:
415                     hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_ID, HID_REPORT_TYPE_RESERVED);
416                     break;
417 
418                 case ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP:
419                     // printf("ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP\n");
420                     hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_MAP_ID, HID_REPORT_TYPE_INPUT);
421                     break;
422 
423                 case ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION:
424                     // printf("ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION\n");
425                     hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_HID_INFORMATION_ID, HID_REPORT_TYPE_INPUT);
426                     break;
427 
428                 case ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT:
429                     // printf("ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT\n");
430                     hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_HID_CONTROL_POINT_ID, HID_REPORT_TYPE_INPUT);
431                     break;
432                 default:
433                     break;
434             }
435             break;
436 
437         case GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT:
438             client = hids_get_client_for_con_handle(gatt_event_all_characteristic_descriptors_query_result_get_handle(packet));
439             btstack_assert(client != NULL);
440             gatt_event_all_characteristic_descriptors_query_result_get_characteristic_descriptor(packet, &characteristic_descriptor);
441 
442             // setup for descriptor value query
443             if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_REPORT_REFERENCE){
444                 client->descriptor_handle = characteristic_descriptor.handle;
445             }
446             break;
447 
448         case GATT_EVENT_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT:
449             // get report ID
450             client = hids_get_client_for_con_handle(gatt_event_characteristic_descriptor_query_result_get_handle(packet));
451             btstack_assert(client != NULL);
452             if (gatt_event_characteristic_descriptor_query_result_get_descriptor_length(packet) == 2){
453                 const uint8_t * value = gatt_event_characteristic_descriptor_query_result_get_descriptor(packet);
454                 client->reports[client->active_report_index].report_id = value[0];
455                 client->reports[client->active_report_index].report_type = (hid_report_type_t)value[1];
456             }
457             break;
458 
459         case GATT_EVENT_QUERY_COMPLETE:
460             client = hids_get_client_for_con_handle(gatt_event_query_complete_get_handle(packet));
461             btstack_assert(client != NULL);
462 
463             att_status = gatt_event_query_complete_get_att_status(packet);
464 
465             switch (client->state){
466                 case HIDS_CLIENT_STATE_W4_SERVICE_RESULT:
467                     if (att_status != ATT_ERROR_SUCCESS){
468                         hids_emit_connection_established(client, att_status);
469                         hids_finalize_client(client);
470                         break;
471                     }
472 
473                     if (client->num_instances == 0){
474                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
475                         hids_finalize_client(client);
476                         break;
477                     }
478 
479                     if (client->num_instances > MAX_NUM_HID_SERVICES) {
480                         log_info("%d hid services found, only first %d can be stored, increase MAX_NUM_HID_SERVICES", client->num_instances, MAX_NUM_HID_SERVICES);
481                         client->num_instances = MAX_NUM_HID_SERVICES;
482                     }
483 
484                     client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
485                     client->service_index = 0;
486                     break;
487 
488                 case HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT:
489                     if (att_status != ATT_ERROR_SUCCESS){
490                         hids_emit_connection_established(client, att_status);
491                         hids_finalize_client(client);
492                         break;
493                     }
494 
495                     switch (client->required_protocol_mode){
496                         case HID_PROTOCOL_MODE_BOOT:
497                             if (get_boot_keyboard_input_report(client) != NULL){
498                                 client->state = HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD;
499                                 break;
500                             }
501                             if (get_boot_mouse_input_report(client) != NULL){
502                                 client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
503                                 break;
504                             }
505                             hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
506                             hids_finalize_client(client);
507                             break;
508                         case HID_PROTOCOL_MODE_REPORT:
509                             if ((client->service_index + 1) < client->num_instances){
510                                 // discover characteristics of next service
511                                 client->service_index++;
512                                 client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
513                             } else {
514 
515                                 // discover characteristic descriptor for all Report characteristics,
516                                 // then read value of characteristic descriptor to get Report ID
517                                 client->active_report_index = 0;
518                                 client->service_index = 0;
519 
520                                 hids_client_get_next_active_report_index(client);
521                                 if (client->active_report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
522                                     client->state = HIDS_CLIENT_STATE_W2_REPORT_QUERY_CHARACTERISTIC_DESCRIPTORS;
523                                     break;
524                                 }
525                                 hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
526                                 hids_finalize_client(client);
527                             }
528                             break;
529                         default:
530                             break;
531                     }
532                     break;
533 
534                 case HIDS_CLIENT_STATE_W4_REPORT_CHARACTERISTIC_DESCRIPTORS_RESULT:
535                     if (client->descriptor_handle != 0){
536                         // descriptor found
537                         client->state = HIDS_CLIENT_STATE_W2_REPORT_READ_CHARACTERISTIC_DESCRIPTOR_VALUE;
538                         break;
539                     }
540 
541                     // go for next report
542                     client->active_report_index++;
543                     hids_client_get_next_active_report_index(client);
544 
545                     if (client->active_report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
546                         client->state = HIDS_CLIENT_STATE_W2_REPORT_QUERY_CHARACTERISTIC_DESCRIPTORS;
547                         break;
548                     }
549 
550                     // TODO continue with report mode
551                     hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
552                     break;
553 
554                 case HIDS_CLIENT_STATE_W4_REPORT_CHARACTERISTIC_DESCRIPTOR_VALUE_RESULT:
555                     // go for next report
556                     client->active_report_index++;
557                     hids_client_get_next_active_report_index(client);
558                     if (client->active_report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
559                         client->state = HIDS_CLIENT_STATE_W2_REPORT_QUERY_CHARACTERISTIC_DESCRIPTORS;
560                         break;
561                     }
562 
563                     // TODO continue with report mode
564                     hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
565                     break;
566 
567                 case HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED:
568                     boot_keyboard_report = get_boot_keyboard_input_report(client);
569                     if (boot_keyboard_report != NULL){
570                         // setup listener
571                         characteristic.value_handle = boot_keyboard_report->value_handle;
572                         gatt_client_listen_for_characteristic_value_updates(&boot_keyboard_report->notifications, &handle_boot_keyboard_hid_event, client->con_handle, &characteristic);
573                     }
574 
575                     // check if there is mouse input report
576                     boot_mouse_report = get_boot_mouse_input_report(client);
577                     if (boot_mouse_report != NULL){
578                         client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
579                         break;
580                     }
581 
582                     // if none of the reports is found bail out
583                     if (!boot_mouse_report && !boot_keyboard_report){
584                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
585                         hids_finalize_client(client);
586                         break;
587                     }
588 
589                     // set protocol
590                     if (client->protocol_mode_value_handle != 0){
591                         client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
592                     } else {
593                         client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
594                         client->state = HIDS_CLIENT_STATE_CONNECTED;
595                         hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
596                     }
597                     hids_run_for_client(client);
598                     break;
599 
600                 case HIDS_CLIENT_STATE_W4_MOUSE_ENABLED:
601                     boot_mouse_report = get_boot_mouse_input_report(client);
602                     if (boot_mouse_report != NULL){
603                         // setup listener
604                         characteristic.value_handle = boot_mouse_report->value_handle;
605                         gatt_client_listen_for_characteristic_value_updates(&boot_mouse_report->notifications, &handle_boot_mouse_hid_event, client->con_handle, &characteristic);
606                     }
607 
608                     boot_keyboard_report = get_boot_keyboard_input_report(client);
609                     if (!boot_mouse_report && !boot_keyboard_report){
610                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
611                         hids_finalize_client(client);
612                         break;
613                     }
614 
615                     if (client->protocol_mode_value_handle != 0){
616                         client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
617                     } else {
618                         client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
619                         client->state = HIDS_CLIENT_STATE_CONNECTED;
620                         hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
621                     }
622                     break;
623                 default:
624                     break;
625             }
626             break;
627 
628         default:
629             break;
630     }
631 
632     if (client != NULL){
633         hids_run_for_client(client);
634     }
635 }
636 
637 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){
638     btstack_assert(packet_handler != NULL);
639 
640     hids_client_t * client = hids_get_client_for_con_handle(con_handle);
641     if (client != NULL){
642         return ERROR_CODE_COMMAND_DISALLOWED;
643     }
644 
645     uint16_t cid = hids_get_next_cid();
646     if (hids_cid != NULL) {
647         *hids_cid = cid;
648     }
649 
650     client = hids_create_client(con_handle, cid);
651     if (client == NULL) {
652         return BTSTACK_MEMORY_ALLOC_FAILED;
653     }
654 
655     client->required_protocol_mode = protocol_mode;
656     client->client_handler = packet_handler;
657     client->state = HIDS_CLIENT_STATE_W2_QUERY_SERVICE;
658 
659     hids_run_for_client(client);
660     return ERROR_CODE_SUCCESS;
661 }
662 
663 uint8_t hids_client_disconnect(uint16_t hids_cid){
664     hids_client_t * client = hids_get_client_for_cid(hids_cid);
665     if (client == NULL){
666         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
667     }
668     // finalize connection
669     hids_finalize_client(client);
670     return ERROR_CODE_SUCCESS;
671 }
672 
673 uint8_t hids_client_send_report(uint16_t hids_cid, uint8_t report_id, const uint8_t * report, uint8_t report_len){
674     hids_client_t * client = hids_get_client_for_cid(hids_cid);
675     if (client == NULL){
676         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
677     }
678 
679     if (client->state != HIDS_CLIENT_STATE_CONNECTED) {
680         return ERROR_CODE_COMMAND_DISALLOWED;
681     }
682 
683     uint8_t report_index = find_report_index_for_report_id_and_type(client, report_id, HID_REPORT_TYPE_OUTPUT);
684     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
685         return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE;
686     }
687 
688     uint16_t mtu;
689     uint8_t status = gatt_client_get_mtu(client->con_handle, &mtu);
690 
691     if (status != ERROR_CODE_SUCCESS){
692         return status;
693     }
694 
695     if (mtu - 2 < report_len){
696         return ERROR_CODE_PARAMETER_OUT_OF_MANDATORY_RANGE;
697     }
698 
699     client->state = HIDS_CLIENT_W2_SEND_REPORT;
700     client->active_report_index = report_index;
701     client->report = report;
702     client->report_len = report_len;
703 
704     hids_run_for_client(client);
705     return ERROR_CODE_SUCCESS;
706 }
707 
708 void hids_client_init(void){}
709 
710 void hids_client_deinit(void){}
711