xref: /btstack/src/ble/gatt-service/hids_client.c (revision a381a464ca15126a7cbf15b070e35966cca429e8)
1fc975d0eSMilanka Ringwald /*
2fc975d0eSMilanka Ringwald  * Copyright (C) 2021 BlueKitchen GmbH
3fc975d0eSMilanka Ringwald  *
4fc975d0eSMilanka Ringwald  * Redistribution and use in source and binary forms, with or without
5fc975d0eSMilanka Ringwald  * modification, are permitted provided that the following conditions
6fc975d0eSMilanka Ringwald  * are met:
7fc975d0eSMilanka Ringwald  *
8fc975d0eSMilanka Ringwald  * 1. Redistributions of source code must retain the above copyright
9fc975d0eSMilanka Ringwald  *    notice, this list of conditions and the following disclaimer.
10fc975d0eSMilanka Ringwald  * 2. Redistributions in binary form must reproduce the above copyright
11fc975d0eSMilanka Ringwald  *    notice, this list of conditions and the following disclaimer in the
12fc975d0eSMilanka Ringwald  *    documentation and/or other materials provided with the distribution.
13fc975d0eSMilanka Ringwald  * 3. Neither the name of the copyright holders nor the names of
14fc975d0eSMilanka Ringwald  *    contributors may be used to endorse or promote products derived
15fc975d0eSMilanka Ringwald  *    from this software without specific prior written permission.
16fc975d0eSMilanka Ringwald  * 4. Any redistribution, use, or modification is done solely for
17fc975d0eSMilanka Ringwald  *    personal benefit and not for any commercial purpose or for
18fc975d0eSMilanka Ringwald  *    monetary gain.
19fc975d0eSMilanka Ringwald  *
20fc975d0eSMilanka Ringwald  * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
21fc975d0eSMilanka Ringwald  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22fc975d0eSMilanka Ringwald  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23fc975d0eSMilanka Ringwald  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
24fc975d0eSMilanka Ringwald  * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25fc975d0eSMilanka Ringwald  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26fc975d0eSMilanka Ringwald  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27fc975d0eSMilanka Ringwald  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28fc975d0eSMilanka Ringwald  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29fc975d0eSMilanka Ringwald  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
30fc975d0eSMilanka Ringwald  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31fc975d0eSMilanka Ringwald  * SUCH DAMAGE.
32fc975d0eSMilanka Ringwald  *
33fc975d0eSMilanka Ringwald  * Please inquire about commercial licensing options at
34fc975d0eSMilanka Ringwald  * [email protected]
35fc975d0eSMilanka Ringwald  *
36fc975d0eSMilanka Ringwald  */
37fc975d0eSMilanka Ringwald 
38fc975d0eSMilanka Ringwald #define BTSTACK_FILE__ "hids_client.c"
39fc975d0eSMilanka Ringwald 
40fc975d0eSMilanka Ringwald #include "btstack_config.h"
41fc975d0eSMilanka Ringwald 
42fc975d0eSMilanka Ringwald #include <stdint.h>
43fc975d0eSMilanka Ringwald #include <string.h>
44fc975d0eSMilanka Ringwald 
45cf26c8fbSMilanka Ringwald #include "ble/gatt-service/hids_client.h"
46fc975d0eSMilanka Ringwald 
47cf26c8fbSMilanka Ringwald #include "btstack_memory.h"
48fc975d0eSMilanka Ringwald #include "ble/att_db.h"
49fc975d0eSMilanka Ringwald #include "ble/core.h"
50fc975d0eSMilanka Ringwald #include "ble/gatt_client.h"
51fc975d0eSMilanka Ringwald #include "ble/sm.h"
52cf26c8fbSMilanka Ringwald #include "bluetooth_gatt.h"
53fc975d0eSMilanka Ringwald #include "btstack_debug.h"
54fc975d0eSMilanka Ringwald #include "btstack_event.h"
55fc975d0eSMilanka Ringwald #include "btstack_run_loop.h"
56fc975d0eSMilanka Ringwald #include "gap.h"
57fc975d0eSMilanka Ringwald 
58cf26c8fbSMilanka Ringwald static btstack_linked_list_t clients;
59cf26c8fbSMilanka Ringwald static uint16_t hids_cid_counter = 0;
60fc975d0eSMilanka Ringwald 
61cf26c8fbSMilanka Ringwald static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
62cf26c8fbSMilanka Ringwald 
63cf26c8fbSMilanka Ringwald static uint16_t hids_get_next_cid(void){
64cf26c8fbSMilanka Ringwald     if (hids_cid_counter == 0xffff) {
65cf26c8fbSMilanka Ringwald         hids_cid_counter = 1;
66cf26c8fbSMilanka Ringwald     } else {
67cf26c8fbSMilanka Ringwald         hids_cid_counter++;
68cf26c8fbSMilanka Ringwald     }
69cf26c8fbSMilanka Ringwald     return hids_cid_counter;
70fc975d0eSMilanka Ringwald }
71fc975d0eSMilanka Ringwald 
72ab116b1cSMilanka Ringwald static uint8_t find_report_index_for_value_handle(hids_client_t * client, uint16_t value_handle){
73ab116b1cSMilanka Ringwald     uint8_t i;
74*a381a464SMilanka Ringwald     for (i = 0; i < client->num_reports; i++){
75ab116b1cSMilanka Ringwald         if (client->reports[i].value_handle == value_handle){
76ab116b1cSMilanka Ringwald             return i;
77ab116b1cSMilanka Ringwald         }
78ab116b1cSMilanka Ringwald     }
79ab116b1cSMilanka Ringwald     return HIDS_CLIENT_INVALID_REPORT_INDEX;
80ab116b1cSMilanka Ringwald }
81ab116b1cSMilanka Ringwald 
82ab116b1cSMilanka Ringwald 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){
83ab116b1cSMilanka Ringwald     uint8_t i;
84*a381a464SMilanka Ringwald     for (i = 0; i < client->num_reports; i++){
85ab116b1cSMilanka Ringwald         if ( (client->reports[i].report_id == report_id) && (client->reports[i].report_type == report_type)){
86ab116b1cSMilanka Ringwald             return i;
87ab116b1cSMilanka Ringwald         }
88ab116b1cSMilanka Ringwald     }
89ab116b1cSMilanka Ringwald     return HIDS_CLIENT_INVALID_REPORT_INDEX;
90ab116b1cSMilanka Ringwald }
91ab116b1cSMilanka Ringwald 
92ab116b1cSMilanka Ringwald 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){
93ab116b1cSMilanka Ringwald     uint8_t i;
94*a381a464SMilanka Ringwald     for (i = 0; i < client->num_reports; i++){
95ab116b1cSMilanka Ringwald         if ( (client->reports[i].report_id == report_id) && (client->reports[i].report_type == report_type)){
96ab116b1cSMilanka Ringwald             return &client->reports[i];
97ab116b1cSMilanka Ringwald         }
98ab116b1cSMilanka Ringwald     }
99ab116b1cSMilanka Ringwald     return NULL;
100ab116b1cSMilanka Ringwald }
101ab116b1cSMilanka Ringwald 
102ab116b1cSMilanka Ringwald static hids_client_report_t * get_boot_mouse_input_report(hids_client_t * client){
103ab116b1cSMilanka Ringwald     return find_report_for_report_id_and_type(client, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT);
104ab116b1cSMilanka Ringwald }
105ab116b1cSMilanka Ringwald static hids_client_report_t * get_boot_keyboard_input_report(hids_client_t * client){
106ab116b1cSMilanka Ringwald     return find_report_for_report_id_and_type(client, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT);
107ab116b1cSMilanka Ringwald }
108ab116b1cSMilanka Ringwald 
109ab116b1cSMilanka Ringwald 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){
110ab116b1cSMilanka Ringwald     uint8_t report_index = find_report_index_for_value_handle(client, characteristic->value_handle);
111ab116b1cSMilanka Ringwald     if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
112ab116b1cSMilanka Ringwald         return;
113ab116b1cSMilanka Ringwald     }
114ab116b1cSMilanka Ringwald 
115*a381a464SMilanka Ringwald     if (client->active_report_index < HIDS_CLIENT_NUM_REPORTS) {
116*a381a464SMilanka Ringwald         client->active_report_index++;
117*a381a464SMilanka Ringwald         client->reports[client->active_report_index].value_handle = characteristic->value_handle;
118*a381a464SMilanka Ringwald         client->reports[client->active_report_index].end_handle = characteristic->end_handle;
119*a381a464SMilanka Ringwald         client->reports[client->active_report_index].properties = characteristic->properties;
120ab116b1cSMilanka Ringwald 
121*a381a464SMilanka Ringwald         client->reports[client->active_report_index].report_id = report_id;
122*a381a464SMilanka Ringwald         client->reports[client->active_report_index].report_type = report_type;
123ab116b1cSMilanka Ringwald         client->num_reports++;
124ab116b1cSMilanka Ringwald     } else {
125ab116b1cSMilanka Ringwald         log_info("not enough storage, increase HIDS_CLIENT_NUM_REPORTS");
126ab116b1cSMilanka Ringwald     }
127ab116b1cSMilanka Ringwald }
128ab116b1cSMilanka Ringwald 
129ab116b1cSMilanka Ringwald 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){
130ab116b1cSMilanka Ringwald     uint8_t report_index = find_report_index_for_report_id_and_type(client, report_id, report_type);
131ab116b1cSMilanka Ringwald     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
132ab116b1cSMilanka Ringwald         return report_index;
133ab116b1cSMilanka Ringwald     }
134ab116b1cSMilanka Ringwald 
135ab116b1cSMilanka Ringwald     characteristic->value_handle = client->reports[report_index].value_handle;
136ab116b1cSMilanka Ringwald     characteristic->end_handle = client->reports[report_index].end_handle;
137ab116b1cSMilanka Ringwald     characteristic->properties = client->reports[report_index].properties;
138ab116b1cSMilanka Ringwald     return report_index;
139ab116b1cSMilanka Ringwald }
140ab116b1cSMilanka Ringwald 
141ab116b1cSMilanka Ringwald 
142cf26c8fbSMilanka Ringwald static hids_client_t * hids_create_client(hci_con_handle_t con_handle, uint16_t cid){
143cf26c8fbSMilanka Ringwald     hids_client_t * client = btstack_memory_hids_client_get();
144cf26c8fbSMilanka Ringwald     if (!client){
145cf26c8fbSMilanka Ringwald         log_error("Not enough memory to create client");
146cf26c8fbSMilanka Ringwald         return NULL;
147cf26c8fbSMilanka Ringwald     }
148cf26c8fbSMilanka Ringwald     client->state = HIDS_CLIENT_STATE_IDLE;
149cf26c8fbSMilanka Ringwald     client->cid = cid;
150cf26c8fbSMilanka Ringwald     client->con_handle = con_handle;
151fc975d0eSMilanka Ringwald 
152cf26c8fbSMilanka Ringwald     btstack_linked_list_add(&clients, (btstack_linked_item_t *) client);
153cf26c8fbSMilanka Ringwald     return client;
154fc975d0eSMilanka Ringwald }
155fc975d0eSMilanka Ringwald 
156cf26c8fbSMilanka Ringwald static void hids_finalize_client(hids_client_t * client){
157cf26c8fbSMilanka Ringwald     btstack_linked_list_remove(&clients, (btstack_linked_item_t *) client);
158cf26c8fbSMilanka Ringwald     btstack_memory_hids_client_free(client);
159fc975d0eSMilanka Ringwald }
160cf26c8fbSMilanka Ringwald 
161cf26c8fbSMilanka Ringwald static hids_client_t * hids_get_client_for_con_handle(hci_con_handle_t con_handle){
162cf26c8fbSMilanka Ringwald     btstack_linked_list_iterator_t it;
163cf26c8fbSMilanka Ringwald     btstack_linked_list_iterator_init(&it, &clients);
164cf26c8fbSMilanka Ringwald     while (btstack_linked_list_iterator_has_next(&it)){
165cf26c8fbSMilanka Ringwald         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
166cf26c8fbSMilanka Ringwald         if (client->con_handle != con_handle) continue;
167cf26c8fbSMilanka Ringwald         return client;
168cf26c8fbSMilanka Ringwald     }
169cf26c8fbSMilanka Ringwald     return NULL;
170cf26c8fbSMilanka Ringwald }
171cf26c8fbSMilanka Ringwald 
172cf26c8fbSMilanka Ringwald static hids_client_t * hids_get_client_for_cid(uint16_t hids_cid){
173cf26c8fbSMilanka Ringwald     btstack_linked_list_iterator_t it;
174cf26c8fbSMilanka Ringwald     btstack_linked_list_iterator_init(&it, &clients);
175cf26c8fbSMilanka Ringwald     while (btstack_linked_list_iterator_has_next(&it)){
176cf26c8fbSMilanka Ringwald         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
177cf26c8fbSMilanka Ringwald         if (client->cid != hids_cid) continue;
178cf26c8fbSMilanka Ringwald         return client;
179cf26c8fbSMilanka Ringwald     }
180cf26c8fbSMilanka Ringwald     return NULL;
181cf26c8fbSMilanka Ringwald }
182cf26c8fbSMilanka Ringwald 
183cf26c8fbSMilanka Ringwald static void hids_emit_connection_established(hids_client_t * client, uint8_t status){
1846bcfb631SMilanka Ringwald     uint8_t event[8];
185cf26c8fbSMilanka Ringwald     int pos = 0;
186cf26c8fbSMilanka Ringwald     event[pos++] = HCI_EVENT_GATTSERVICE_META;
187cf26c8fbSMilanka Ringwald     event[pos++] = sizeof(event) - 2;
188cf26c8fbSMilanka Ringwald     event[pos++] = GATTSERVICE_SUBEVENT_HID_SERVICE_CONNECTED;
189cf26c8fbSMilanka Ringwald     little_endian_store_16(event, pos, client->cid);
190cf26c8fbSMilanka Ringwald     pos += 2;
191cf26c8fbSMilanka Ringwald     event[pos++] = status;
1926bcfb631SMilanka Ringwald     event[pos++] = client->protocol_mode;
193cf26c8fbSMilanka Ringwald     event[pos++] = client->num_instances;
194cf26c8fbSMilanka Ringwald     (*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
195cf26c8fbSMilanka Ringwald }
196cf26c8fbSMilanka Ringwald 
197cf26c8fbSMilanka Ringwald 
198cf26c8fbSMilanka Ringwald static void hids_run_for_client(hids_client_t * client){
199cf26c8fbSMilanka Ringwald     uint8_t att_status;
2006bcfb631SMilanka Ringwald     gatt_client_service_t service;
2016bcfb631SMilanka Ringwald     gatt_client_characteristic_t characteristic;
202ab116b1cSMilanka Ringwald     uint8_t report_index;
203cf26c8fbSMilanka Ringwald 
204cf26c8fbSMilanka Ringwald     switch (client->state){
205cf26c8fbSMilanka Ringwald         case HIDS_CLIENT_STATE_W2_QUERY_SERVICE:
206cf26c8fbSMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_SERVICE_RESULT;
207cf26c8fbSMilanka Ringwald             att_status = gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, client->con_handle, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE);
208cf26c8fbSMilanka Ringwald             // TODO handle status
209cf26c8fbSMilanka Ringwald             UNUSED(att_status);
210cf26c8fbSMilanka Ringwald             break;
211cf26c8fbSMilanka Ringwald 
212cf26c8fbSMilanka Ringwald         case HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC:
2136bcfb631SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT;
2146bcfb631SMilanka Ringwald 
2156bcfb631SMilanka Ringwald             service.start_group_handle = client->services[client->service_index].start_handle;
2166bcfb631SMilanka Ringwald             service.end_group_handle = client->services[client->service_index].end_handle;
2176bcfb631SMilanka Ringwald             att_status = gatt_client_discover_characteristics_for_service(&handle_gatt_client_event, client->con_handle, &service);
2186bcfb631SMilanka Ringwald 
2196bcfb631SMilanka Ringwald             UNUSED(att_status);
2206bcfb631SMilanka Ringwald             break;
2216bcfb631SMilanka Ringwald 
2222901a9b7SMilanka Ringwald         case HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD:
2232901a9b7SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED;
2242901a9b7SMilanka Ringwald 
225ab116b1cSMilanka Ringwald             report_index = hids_client_get_characteristic(client, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT, &characteristic);
226ab116b1cSMilanka Ringwald             if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
227ab116b1cSMilanka Ringwald                 att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
228ab116b1cSMilanka Ringwald             }
229ab116b1cSMilanka Ringwald 
230ab116b1cSMilanka Ringwald             if ((report_index == HIDS_CLIENT_INVALID_REPORT_INDEX) || (att_status != ERROR_CODE_SUCCESS)){
231ab116b1cSMilanka Ringwald                 client->reports[report_index].value_handle = 0;
232ab116b1cSMilanka Ringwald                 client->state = HIDS_CLIENT_STATE_CONNECTED;
2332901a9b7SMilanka Ringwald             }
2346bcfb631SMilanka Ringwald             break;
2356bcfb631SMilanka Ringwald 
2362901a9b7SMilanka Ringwald         case HIDS_CLIENT_STATE_W2_ENABLE_MOUSE:
2372901a9b7SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_MOUSE_ENABLED;
2382901a9b7SMilanka Ringwald 
239ab116b1cSMilanka Ringwald             report_index = hids_client_get_characteristic(client, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT, &characteristic);
240ab116b1cSMilanka Ringwald 
241ab116b1cSMilanka Ringwald             if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
242ab116b1cSMilanka Ringwald                 att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
2432901a9b7SMilanka Ringwald             }
244ab116b1cSMilanka Ringwald 
245ab116b1cSMilanka Ringwald             if ((report_index == HIDS_CLIENT_INVALID_REPORT_INDEX) || (att_status != ERROR_CODE_SUCCESS)){
246ab116b1cSMilanka Ringwald                 client->reports[report_index].value_handle = 0;
247ab116b1cSMilanka Ringwald                 client->state = HIDS_CLIENT_STATE_CONNECTED;
248ab116b1cSMilanka Ringwald             }
249ab116b1cSMilanka Ringwald 
2506bcfb631SMilanka Ringwald             break;
2516bcfb631SMilanka Ringwald 
2522901a9b7SMilanka Ringwald         case HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE:
2532901a9b7SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_SET_PROTOCOL_MODE;
2542901a9b7SMilanka Ringwald             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);
2556bcfb631SMilanka Ringwald             UNUSED(att_status);
2562901a9b7SMilanka Ringwald 
2572901a9b7SMilanka Ringwald             client->protocol_mode = client->required_protocol_mode;
2586bcfb631SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_CONNECTED;
2596bcfb631SMilanka Ringwald             hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
260cf26c8fbSMilanka Ringwald             break;
261cf26c8fbSMilanka Ringwald 
2621624214bSMilanka Ringwald         case HIDS_CLIENT_W2_SEND_REPORT:{
2631624214bSMilanka Ringwald             client->state = HIDS_CLIENT_STATE_CONNECTED;
2641624214bSMilanka Ringwald 
2651624214bSMilanka Ringwald             att_status = gatt_client_write_value_of_characteristic_without_response(client->con_handle,
2661624214bSMilanka Ringwald                 client->reports[client->active_report_index].value_handle,
2671624214bSMilanka Ringwald                 client->report_len, (uint8_t *)client->report);
2681624214bSMilanka Ringwald             UNUSED(att_status);
2691624214bSMilanka Ringwald             break;
2701624214bSMilanka Ringwald         }
2716bcfb631SMilanka Ringwald         default:
2726bcfb631SMilanka Ringwald             break;
2736bcfb631SMilanka Ringwald     }
2746bcfb631SMilanka Ringwald }
2756bcfb631SMilanka Ringwald 
27672a8858fSMilanka Ringwald static void hids_client_setup_report_event(hids_client_t * client, uint8_t report_id, uint8_t *buffer, uint16_t report_len){
27772a8858fSMilanka Ringwald     uint16_t pos = 0;
27872a8858fSMilanka Ringwald     buffer[pos++] = HCI_EVENT_GATTSERVICE_META;
27972a8858fSMilanka Ringwald     pos++;  // skip len
28072a8858fSMilanka Ringwald     buffer[pos++] = GATTSERVICE_SUBEVENT_HID_REPORT;
28172a8858fSMilanka Ringwald     little_endian_store_16(buffer, pos, client->cid);
28272a8858fSMilanka Ringwald     pos += 2;
28372a8858fSMilanka Ringwald     buffer[pos++] = report_id;
28472a8858fSMilanka Ringwald     little_endian_store_16(buffer, pos, report_len);
28572a8858fSMilanka Ringwald     pos += 2;
28672a8858fSMilanka Ringwald     buffer[1] = pos + report_len - 2;
28772a8858fSMilanka Ringwald }
28872a8858fSMilanka Ringwald 
28972a8858fSMilanka Ringwald static void handle_boot_keyboard_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
2906bcfb631SMilanka Ringwald     UNUSED(packet_type);
2916bcfb631SMilanka Ringwald     UNUSED(channel);
2926bcfb631SMilanka Ringwald     UNUSED(size);
29372a8858fSMilanka Ringwald 
29472a8858fSMilanka Ringwald     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
29572a8858fSMilanka Ringwald 
29672a8858fSMilanka Ringwald     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
29772a8858fSMilanka Ringwald     btstack_assert(client != NULL);
29872a8858fSMilanka Ringwald 
29972a8858fSMilanka Ringwald     uint8_t * in_place_event = packet;
30072a8858fSMilanka Ringwald     hids_client_setup_report_event(client, HID_BOOT_MODE_KEYBOARD_ID, in_place_event, gatt_event_notification_get_value_length(packet));
30172a8858fSMilanka Ringwald     (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
30272a8858fSMilanka Ringwald }
30372a8858fSMilanka Ringwald 
30472a8858fSMilanka Ringwald static void handle_boot_mouse_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
30572a8858fSMilanka Ringwald     UNUSED(packet_type);
30672a8858fSMilanka Ringwald     UNUSED(channel);
30772a8858fSMilanka Ringwald     UNUSED(size);
30872a8858fSMilanka Ringwald 
30972a8858fSMilanka Ringwald     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
31072a8858fSMilanka Ringwald 
31172a8858fSMilanka Ringwald     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
31272a8858fSMilanka Ringwald     btstack_assert(client != NULL);
31372a8858fSMilanka Ringwald 
31472a8858fSMilanka Ringwald     uint8_t * in_place_event = packet;
31572a8858fSMilanka Ringwald     hids_client_setup_report_event(client, HID_BOOT_MODE_MOUSE_ID, in_place_event, gatt_event_notification_get_value_length(packet));
31672a8858fSMilanka Ringwald     (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
317cf26c8fbSMilanka Ringwald }
318cf26c8fbSMilanka Ringwald 
3191624214bSMilanka Ringwald 
320cf26c8fbSMilanka Ringwald static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
321cf26c8fbSMilanka Ringwald     UNUSED(packet_type);
322cf26c8fbSMilanka Ringwald     UNUSED(channel);
323cf26c8fbSMilanka Ringwald     UNUSED(size);
324cf26c8fbSMilanka Ringwald 
3256bcfb631SMilanka Ringwald     hids_client_t * client = NULL;
326cf26c8fbSMilanka Ringwald     uint8_t att_status;
3276bcfb631SMilanka Ringwald     gatt_client_service_t service;
3286bcfb631SMilanka Ringwald     gatt_client_characteristic_t characteristic;
329ab116b1cSMilanka Ringwald 
330ab116b1cSMilanka Ringwald     hids_client_report_t * boot_keyboard_report;
331ab116b1cSMilanka Ringwald     hids_client_report_t * boot_mouse_report;
332cf26c8fbSMilanka Ringwald 
333cf26c8fbSMilanka Ringwald     switch(hci_event_packet_get_type(packet)){
334cf26c8fbSMilanka Ringwald         case GATT_EVENT_SERVICE_QUERY_RESULT:
335cf26c8fbSMilanka Ringwald             client = hids_get_client_for_con_handle(gatt_event_service_query_result_get_handle(packet));
336cf26c8fbSMilanka Ringwald             btstack_assert(client != NULL);
337cf26c8fbSMilanka Ringwald 
3386bcfb631SMilanka Ringwald             if (client->state != HIDS_CLIENT_STATE_W4_SERVICE_RESULT) {
3396bcfb631SMilanka Ringwald                 hids_emit_connection_established(client, GATT_CLIENT_IN_WRONG_STATE);
3406bcfb631SMilanka Ringwald                 hids_finalize_client(client);
3416bcfb631SMilanka Ringwald                 break;
3426bcfb631SMilanka Ringwald             }
3436bcfb631SMilanka Ringwald 
3446bcfb631SMilanka Ringwald             if (client->num_instances < MAX_NUM_HID_SERVICES){
3456bcfb631SMilanka Ringwald                 gatt_event_service_query_result_get_service(packet, &service);
3466bcfb631SMilanka Ringwald                 client->services[client->num_instances].start_handle = service.start_group_handle;
3476bcfb631SMilanka Ringwald                 client->services[client->num_instances].end_handle = service.end_group_handle;
3486bcfb631SMilanka Ringwald             }
3496bcfb631SMilanka Ringwald             client->num_instances++;
3506bcfb631SMilanka Ringwald             break;
3516bcfb631SMilanka Ringwald 
3526bcfb631SMilanka Ringwald         case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
3536bcfb631SMilanka Ringwald             client = hids_get_client_for_con_handle(gatt_event_characteristic_query_result_get_handle(packet));
3546bcfb631SMilanka Ringwald             btstack_assert(client != NULL);
3556bcfb631SMilanka Ringwald 
3566bcfb631SMilanka Ringwald             gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
3576bcfb631SMilanka Ringwald             switch (characteristic.uuid16){
3586bcfb631SMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT:
359ab116b1cSMilanka Ringwald                     hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT);
3606bcfb631SMilanka Ringwald                     break;
3611624214bSMilanka Ringwald 
3626bcfb631SMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT:
363ab116b1cSMilanka Ringwald                     hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT);
3646bcfb631SMilanka Ringwald                     break;
3651624214bSMilanka Ringwald 
3666bcfb631SMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE:
3676bcfb631SMilanka Ringwald                     client->protocol_mode_value_handle = characteristic.value_handle;
3686bcfb631SMilanka Ringwald                     break;
3691624214bSMilanka Ringwald 
3701624214bSMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT:
371ab116b1cSMilanka Ringwald                     hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_OUTPUT);
3721624214bSMilanka Ringwald                     break;
3731624214bSMilanka Ringwald 
3746bcfb631SMilanka Ringwald                 default:
3756bcfb631SMilanka Ringwald                     break;
3766bcfb631SMilanka Ringwald             }
377cf26c8fbSMilanka Ringwald             break;
378cf26c8fbSMilanka Ringwald 
379cf26c8fbSMilanka Ringwald         case GATT_EVENT_QUERY_COMPLETE:
380cf26c8fbSMilanka Ringwald             client = hids_get_client_for_con_handle(gatt_event_query_complete_get_handle(packet));
381cf26c8fbSMilanka Ringwald             btstack_assert(client != NULL);
382cf26c8fbSMilanka Ringwald 
383cf26c8fbSMilanka Ringwald             att_status = gatt_event_query_complete_get_att_status(packet);
384cf26c8fbSMilanka Ringwald 
385cf26c8fbSMilanka Ringwald             switch (client->state){
386cf26c8fbSMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_SERVICE_RESULT:
3876bcfb631SMilanka Ringwald                     if (att_status != ATT_ERROR_SUCCESS){
3886bcfb631SMilanka Ringwald                         hids_emit_connection_established(client, att_status);
3896bcfb631SMilanka Ringwald                         hids_finalize_client(client);
390cf26c8fbSMilanka Ringwald                         break;
3916bcfb631SMilanka Ringwald                     }
3926bcfb631SMilanka Ringwald 
3936bcfb631SMilanka Ringwald                     if (client->num_instances == 0){
3946bcfb631SMilanka Ringwald                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
3956bcfb631SMilanka Ringwald                         hids_finalize_client(client);
3966bcfb631SMilanka Ringwald                         break;
3976bcfb631SMilanka Ringwald                     }
3986bcfb631SMilanka Ringwald 
3996bcfb631SMilanka Ringwald                     if (client->num_instances > MAX_NUM_HID_SERVICES) {
4006bcfb631SMilanka Ringwald                         log_info("%d hid services found, only first %d can be stored, increase MAX_NUM_HID_SERVICES", client->num_instances, MAX_NUM_HID_SERVICES);
4016bcfb631SMilanka Ringwald                         client->num_instances = MAX_NUM_HID_SERVICES;
4026bcfb631SMilanka Ringwald                     }
4036bcfb631SMilanka Ringwald 
4046bcfb631SMilanka Ringwald                     client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
4056bcfb631SMilanka Ringwald                     client->service_index = 0;
4066bcfb631SMilanka Ringwald                     break;
4076bcfb631SMilanka Ringwald 
4086bcfb631SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT:
4096bcfb631SMilanka Ringwald                     if (att_status != ATT_ERROR_SUCCESS){
4106bcfb631SMilanka Ringwald                         hids_emit_connection_established(client, att_status);
4116bcfb631SMilanka Ringwald                         hids_finalize_client(client);
4126bcfb631SMilanka Ringwald                         break;
4136bcfb631SMilanka Ringwald                     }
4146bcfb631SMilanka Ringwald 
4156bcfb631SMilanka Ringwald                     switch (client->required_protocol_mode){
4166bcfb631SMilanka Ringwald                         case HID_PROTOCOL_MODE_BOOT:
417ab116b1cSMilanka Ringwald                             if (get_boot_keyboard_input_report(client) != NULL){
4182901a9b7SMilanka Ringwald                                 client->state = HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD;
4196bcfb631SMilanka Ringwald                                 break;
4206bcfb631SMilanka Ringwald                             }
421ab116b1cSMilanka Ringwald                             if (get_boot_mouse_input_report(client) != NULL){
4222901a9b7SMilanka Ringwald                                 client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
4236bcfb631SMilanka Ringwald                                 break;
4246bcfb631SMilanka Ringwald                             }
4256bcfb631SMilanka Ringwald                             hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
4266bcfb631SMilanka Ringwald                             hids_finalize_client(client);
4276bcfb631SMilanka Ringwald                             break;
4286bcfb631SMilanka Ringwald                         default:
4296bcfb631SMilanka Ringwald                             break;
4306bcfb631SMilanka Ringwald                     }
4316bcfb631SMilanka Ringwald                     break;
4326bcfb631SMilanka Ringwald 
4336bcfb631SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED:
434ab116b1cSMilanka Ringwald                     boot_keyboard_report = get_boot_keyboard_input_report(client);
435ab116b1cSMilanka Ringwald                     if (boot_keyboard_report != NULL){
4366bcfb631SMilanka Ringwald                         // setup listener
437ab116b1cSMilanka Ringwald                         characteristic.value_handle = boot_keyboard_report->value_handle;
438ab116b1cSMilanka Ringwald                         gatt_client_listen_for_characteristic_value_updates(&boot_keyboard_report->notifications, &handle_boot_keyboard_hid_event, client->con_handle, &characteristic);
4392901a9b7SMilanka Ringwald                     }
4402901a9b7SMilanka Ringwald 
441ab116b1cSMilanka Ringwald                     // check if there is mouse input report
442ab116b1cSMilanka Ringwald                     boot_mouse_report = get_boot_mouse_input_report(client);
443ab116b1cSMilanka Ringwald                     if (boot_mouse_report != NULL){
4442901a9b7SMilanka Ringwald                         client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
4452901a9b7SMilanka Ringwald                         break;
4462901a9b7SMilanka Ringwald                     }
4472901a9b7SMilanka Ringwald 
448ab116b1cSMilanka Ringwald                     // if none of the reports is found bail out
449ab116b1cSMilanka Ringwald                     if (!boot_mouse_report && !boot_keyboard_report){
450ab116b1cSMilanka Ringwald                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
451ab116b1cSMilanka Ringwald                         hids_finalize_client(client);
452ab116b1cSMilanka Ringwald                         break;
453ab116b1cSMilanka Ringwald                     }
454ab116b1cSMilanka Ringwald 
4556bcfb631SMilanka Ringwald                     // set protocol
4566bcfb631SMilanka Ringwald                     if (client->protocol_mode_value_handle != 0){
4572901a9b7SMilanka Ringwald                         client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
4586bcfb631SMilanka Ringwald                     } else {
4592901a9b7SMilanka Ringwald                         client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
4606bcfb631SMilanka Ringwald                         client->state = HIDS_CLIENT_STATE_CONNECTED;
4616bcfb631SMilanka Ringwald                         hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
4626bcfb631SMilanka Ringwald                     }
4632901a9b7SMilanka Ringwald                     hids_run_for_client(client);
4646bcfb631SMilanka Ringwald                     break;
4656bcfb631SMilanka Ringwald 
4666bcfb631SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_MOUSE_ENABLED:
467ab116b1cSMilanka Ringwald                     boot_mouse_report = get_boot_mouse_input_report(client);
468ab116b1cSMilanka Ringwald                     if (boot_mouse_report != NULL){
4696bcfb631SMilanka Ringwald                         // setup listener
470ab116b1cSMilanka Ringwald                         characteristic.value_handle = boot_mouse_report->value_handle;
471ab116b1cSMilanka Ringwald                         gatt_client_listen_for_characteristic_value_updates(&boot_mouse_report->notifications, &handle_boot_mouse_hid_event, client->con_handle, &characteristic);
472ab116b1cSMilanka Ringwald                     }
473ab116b1cSMilanka Ringwald 
474ab116b1cSMilanka Ringwald                     boot_keyboard_report = get_boot_keyboard_input_report(client);
475ab116b1cSMilanka Ringwald                     if (!boot_mouse_report && !boot_keyboard_report){
4762901a9b7SMilanka Ringwald                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
4772901a9b7SMilanka Ringwald                         hids_finalize_client(client);
4782901a9b7SMilanka Ringwald                         break;
4792901a9b7SMilanka Ringwald                     }
4806bcfb631SMilanka Ringwald 
4816bcfb631SMilanka Ringwald                     if (client->protocol_mode_value_handle != 0){
4822901a9b7SMilanka Ringwald                         client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
4836bcfb631SMilanka Ringwald                     } else {
4842901a9b7SMilanka Ringwald                         client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
4856bcfb631SMilanka Ringwald                         client->state = HIDS_CLIENT_STATE_CONNECTED;
4866bcfb631SMilanka Ringwald                         hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
4876bcfb631SMilanka Ringwald                     }
4886bcfb631SMilanka Ringwald                     break;
489cf26c8fbSMilanka Ringwald                 default:
490cf26c8fbSMilanka Ringwald                     break;
491cf26c8fbSMilanka Ringwald             }
492cf26c8fbSMilanka Ringwald             break;
493cf26c8fbSMilanka Ringwald 
494cf26c8fbSMilanka Ringwald         default:
495cf26c8fbSMilanka Ringwald             break;
496cf26c8fbSMilanka Ringwald     }
4976bcfb631SMilanka Ringwald 
4986bcfb631SMilanka Ringwald     if (client != NULL){
4996bcfb631SMilanka Ringwald         hids_run_for_client(client);
5006bcfb631SMilanka Ringwald     }
501cf26c8fbSMilanka Ringwald }
502cf26c8fbSMilanka Ringwald 
5036bcfb631SMilanka Ringwald 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){
504cf26c8fbSMilanka Ringwald     btstack_assert(packet_handler != NULL);
505cf26c8fbSMilanka Ringwald 
506cf26c8fbSMilanka Ringwald     hids_client_t * client = hids_get_client_for_con_handle(con_handle);
507cf26c8fbSMilanka Ringwald     if (client != NULL){
508cf26c8fbSMilanka Ringwald         return ERROR_CODE_COMMAND_DISALLOWED;
509cf26c8fbSMilanka Ringwald     }
510cf26c8fbSMilanka Ringwald 
511cf26c8fbSMilanka Ringwald     uint16_t cid = hids_get_next_cid();
512cf26c8fbSMilanka Ringwald     if (hids_cid != NULL) {
513cf26c8fbSMilanka Ringwald         *hids_cid = cid;
514cf26c8fbSMilanka Ringwald     }
515cf26c8fbSMilanka Ringwald 
516cf26c8fbSMilanka Ringwald     client = hids_create_client(con_handle, cid);
517cf26c8fbSMilanka Ringwald     if (client == NULL) {
518cf26c8fbSMilanka Ringwald         return BTSTACK_MEMORY_ALLOC_FAILED;
519cf26c8fbSMilanka Ringwald     }
520cf26c8fbSMilanka Ringwald 
5216bcfb631SMilanka Ringwald     client->required_protocol_mode = protocol_mode;
522cf26c8fbSMilanka Ringwald     client->client_handler = packet_handler;
523cf26c8fbSMilanka Ringwald     client->state = HIDS_CLIENT_STATE_W2_QUERY_SERVICE;
524cf26c8fbSMilanka Ringwald 
525cf26c8fbSMilanka Ringwald     hids_run_for_client(client);
526cf26c8fbSMilanka Ringwald     return ERROR_CODE_SUCCESS;
527cf26c8fbSMilanka Ringwald }
528cf26c8fbSMilanka Ringwald 
529cf26c8fbSMilanka Ringwald uint8_t hids_client_disconnect(uint16_t hids_cid){
530cf26c8fbSMilanka Ringwald     hids_client_t * client = hids_get_client_for_cid(hids_cid);
531cf26c8fbSMilanka Ringwald     if (client == NULL){
532cf26c8fbSMilanka Ringwald         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
533cf26c8fbSMilanka Ringwald     }
534cf26c8fbSMilanka Ringwald     // finalize connection
535cf26c8fbSMilanka Ringwald     hids_finalize_client(client);
536cf26c8fbSMilanka Ringwald     return ERROR_CODE_SUCCESS;
537cf26c8fbSMilanka Ringwald }
538cf26c8fbSMilanka Ringwald 
5391624214bSMilanka Ringwald uint8_t hids_client_send_report(uint16_t hids_cid, uint8_t report_id, const uint8_t * report, uint8_t report_len){
5401624214bSMilanka Ringwald     hids_client_t * client = hids_get_client_for_cid(hids_cid);
5411624214bSMilanka Ringwald     if (client == NULL){
5421624214bSMilanka Ringwald         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
5431624214bSMilanka Ringwald     }
5441624214bSMilanka Ringwald 
5451624214bSMilanka Ringwald     if (client->state != HIDS_CLIENT_STATE_CONNECTED) {
5461624214bSMilanka Ringwald         return ERROR_CODE_COMMAND_DISALLOWED;
5471624214bSMilanka Ringwald     }
5481624214bSMilanka Ringwald 
5492fe59e00SMilanka Ringwald     uint8_t report_index = find_report_index_for_report_id_and_type(client, report_id, HID_REPORT_TYPE_OUTPUT);
5501624214bSMilanka Ringwald     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
5511624214bSMilanka Ringwald         return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE;
5521624214bSMilanka Ringwald     }
5531624214bSMilanka Ringwald 
5541624214bSMilanka Ringwald     uint16_t mtu;
5551624214bSMilanka Ringwald     uint8_t status = gatt_client_get_mtu(client->con_handle, &mtu);
5561624214bSMilanka Ringwald 
5571624214bSMilanka Ringwald     if (status != ERROR_CODE_SUCCESS){
5581624214bSMilanka Ringwald         return status;
5591624214bSMilanka Ringwald     }
5601624214bSMilanka Ringwald 
5611624214bSMilanka Ringwald     if (mtu - 2 < report_len){
5621624214bSMilanka Ringwald         return ERROR_CODE_PARAMETER_OUT_OF_MANDATORY_RANGE;
5631624214bSMilanka Ringwald     }
5641624214bSMilanka Ringwald 
5651624214bSMilanka Ringwald     client->state = HIDS_CLIENT_W2_SEND_REPORT;
5661624214bSMilanka Ringwald     client->active_report_index = report_index;
5671624214bSMilanka Ringwald     client->report = report;
5681624214bSMilanka Ringwald     client->report_len = report_len;
5691624214bSMilanka Ringwald 
5701624214bSMilanka Ringwald     hids_run_for_client(client);
5711624214bSMilanka Ringwald     return ERROR_CODE_SUCCESS;
5721624214bSMilanka Ringwald }
5731624214bSMilanka Ringwald 
574cf26c8fbSMilanka Ringwald void hids_client_init(void){}
575cf26c8fbSMilanka Ringwald 
576cf26c8fbSMilanka Ringwald void hids_client_deinit(void){}
577