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