xref: /btstack/src/ble/gatt-service/hids_client.c (revision ab116b1ccc907ce10ac24793b7bfa16dbfe25f8c)
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 static uint16_t hids_report_counter = 0;
61 
62 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
63 
64 static uint16_t hids_get_next_cid(void){
65     if (hids_cid_counter == 0xffff) {
66         hids_cid_counter = 1;
67     } else {
68         hids_cid_counter++;
69     }
70     return hids_cid_counter;
71 }
72 
73 static uint8_t hids_get_next_report_index(void){
74     if (hids_report_counter < HIDS_CLIENT_NUM_REPORTS) {
75         hids_report_counter++;
76     } else {
77         hids_report_counter = HIDS_CLIENT_INVALID_REPORT_INDEX;
78     }
79     return hids_report_counter;
80 }
81 
82 static uint8_t find_report_index_for_value_handle(hids_client_t * client, uint16_t value_handle){
83     uint8_t i;
84     for (i = 0; i < client->num_reports; client++){
85         if (client->reports[i].value_handle == value_handle){
86             return i;
87         }
88     }
89     return HIDS_CLIENT_INVALID_REPORT_INDEX;
90 }
91 
92 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){
93     uint8_t i;
94     for (i = 0; i < client->num_reports; client++){
95         if ( (client->reports[i].report_id == report_id) && (client->reports[i].report_type == report_type)){
96             return i;
97         }
98     }
99     return HIDS_CLIENT_INVALID_REPORT_INDEX;
100 }
101 
102 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){
103     uint8_t i;
104     for (i = 0; i < client->num_reports; client++){
105         if ( (client->reports[i].report_id == report_id) && (client->reports[i].report_type == report_type)){
106             return &client->reports[i];
107         }
108     }
109     return NULL;
110 }
111 
112 static hids_client_report_t * get_boot_mouse_input_report(hids_client_t * client){
113     return find_report_for_report_id_and_type(client, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT);
114 }
115 static hids_client_report_t * get_boot_keyboard_input_report(hids_client_t * client){
116     return find_report_for_report_id_and_type(client, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT);
117 }
118 
119 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){
120     uint8_t report_index = find_report_index_for_value_handle(client, characteristic->value_handle);
121     if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
122         return;
123     }
124 
125     report_index = hids_get_next_report_index();
126 
127     if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
128         client->reports[report_index].value_handle = characteristic->value_handle;
129         client->reports[report_index].end_handle = characteristic->end_handle;
130         client->reports[report_index].properties = characteristic->properties;
131 
132         client->reports[report_index].report_id = report_id;
133         client->reports[report_index].report_type = report_type;
134         client->num_reports++;
135     } else {
136         log_info("not enough storage, increase HIDS_CLIENT_NUM_REPORTS");
137     }
138 }
139 
140 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){
141     uint8_t report_index = find_report_index_for_report_id_and_type(client, report_id, report_type);
142     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
143         return report_index;
144     }
145 
146     characteristic->value_handle = client->reports[report_index].value_handle;
147     characteristic->end_handle = client->reports[report_index].end_handle;
148     characteristic->properties = client->reports[report_index].properties;
149     return report_index;
150 }
151 
152 
153 static hids_client_t * hids_create_client(hci_con_handle_t con_handle, uint16_t cid){
154     hids_client_t * client = btstack_memory_hids_client_get();
155     if (!client){
156         log_error("Not enough memory to create client");
157         return NULL;
158     }
159     client->state = HIDS_CLIENT_STATE_IDLE;
160     client->cid = cid;
161     client->con_handle = con_handle;
162 
163     btstack_linked_list_add(&clients, (btstack_linked_item_t *) client);
164     return client;
165 }
166 
167 static void hids_finalize_client(hids_client_t * client){
168     btstack_linked_list_remove(&clients, (btstack_linked_item_t *) client);
169     btstack_memory_hids_client_free(client);
170 }
171 
172 static hids_client_t * hids_get_client_for_con_handle(hci_con_handle_t con_handle){
173     btstack_linked_list_iterator_t it;
174     btstack_linked_list_iterator_init(&it, &clients);
175     while (btstack_linked_list_iterator_has_next(&it)){
176         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
177         if (client->con_handle != con_handle) continue;
178         return client;
179     }
180     return NULL;
181 }
182 
183 static hids_client_t * hids_get_client_for_cid(uint16_t hids_cid){
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->cid != hids_cid) continue;
189         return client;
190     }
191     return NULL;
192 }
193 
194 static void hids_emit_connection_established(hids_client_t * client, uint8_t status){
195     uint8_t event[8];
196     int pos = 0;
197     event[pos++] = HCI_EVENT_GATTSERVICE_META;
198     event[pos++] = sizeof(event) - 2;
199     event[pos++] = GATTSERVICE_SUBEVENT_HID_SERVICE_CONNECTED;
200     little_endian_store_16(event, pos, client->cid);
201     pos += 2;
202     event[pos++] = status;
203     event[pos++] = client->protocol_mode;
204     event[pos++] = client->num_instances;
205     (*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
206 }
207 
208 
209 static void hids_run_for_client(hids_client_t * client){
210     uint8_t att_status;
211     gatt_client_service_t service;
212     gatt_client_characteristic_t characteristic;
213     uint8_t report_index;
214 
215     switch (client->state){
216         case HIDS_CLIENT_STATE_W2_QUERY_SERVICE:
217             client->state = HIDS_CLIENT_STATE_W4_SERVICE_RESULT;
218             att_status = gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, client->con_handle, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE);
219             // TODO handle status
220             UNUSED(att_status);
221             break;
222 
223         case HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC:
224             client->state = HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT;
225 
226             service.start_group_handle = client->services[client->service_index].start_handle;
227             service.end_group_handle = client->services[client->service_index].end_handle;
228             att_status = gatt_client_discover_characteristics_for_service(&handle_gatt_client_event, client->con_handle, &service);
229 
230             UNUSED(att_status);
231             break;
232 
233         case HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD:
234             client->state = HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED;
235 
236             report_index = hids_client_get_characteristic(client, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT, &characteristic);
237             if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
238                 att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
239             }
240 
241             if ((report_index == HIDS_CLIENT_INVALID_REPORT_INDEX) || (att_status != ERROR_CODE_SUCCESS)){
242                 client->reports[report_index].value_handle = 0;
243                 client->state = HIDS_CLIENT_STATE_CONNECTED;
244             }
245             break;
246 
247         case HIDS_CLIENT_STATE_W2_ENABLE_MOUSE:
248             client->state = HIDS_CLIENT_STATE_W4_MOUSE_ENABLED;
249 
250             report_index = hids_client_get_characteristic(client, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT, &characteristic);
251 
252             if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
253                 att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
254             }
255 
256             if ((report_index == HIDS_CLIENT_INVALID_REPORT_INDEX) || (att_status != ERROR_CODE_SUCCESS)){
257                 client->reports[report_index].value_handle = 0;
258                 client->state = HIDS_CLIENT_STATE_CONNECTED;
259             }
260 
261             break;
262 
263         case HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE:
264             client->state = HIDS_CLIENT_STATE_W4_SET_PROTOCOL_MODE;
265             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);
266             UNUSED(att_status);
267 
268             client->protocol_mode = client->required_protocol_mode;
269             client->state = HIDS_CLIENT_STATE_CONNECTED;
270             hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
271             break;
272 
273         case HIDS_CLIENT_W2_SEND_REPORT:{
274             client->state = HIDS_CLIENT_STATE_CONNECTED;
275 
276             att_status = gatt_client_write_value_of_characteristic_without_response(client->con_handle,
277                 client->reports[client->active_report_index].value_handle,
278                 client->report_len, (uint8_t *)client->report);
279             UNUSED(att_status);
280             break;
281         }
282         default:
283             break;
284     }
285 }
286 
287 static void hids_client_setup_report_event(hids_client_t * client, uint8_t report_id, uint8_t *buffer, uint16_t report_len){
288     uint16_t pos = 0;
289     buffer[pos++] = HCI_EVENT_GATTSERVICE_META;
290     pos++;  // skip len
291     buffer[pos++] = GATTSERVICE_SUBEVENT_HID_REPORT;
292     little_endian_store_16(buffer, pos, client->cid);
293     pos += 2;
294     buffer[pos++] = report_id;
295     little_endian_store_16(buffer, pos, report_len);
296     pos += 2;
297     buffer[1] = pos + report_len - 2;
298 }
299 
300 static void handle_boot_keyboard_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
301     UNUSED(packet_type);
302     UNUSED(channel);
303     UNUSED(size);
304 
305     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
306 
307     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
308     btstack_assert(client != NULL);
309 
310     uint8_t * in_place_event = packet;
311     hids_client_setup_report_event(client, HID_BOOT_MODE_KEYBOARD_ID, in_place_event, gatt_event_notification_get_value_length(packet));
312     (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
313 }
314 
315 static void handle_boot_mouse_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
316     UNUSED(packet_type);
317     UNUSED(channel);
318     UNUSED(size);
319 
320     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
321 
322     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
323     btstack_assert(client != NULL);
324 
325     uint8_t * in_place_event = packet;
326     hids_client_setup_report_event(client, HID_BOOT_MODE_MOUSE_ID, in_place_event, gatt_event_notification_get_value_length(packet));
327     (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
328 }
329 
330 
331 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
332     UNUSED(packet_type);
333     UNUSED(channel);
334     UNUSED(size);
335 
336     hids_client_t * client = NULL;
337     uint8_t att_status;
338     gatt_client_service_t service;
339     gatt_client_characteristic_t characteristic;
340 
341     hids_client_report_t * boot_keyboard_report;
342     hids_client_report_t * boot_mouse_report;
343 
344     switch(hci_event_packet_get_type(packet)){
345         case GATT_EVENT_SERVICE_QUERY_RESULT:
346             client = hids_get_client_for_con_handle(gatt_event_service_query_result_get_handle(packet));
347             btstack_assert(client != NULL);
348 
349             if (client->state != HIDS_CLIENT_STATE_W4_SERVICE_RESULT) {
350                 hids_emit_connection_established(client, GATT_CLIENT_IN_WRONG_STATE);
351                 hids_finalize_client(client);
352                 break;
353             }
354 
355             if (client->num_instances < MAX_NUM_HID_SERVICES){
356                 gatt_event_service_query_result_get_service(packet, &service);
357                 client->services[client->num_instances].start_handle = service.start_group_handle;
358                 client->services[client->num_instances].end_handle = service.end_group_handle;
359             }
360             client->num_instances++;
361             break;
362 
363         case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
364             client = hids_get_client_for_con_handle(gatt_event_characteristic_query_result_get_handle(packet));
365             btstack_assert(client != NULL);
366 
367             gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
368             switch (characteristic.uuid16){
369                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT:
370                     hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT);
371                     break;
372 
373                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT:
374                     hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT);
375                     break;
376 
377                 case ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE:
378                     client->protocol_mode_value_handle = characteristic.value_handle;
379                     break;
380 
381                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT:
382                     hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_OUTPUT);
383                     break;
384 
385                 default:
386                     break;
387             }
388             break;
389 
390         case GATT_EVENT_QUERY_COMPLETE:
391             client = hids_get_client_for_con_handle(gatt_event_query_complete_get_handle(packet));
392             btstack_assert(client != NULL);
393 
394             att_status = gatt_event_query_complete_get_att_status(packet);
395 
396             switch (client->state){
397                 case HIDS_CLIENT_STATE_W4_SERVICE_RESULT:
398                     if (att_status != ATT_ERROR_SUCCESS){
399                         hids_emit_connection_established(client, att_status);
400                         hids_finalize_client(client);
401                         break;
402                     }
403 
404                     if (client->num_instances == 0){
405                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
406                         hids_finalize_client(client);
407                         break;
408                     }
409 
410                     if (client->num_instances > MAX_NUM_HID_SERVICES) {
411                         log_info("%d hid services found, only first %d can be stored, increase MAX_NUM_HID_SERVICES", client->num_instances, MAX_NUM_HID_SERVICES);
412                         client->num_instances = MAX_NUM_HID_SERVICES;
413                     }
414 
415                     client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
416                     client->service_index = 0;
417                     break;
418 
419                 case HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT:
420                     if (att_status != ATT_ERROR_SUCCESS){
421                         hids_emit_connection_established(client, att_status);
422                         hids_finalize_client(client);
423                         break;
424                     }
425 
426                     switch (client->required_protocol_mode){
427                         case HID_PROTOCOL_MODE_BOOT:
428                             if (get_boot_keyboard_input_report(client) != NULL){
429                                 client->state = HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD;
430                                 break;
431                             }
432                             if (get_boot_mouse_input_report(client) != NULL){
433                                 client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
434                                 break;
435                             }
436                             hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
437                             hids_finalize_client(client);
438                             break;
439                         default:
440                             break;
441                     }
442                     break;
443 
444                 case HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED:
445                     boot_keyboard_report = get_boot_keyboard_input_report(client);
446                     if (boot_keyboard_report != NULL){
447                         // setup listener
448                         characteristic.value_handle = boot_keyboard_report->value_handle;
449                         gatt_client_listen_for_characteristic_value_updates(&boot_keyboard_report->notifications, &handle_boot_keyboard_hid_event, client->con_handle, &characteristic);
450                     }
451 
452                     // check if there is mouse input report
453                     boot_mouse_report = get_boot_mouse_input_report(client);
454                     if (boot_mouse_report != NULL){
455                         client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
456                         break;
457                     }
458 
459                     // if none of the reports is found bail out
460                     if (!boot_mouse_report && !boot_keyboard_report){
461                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
462                         hids_finalize_client(client);
463                         break;
464                     }
465 
466                     // set protocol
467                     if (client->protocol_mode_value_handle != 0){
468                         client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
469                     } else {
470                         client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
471                         client->state = HIDS_CLIENT_STATE_CONNECTED;
472                         hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
473                     }
474                     hids_run_for_client(client);
475                     break;
476 
477                 case HIDS_CLIENT_STATE_W4_MOUSE_ENABLED:
478                     boot_mouse_report = get_boot_mouse_input_report(client);
479                     if (boot_mouse_report != NULL){
480                         // setup listener
481                         characteristic.value_handle = boot_mouse_report->value_handle;
482                         gatt_client_listen_for_characteristic_value_updates(&boot_mouse_report->notifications, &handle_boot_mouse_hid_event, client->con_handle, &characteristic);
483                     }
484 
485                     boot_keyboard_report = get_boot_keyboard_input_report(client);
486                     if (!boot_mouse_report && !boot_keyboard_report){
487                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
488                         hids_finalize_client(client);
489                         break;
490                     }
491 
492                     if (client->protocol_mode_value_handle != 0){
493                         client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
494                     } else {
495                         client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
496                         client->state = HIDS_CLIENT_STATE_CONNECTED;
497                         hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
498                     }
499                     break;
500                 default:
501                     break;
502             }
503             break;
504 
505         default:
506             break;
507     }
508 
509     if (client != NULL){
510         hids_run_for_client(client);
511     }
512 }
513 
514 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){
515     btstack_assert(packet_handler != NULL);
516 
517     hids_client_t * client = hids_get_client_for_con_handle(con_handle);
518     if (client != NULL){
519         return ERROR_CODE_COMMAND_DISALLOWED;
520     }
521 
522     uint16_t cid = hids_get_next_cid();
523     if (hids_cid != NULL) {
524         *hids_cid = cid;
525     }
526 
527     client = hids_create_client(con_handle, cid);
528     if (client == NULL) {
529         return BTSTACK_MEMORY_ALLOC_FAILED;
530     }
531 
532     client->required_protocol_mode = protocol_mode;
533     client->client_handler = packet_handler;
534     client->state = HIDS_CLIENT_STATE_W2_QUERY_SERVICE;
535 
536     hids_run_for_client(client);
537     return ERROR_CODE_SUCCESS;
538 }
539 
540 uint8_t hids_client_disconnect(uint16_t hids_cid){
541     hids_client_t * client = hids_get_client_for_cid(hids_cid);
542     if (client == NULL){
543         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
544     }
545     // finalize connection
546     hids_finalize_client(client);
547     return ERROR_CODE_SUCCESS;
548 }
549 
550 uint8_t hids_client_send_report(uint16_t hids_cid, uint8_t report_id, const uint8_t * report, uint8_t report_len){
551     hids_client_t * client = hids_get_client_for_cid(hids_cid);
552     if (client == NULL){
553         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
554     }
555 
556     if (client->state != HIDS_CLIENT_STATE_CONNECTED) {
557         return ERROR_CODE_COMMAND_DISALLOWED;
558     }
559 
560     uint8_t report_index = find_report_index_for_report_id_and_type(client, report_id, HID_REPORT_TYPE_OUTPUT);
561     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
562         return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE;
563     }
564 
565     uint16_t mtu;
566     uint8_t status = gatt_client_get_mtu(client->con_handle, &mtu);
567 
568     if (status != ERROR_CODE_SUCCESS){
569         return status;
570     }
571 
572     if (mtu - 2 < report_len){
573         return ERROR_CODE_PARAMETER_OUT_OF_MANDATORY_RANGE;
574     }
575 
576     client->state = HIDS_CLIENT_W2_SEND_REPORT;
577     client->active_report_index = report_index;
578     client->report = report;
579     client->report_len = report_len;
580 
581     hids_run_for_client(client);
582     return ERROR_CODE_SUCCESS;
583 }
584 
585 void hids_client_init(void){}
586 
587 void hids_client_deinit(void){}
588