xref: /btstack/src/ble/gatt-service/hids_client.c (revision 2901a9b7af2a81f193187d7d58a554b5f7f1928c)
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 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
62 
63 static uint16_t hids_get_next_cid(void){
64     if (hids_cid_counter == 0xffff) {
65         hids_cid_counter = 1;
66     } else {
67         hids_cid_counter++;
68     }
69     return hids_cid_counter;
70 }
71 
72 static hids_client_t * hids_create_client(hci_con_handle_t con_handle, uint16_t cid){
73     hids_client_t * client = btstack_memory_hids_client_get();
74     if (!client){
75         log_error("Not enough memory to create client");
76         return NULL;
77     }
78     client->state = HIDS_CLIENT_STATE_IDLE;
79     client->cid = cid;
80     client->con_handle = con_handle;
81 
82     btstack_linked_list_add(&clients, (btstack_linked_item_t *) client);
83     return client;
84 }
85 
86 static void hids_finalize_client(hids_client_t * client){
87     btstack_linked_list_remove(&clients, (btstack_linked_item_t *) client);
88     btstack_memory_hids_client_free(client);
89 }
90 
91 static hids_client_t * hids_get_client_for_con_handle(hci_con_handle_t con_handle){
92     btstack_linked_list_iterator_t it;
93     btstack_linked_list_iterator_init(&it, &clients);
94     while (btstack_linked_list_iterator_has_next(&it)){
95         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
96         if (client->con_handle != con_handle) continue;
97         return client;
98     }
99     return NULL;
100 }
101 
102 static hids_client_t * hids_get_client_for_cid(uint16_t hids_cid){
103     btstack_linked_list_iterator_t it;
104     btstack_linked_list_iterator_init(&it, &clients);
105     while (btstack_linked_list_iterator_has_next(&it)){
106         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
107         if (client->cid != hids_cid) continue;
108         return client;
109     }
110     return NULL;
111 }
112 
113 static void hids_emit_connection_established(hids_client_t * client, uint8_t status){
114     uint8_t event[8];
115     int pos = 0;
116     event[pos++] = HCI_EVENT_GATTSERVICE_META;
117     event[pos++] = sizeof(event) - 2;
118     event[pos++] = GATTSERVICE_SUBEVENT_HID_SERVICE_CONNECTED;
119     little_endian_store_16(event, pos, client->cid);
120     pos += 2;
121     event[pos++] = status;
122     event[pos++] = client->protocol_mode;
123     event[pos++] = client->num_instances;
124     (*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
125 }
126 
127 
128 static void hids_run_for_client(hids_client_t * client){
129     uint8_t att_status;
130     gatt_client_service_t service;
131     gatt_client_characteristic_t characteristic;
132 
133     switch (client->state){
134         case HIDS_CLIENT_STATE_W2_QUERY_SERVICE:
135             client->state = HIDS_CLIENT_STATE_W4_SERVICE_RESULT;
136             att_status = gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, client->con_handle, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE);
137             // TODO handle status
138             UNUSED(att_status);
139             break;
140 
141         case HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC:
142             client->state = HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT;
143 
144             service.start_group_handle = client->services[client->service_index].start_handle;
145             service.end_group_handle = client->services[client->service_index].end_handle;
146             att_status = gatt_client_discover_characteristics_for_service(&handle_gatt_client_event, client->con_handle, &service);
147 
148             UNUSED(att_status);
149             break;
150 
151         case HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD:
152             client->state = HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED;
153             characteristic.value_handle = client->boot_keyboard_input_value_handle;
154             characteristic.end_handle = client->boot_keyboard_input_end_handle;
155             characteristic.properties = client->boot_keyboard_input_properties;
156             att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
157 
158             if (att_status != ERROR_CODE_SUCCESS){
159                 client->boot_keyboard_input_value_handle = 0;
160             }
161             break;
162 
163         case HIDS_CLIENT_STATE_W2_ENABLE_MOUSE:
164             client->state = HIDS_CLIENT_STATE_W4_MOUSE_ENABLED;
165             characteristic.value_handle = client->boot_mouse_input_value_handle;
166             characteristic.end_handle = client->boot_mouse_input_end_handle;
167             characteristic.properties = client->boot_mouse_input_properties;
168             att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
169 
170             if (att_status != ERROR_CODE_SUCCESS){
171                 client->boot_mouse_input_value_handle = 0;
172             }
173             break;
174 
175         case HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE:
176             client->state = HIDS_CLIENT_STATE_W4_SET_PROTOCOL_MODE;
177             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);
178             UNUSED(att_status);
179 
180             client->protocol_mode = client->required_protocol_mode;
181             client->state = HIDS_CLIENT_STATE_CONNECTED;
182             hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
183             break;
184 
185         default:
186             break;
187     }
188 }
189 
190 static void hids_client_setup_report_event(hids_client_t * client, uint8_t report_id, uint8_t *buffer, uint16_t report_len){
191     uint16_t pos = 0;
192     buffer[pos++] = HCI_EVENT_GATTSERVICE_META;
193     pos++;  // skip len
194     buffer[pos++] = GATTSERVICE_SUBEVENT_HID_REPORT;
195     little_endian_store_16(buffer, pos, client->cid);
196     pos += 2;
197     buffer[pos++] = report_id;
198     little_endian_store_16(buffer, pos, report_len);
199     pos += 2;
200     buffer[1] = pos + report_len - 2;
201 }
202 
203 static void handle_boot_keyboard_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
204     UNUSED(packet_type);
205     UNUSED(channel);
206     UNUSED(size);
207 
208     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
209 
210     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
211     btstack_assert(client != NULL);
212 
213     uint8_t * in_place_event = packet;
214     hids_client_setup_report_event(client, HID_BOOT_MODE_KEYBOARD_ID, in_place_event, gatt_event_notification_get_value_length(packet));
215     (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
216 }
217 
218 static void handle_boot_mouse_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
219     UNUSED(packet_type);
220     UNUSED(channel);
221     UNUSED(size);
222 
223     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
224 
225     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
226     btstack_assert(client != NULL);
227 
228     uint8_t * in_place_event = packet;
229     hids_client_setup_report_event(client, HID_BOOT_MODE_MOUSE_ID, in_place_event, gatt_event_notification_get_value_length(packet));
230     (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
231 }
232 
233 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
234     UNUSED(packet_type);
235     UNUSED(channel);
236     UNUSED(size);
237 
238     hids_client_t * client = NULL;
239     uint8_t att_status;
240     gatt_client_service_t service;
241     gatt_client_characteristic_t characteristic;
242 
243     switch(hci_event_packet_get_type(packet)){
244         case GATT_EVENT_SERVICE_QUERY_RESULT:
245             client = hids_get_client_for_con_handle(gatt_event_service_query_result_get_handle(packet));
246             btstack_assert(client != NULL);
247 
248             if (client->state != HIDS_CLIENT_STATE_W4_SERVICE_RESULT) {
249                 hids_emit_connection_established(client, GATT_CLIENT_IN_WRONG_STATE);
250                 hids_finalize_client(client);
251                 break;
252             }
253 
254             if (client->num_instances < MAX_NUM_HID_SERVICES){
255                 gatt_event_service_query_result_get_service(packet, &service);
256                 client->services[client->num_instances].start_handle = service.start_group_handle;
257                 client->services[client->num_instances].end_handle = service.end_group_handle;
258             }
259             client->num_instances++;
260             break;
261 
262         case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
263             client = hids_get_client_for_con_handle(gatt_event_characteristic_query_result_get_handle(packet));
264             btstack_assert(client != NULL);
265 
266             gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
267             switch (characteristic.uuid16){
268                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT:
269                     client->boot_keyboard_input_value_handle = characteristic.value_handle;
270                     client->boot_keyboard_input_end_handle = characteristic.end_handle;
271                     client->boot_keyboard_input_properties = characteristic.properties;
272                     break;
273                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT:
274                     client->boot_mouse_input_value_handle = characteristic.value_handle;
275                     client->boot_mouse_input_end_handle = characteristic.end_handle;
276                     client->boot_mouse_input_properties = characteristic.properties;
277                     break;
278                 case ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE:
279                     client->protocol_mode_value_handle = characteristic.value_handle;
280                     break;
281                 default:
282                     break;
283             }
284             break;
285 
286         case GATT_EVENT_QUERY_COMPLETE:
287             client = hids_get_client_for_con_handle(gatt_event_query_complete_get_handle(packet));
288             btstack_assert(client != NULL);
289 
290             att_status = gatt_event_query_complete_get_att_status(packet);
291 
292             switch (client->state){
293                 case HIDS_CLIENT_STATE_W4_SERVICE_RESULT:
294                     if (att_status != ATT_ERROR_SUCCESS){
295                         hids_emit_connection_established(client, att_status);
296                         hids_finalize_client(client);
297                         break;
298                     }
299 
300                     if (client->num_instances == 0){
301                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
302                         hids_finalize_client(client);
303                         break;
304                     }
305 
306                     if (client->num_instances > MAX_NUM_HID_SERVICES) {
307                         log_info("%d hid services found, only first %d can be stored, increase MAX_NUM_HID_SERVICES", client->num_instances, MAX_NUM_HID_SERVICES);
308                         client->num_instances = MAX_NUM_HID_SERVICES;
309                     }
310 
311                     client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
312                     client->service_index = 0;
313                     break;
314 
315                 case HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT:
316                     if (att_status != ATT_ERROR_SUCCESS){
317                         hids_emit_connection_established(client, att_status);
318                         hids_finalize_client(client);
319                         break;
320                     }
321 
322                     switch (client->required_protocol_mode){
323                         case HID_PROTOCOL_MODE_BOOT:
324                             if (client->boot_keyboard_input_value_handle != 0){
325                                 client->state = HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD;
326                                 break;
327                             }
328                             if (client->boot_mouse_input_value_handle != 0){
329                                 client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
330                                 break;
331                             }
332                             hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
333                             hids_finalize_client(client);
334                             break;
335                         default:
336                             break;
337                     }
338                     break;
339 
340                 case HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED:
341                     if (client->boot_keyboard_input_value_handle != 0){
342                         // setup listener
343                         characteristic.value_handle = client->boot_keyboard_input_value_handle;
344                         gatt_client_listen_for_characteristic_value_updates(&client->boot_keyboard_notifications, &handle_boot_keyboard_hid_event, client->con_handle, &characteristic);
345                     } else {
346                         if (client->boot_mouse_input_value_handle == 0){
347                             hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
348                             hids_finalize_client(client);
349                             break;
350                         }
351                     }
352 
353                     if (client->boot_mouse_input_value_handle != 0){
354                         client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
355                         break;
356                     }
357 
358                     // set protocol
359                     if (client->protocol_mode_value_handle != 0){
360                         client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
361                     } else {
362                         client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
363                         client->state = HIDS_CLIENT_STATE_CONNECTED;
364                         hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
365                     }
366                     hids_run_for_client(client);
367                     break;
368 
369                 case HIDS_CLIENT_STATE_W4_MOUSE_ENABLED:
370                     if (client->boot_mouse_input_value_handle != 0){
371                         // setup listener
372                         characteristic.value_handle = client->boot_mouse_input_value_handle;
373                         gatt_client_listen_for_characteristic_value_updates(&client->boot_mouse_notifications, &handle_boot_mouse_hid_event, client->con_handle, &characteristic);
374                     } else {
375                         if (client->boot_keyboard_input_value_handle == 0){
376                             hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
377                             hids_finalize_client(client);
378                             break;
379                         }
380                     }
381 
382                     if (client->protocol_mode_value_handle != 0){
383                         client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
384                     } else {
385                         client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
386                         client->state = HIDS_CLIENT_STATE_CONNECTED;
387                         hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
388                     }
389                     break;
390                 default:
391                     break;
392             }
393             break;
394 
395         default:
396             break;
397     }
398 
399     if (client != NULL){
400         hids_run_for_client(client);
401     }
402 }
403 
404 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){
405     btstack_assert(packet_handler != NULL);
406 
407     hids_client_t * client = hids_get_client_for_con_handle(con_handle);
408     if (client != NULL){
409         return ERROR_CODE_COMMAND_DISALLOWED;
410     }
411 
412     uint16_t cid = hids_get_next_cid();
413     if (hids_cid != NULL) {
414         *hids_cid = cid;
415     }
416 
417     client = hids_create_client(con_handle, cid);
418     if (client == NULL) {
419         return BTSTACK_MEMORY_ALLOC_FAILED;
420     }
421 
422     client->required_protocol_mode = protocol_mode;
423     client->client_handler = packet_handler;
424     client->state = HIDS_CLIENT_STATE_W2_QUERY_SERVICE;
425 
426     hids_run_for_client(client);
427     return ERROR_CODE_SUCCESS;
428 }
429 
430 uint8_t hids_client_disconnect(uint16_t hids_cid){
431     hids_client_t * client = hids_get_client_for_cid(hids_cid);
432     if (client == NULL){
433         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
434     }
435     // finalize connection
436     hids_finalize_client(client);
437     return ERROR_CODE_SUCCESS;
438 }
439 
440 void hids_client_init(void){}
441 
442 void hids_client_deinit(void){}
443