xref: /btstack/src/ble/gatt-service/hids_client.c (revision af2241c2c01b038fbeb91dad56ed2c7b1d3b8df8)
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 
42708c69d2SMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
43708c69d2SMilanka Ringwald #include <stdio.h>
44708c69d2SMilanka Ringwald #endif
45708c69d2SMilanka Ringwald 
46fc975d0eSMilanka Ringwald #include <stdint.h>
47fc975d0eSMilanka Ringwald #include <string.h>
48fc975d0eSMilanka Ringwald 
49cf26c8fbSMilanka Ringwald #include "ble/gatt-service/hids_client.h"
50fc975d0eSMilanka Ringwald 
51cf26c8fbSMilanka Ringwald #include "btstack_memory.h"
52fc975d0eSMilanka Ringwald #include "ble/att_db.h"
53fc975d0eSMilanka Ringwald #include "ble/core.h"
54fc975d0eSMilanka Ringwald #include "ble/gatt_client.h"
55fc975d0eSMilanka Ringwald #include "ble/sm.h"
56cf26c8fbSMilanka Ringwald #include "bluetooth_gatt.h"
57fc975d0eSMilanka Ringwald #include "btstack_debug.h"
58fc975d0eSMilanka Ringwald #include "btstack_event.h"
59fc975d0eSMilanka Ringwald #include "btstack_run_loop.h"
60fc975d0eSMilanka Ringwald #include "gap.h"
61fc975d0eSMilanka Ringwald 
6270093cf5SMilanka Ringwald #define HID_REPORT_MODE_REPORT_ID               3
6370093cf5SMilanka Ringwald #define HID_REPORT_MODE_REPORT_MAP_ID           4
6470093cf5SMilanka Ringwald #define HID_REPORT_MODE_HID_INFORMATION_ID      5
6570093cf5SMilanka Ringwald #define HID_REPORT_MODE_HID_CONTROL_POINT_ID    6
6670093cf5SMilanka Ringwald 
67da142a6fSMilanka Ringwald static btstack_linked_list_t clients;
68da142a6fSMilanka Ringwald static uint16_t hids_cid_counter = 0;
69da142a6fSMilanka Ringwald 
70da142a6fSMilanka Ringwald static uint8_t * hids_client_descriptor_storage;
71da142a6fSMilanka Ringwald static uint16_t  hids_client_descriptor_storage_len;
72da142a6fSMilanka Ringwald 
73cf26c8fbSMilanka Ringwald static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
74cf26c8fbSMilanka Ringwald 
75556456ccSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
76556456ccSMilanka Ringwald static char * hid_characteristic_name(uint16_t uuid){
77556456ccSMilanka Ringwald     switch (uuid){
78556456ccSMilanka Ringwald         case ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE:
79556456ccSMilanka Ringwald             return "PROTOCOL_MODE";
80556456ccSMilanka Ringwald 
81556456ccSMilanka Ringwald         case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT:
82556456ccSMilanka Ringwald             return "BOOT_KEYBOARD_INPUT_REPORT";
83556456ccSMilanka Ringwald 
84556456ccSMilanka Ringwald         case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT:
85556456ccSMilanka Ringwald             return "BOOT_MOUSE_INPUT_REPORT";
86556456ccSMilanka Ringwald 
87556456ccSMilanka Ringwald         case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT:
88556456ccSMilanka Ringwald             return "BOOT_KEYBOARD_OUTPUT_REPORT";
89556456ccSMilanka Ringwald 
90556456ccSMilanka Ringwald         case ORG_BLUETOOTH_CHARACTERISTIC_REPORT:
91556456ccSMilanka Ringwald             return "REPORT";
92556456ccSMilanka Ringwald 
93556456ccSMilanka Ringwald         case ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP:
94556456ccSMilanka Ringwald             return "REPORT_MAP";
95556456ccSMilanka Ringwald 
96556456ccSMilanka Ringwald         case ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION:
97556456ccSMilanka Ringwald             return "HID_INFORMATION";
98556456ccSMilanka Ringwald 
99556456ccSMilanka Ringwald         case ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT:
100556456ccSMilanka Ringwald             return "HID_CONTROL_POINT";
101556456ccSMilanka Ringwald         default:
102556456ccSMilanka Ringwald             return "UKNOWN";
103556456ccSMilanka Ringwald     }
104556456ccSMilanka Ringwald }
105556456ccSMilanka Ringwald #endif
106556456ccSMilanka Ringwald 
107da142a6fSMilanka Ringwald static hids_client_t * hids_get_client_for_con_handle(hci_con_handle_t con_handle){
108da142a6fSMilanka Ringwald     btstack_linked_list_iterator_t it;
109da142a6fSMilanka Ringwald     btstack_linked_list_iterator_init(&it, &clients);
110da142a6fSMilanka Ringwald     while (btstack_linked_list_iterator_has_next(&it)){
111da142a6fSMilanka Ringwald         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
112da142a6fSMilanka Ringwald         if (client->con_handle != con_handle) continue;
113da142a6fSMilanka Ringwald         return client;
114da142a6fSMilanka Ringwald     }
115da142a6fSMilanka Ringwald     return NULL;
116da142a6fSMilanka Ringwald }
117da142a6fSMilanka Ringwald 
118da142a6fSMilanka Ringwald static hids_client_t * hids_get_client_for_cid(uint16_t hids_cid){
119da142a6fSMilanka Ringwald     btstack_linked_list_iterator_t it;
120da142a6fSMilanka Ringwald     btstack_linked_list_iterator_init(&it, &clients);
121da142a6fSMilanka Ringwald     while (btstack_linked_list_iterator_has_next(&it)){
122da142a6fSMilanka Ringwald         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
123da142a6fSMilanka Ringwald         if (client->cid != hids_cid) continue;
124da142a6fSMilanka Ringwald         return client;
125da142a6fSMilanka Ringwald     }
126da142a6fSMilanka Ringwald     return NULL;
127da142a6fSMilanka Ringwald }
128da142a6fSMilanka Ringwald 
129da142a6fSMilanka Ringwald 
130da142a6fSMilanka Ringwald // START Descriptor Storage Util
131da142a6fSMilanka Ringwald 
132da142a6fSMilanka Ringwald static uint16_t hids_client_descriptor_storage_get_available_space(void){
133da142a6fSMilanka Ringwald     // assumes all descriptors are back to back
134da142a6fSMilanka Ringwald     uint16_t free_space = hids_client_descriptor_storage_len;
135da142a6fSMilanka Ringwald     uint8_t i;
136da142a6fSMilanka Ringwald 
137da142a6fSMilanka Ringwald     btstack_linked_list_iterator_t it;
138da142a6fSMilanka Ringwald     btstack_linked_list_iterator_init(&it, &clients);
139da142a6fSMilanka Ringwald     while (btstack_linked_list_iterator_has_next(&it)){
140da142a6fSMilanka Ringwald         hids_client_t * client = (hids_client_t *)btstack_linked_list_iterator_next(&it);
141da142a6fSMilanka Ringwald         for (i = 0; i < client->num_instances; i++){
142da142a6fSMilanka Ringwald             free_space -= client->services[i].hid_descriptor_len;
143da142a6fSMilanka Ringwald         }
144da142a6fSMilanka Ringwald     }
145da142a6fSMilanka Ringwald     return free_space;
146da142a6fSMilanka Ringwald }
147da142a6fSMilanka Ringwald 
148da142a6fSMilanka Ringwald static void hids_client_descriptor_storage_init(hids_client_t * client, uint8_t service_index){
149da142a6fSMilanka Ringwald     client->services[service_index].hid_descriptor_len = 0;
150da142a6fSMilanka Ringwald     client->services[service_index].hid_descriptor_max_len = hids_client_descriptor_storage_get_available_space();
151da142a6fSMilanka Ringwald     client->services[service_index].hid_descriptor_offset = hids_client_descriptor_storage_len - client->services[service_index].hid_descriptor_max_len;
152da142a6fSMilanka Ringwald }
153da142a6fSMilanka Ringwald 
154da142a6fSMilanka Ringwald static bool hids_client_descriptor_storage_store(hids_client_t * client, uint8_t service_index, uint8_t byte){
155da142a6fSMilanka Ringwald     if (client->services[service_index].hid_descriptor_len >= client->services[service_index].hid_descriptor_max_len) return false;
156da142a6fSMilanka Ringwald 
157da142a6fSMilanka Ringwald     hids_client_descriptor_storage[client->services[service_index].hid_descriptor_offset + client->services[service_index].hid_descriptor_len] = byte;
158da142a6fSMilanka Ringwald     client->services[service_index].hid_descriptor_len++;
159da142a6fSMilanka Ringwald     return true;
160da142a6fSMilanka Ringwald }
161da142a6fSMilanka Ringwald 
162da142a6fSMilanka Ringwald static void hids_client_descriptor_storage_delete(hids_client_t * client){
163da142a6fSMilanka Ringwald     uint8_t service_index = 0;
164da142a6fSMilanka Ringwald     uint16_t next_offset = 0;
165da142a6fSMilanka Ringwald 
166da142a6fSMilanka Ringwald     for (service_index = 0; service_index < client->num_instances; service_index++){
167da142a6fSMilanka Ringwald         next_offset += client->services[service_index].hid_descriptor_offset + client->services[service_index].hid_descriptor_len;
168da142a6fSMilanka Ringwald         client->services[service_index].hid_descriptor_len = 0;
169da142a6fSMilanka Ringwald         client->services[service_index].hid_descriptor_offset = 0;
170da142a6fSMilanka Ringwald     }
171da142a6fSMilanka Ringwald 
172da142a6fSMilanka Ringwald     memmove(&hids_client_descriptor_storage[client->services[0].hid_descriptor_offset],
173da142a6fSMilanka Ringwald             &hids_client_descriptor_storage[next_offset],
174da142a6fSMilanka Ringwald             hids_client_descriptor_storage_len - next_offset);
175da142a6fSMilanka Ringwald 
176da142a6fSMilanka Ringwald     uint8_t i;
177da142a6fSMilanka Ringwald     btstack_linked_list_iterator_t it;
178da142a6fSMilanka Ringwald     btstack_linked_list_iterator_init(&it, &clients);
179da142a6fSMilanka Ringwald     while (btstack_linked_list_iterator_has_next(&it)){
180da142a6fSMilanka Ringwald         hids_client_t * conn = (hids_client_t *)btstack_linked_list_iterator_next(&it);
181da142a6fSMilanka Ringwald         for (i = 0; i < client->num_instances; i++){
182da142a6fSMilanka Ringwald             if (conn->services[i].hid_descriptor_offset >= next_offset){
183da142a6fSMilanka Ringwald                 conn->services[i].hid_descriptor_offset = next_offset;
184da142a6fSMilanka Ringwald                 next_offset += conn->services[service_index].hid_descriptor_len;
185da142a6fSMilanka Ringwald             }
186da142a6fSMilanka Ringwald         }
187da142a6fSMilanka Ringwald     }
188da142a6fSMilanka Ringwald }
189da142a6fSMilanka Ringwald 
190da142a6fSMilanka Ringwald const uint8_t * hids_client_descriptor_storage_get_descriptor_data(uint16_t hids_cid, uint8_t service_index){
191da142a6fSMilanka Ringwald     hids_client_t * client = hids_get_client_for_cid(hids_cid);
192021192e1SMilanka Ringwald     if (client == NULL){
193021192e1SMilanka Ringwald         return NULL;
194021192e1SMilanka Ringwald     }
195021192e1SMilanka Ringwald     if (service_index >= client->num_instances){
196da142a6fSMilanka Ringwald         return NULL;
197da142a6fSMilanka Ringwald     }
198da142a6fSMilanka Ringwald     return &hids_client_descriptor_storage[client->services[service_index].hid_descriptor_offset];
199da142a6fSMilanka Ringwald }
200da142a6fSMilanka Ringwald 
201da142a6fSMilanka Ringwald uint16_t hids_client_descriptor_storage_get_descriptor_len(uint16_t hids_cid, uint8_t service_index){
202da142a6fSMilanka Ringwald     hids_client_t * client = hids_get_client_for_cid(hids_cid);
203021192e1SMilanka Ringwald     if (client == NULL){
204021192e1SMilanka Ringwald         return 0;
205021192e1SMilanka Ringwald     }
206021192e1SMilanka Ringwald     if (service_index >= client->num_instances){
207da142a6fSMilanka Ringwald         return 0;
208da142a6fSMilanka Ringwald     }
209da142a6fSMilanka Ringwald     return client->services[service_index].hid_descriptor_len;
210da142a6fSMilanka Ringwald }
211da142a6fSMilanka Ringwald 
212da142a6fSMilanka Ringwald // END Descriptor Storage Util
213da142a6fSMilanka Ringwald 
214cf26c8fbSMilanka Ringwald static uint16_t hids_get_next_cid(void){
215cf26c8fbSMilanka Ringwald     if (hids_cid_counter == 0xffff) {
216cf26c8fbSMilanka Ringwald         hids_cid_counter = 1;
217cf26c8fbSMilanka Ringwald     } else {
218cf26c8fbSMilanka Ringwald         hids_cid_counter++;
219cf26c8fbSMilanka Ringwald     }
220cf26c8fbSMilanka Ringwald     return hids_cid_counter;
221fc975d0eSMilanka Ringwald }
222fc975d0eSMilanka Ringwald 
223ab116b1cSMilanka Ringwald static uint8_t find_report_index_for_value_handle(hids_client_t * client, uint16_t value_handle){
224ab116b1cSMilanka Ringwald     uint8_t i;
225a381a464SMilanka Ringwald     for (i = 0; i < client->num_reports; i++){
226ab116b1cSMilanka Ringwald         if (client->reports[i].value_handle == value_handle){
227ab116b1cSMilanka Ringwald             return i;
228ab116b1cSMilanka Ringwald         }
229ab116b1cSMilanka Ringwald     }
230ab116b1cSMilanka Ringwald     return HIDS_CLIENT_INVALID_REPORT_INDEX;
231ab116b1cSMilanka Ringwald }
232ab116b1cSMilanka Ringwald 
23319146789SMilanka Ringwald static uint8_t find_external_report_index_for_value_handle(hids_client_t * client, uint16_t value_handle){
23419146789SMilanka Ringwald     uint8_t i;
23519146789SMilanka Ringwald     for (i = 0; i < client->num_external_reports; i++){
23619146789SMilanka Ringwald         if (client->external_reports[i].value_handle == value_handle){
23719146789SMilanka Ringwald             return i;
23819146789SMilanka Ringwald         }
23919146789SMilanka Ringwald     }
24019146789SMilanka Ringwald     return HIDS_CLIENT_INVALID_REPORT_INDEX;
24119146789SMilanka Ringwald }
24219146789SMilanka Ringwald 
24319146789SMilanka Ringwald static bool external_report_index_for_uuid_exists(hids_client_t * client, uint16_t uuid16){
24419146789SMilanka Ringwald     uint8_t i;
24519146789SMilanka Ringwald     for (i = 0; i < client->num_external_reports; i++){
24619146789SMilanka Ringwald         if (client->external_reports[i].external_report_reference_uuid == uuid16){
24719146789SMilanka Ringwald             return true;
24819146789SMilanka Ringwald         }
24919146789SMilanka Ringwald     }
25019146789SMilanka Ringwald     return false;
25119146789SMilanka Ringwald }
25219146789SMilanka Ringwald 
253835a13f1SMilanka Ringwald static uint8_t find_report_index_for_report_id(hids_client_t * client, uint8_t report_id){
25483d7ed1cSMilanka Ringwald     uint8_t i;
255835a13f1SMilanka Ringwald 
25683d7ed1cSMilanka Ringwald     switch (client->protocol_mode){
25783d7ed1cSMilanka Ringwald         case HID_PROTOCOL_MODE_BOOT:
25883d7ed1cSMilanka Ringwald             for (i = 0; i < client->num_reports; i++){
25983d7ed1cSMilanka Ringwald                 if (!client->reports[i].boot_report){
26083d7ed1cSMilanka Ringwald                     continue;
26183d7ed1cSMilanka Ringwald                 }
262835a13f1SMilanka Ringwald                 if (client->reports[i].report_id == report_id){
26383d7ed1cSMilanka Ringwald                     return i;
26483d7ed1cSMilanka Ringwald                 }
26583d7ed1cSMilanka Ringwald             }
26683d7ed1cSMilanka Ringwald             break;
26783d7ed1cSMilanka Ringwald 
26883d7ed1cSMilanka Ringwald         default:
26983d7ed1cSMilanka Ringwald             for (i = 0; i < client->num_reports; i++){
27083d7ed1cSMilanka Ringwald                 if (client->reports[i].boot_report){
27183d7ed1cSMilanka Ringwald                     continue;
27283d7ed1cSMilanka Ringwald                 }
273835a13f1SMilanka Ringwald                 if (client->reports[i].report_id == report_id){
27483d7ed1cSMilanka Ringwald                     return i;
27583d7ed1cSMilanka Ringwald                 }
27683d7ed1cSMilanka Ringwald             }
27783d7ed1cSMilanka Ringwald             break;
27883d7ed1cSMilanka Ringwald     }
27983d7ed1cSMilanka Ringwald     return HIDS_CLIENT_INVALID_REPORT_INDEX;
28083d7ed1cSMilanka Ringwald }
28119146789SMilanka Ringwald 
28219146789SMilanka Ringwald 
283ab116b1cSMilanka 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){
284ab116b1cSMilanka Ringwald     uint8_t i;
28583d7ed1cSMilanka Ringwald     switch (client->protocol_mode){
28683d7ed1cSMilanka Ringwald         case HID_PROTOCOL_MODE_BOOT:
287a381a464SMilanka Ringwald             for (i = 0; i < client->num_reports; i++){
28883d7ed1cSMilanka Ringwald                 if (!client->reports[i].boot_report){
28983d7ed1cSMilanka Ringwald                     continue;
29083d7ed1cSMilanka Ringwald                 }
291ab116b1cSMilanka Ringwald                 if ((client->reports[i].report_id == report_id) && (client->reports[i].report_type == report_type)){
292ab116b1cSMilanka Ringwald                     return i;
293ab116b1cSMilanka Ringwald                 }
294ab116b1cSMilanka Ringwald             }
29583d7ed1cSMilanka Ringwald             break;
29683d7ed1cSMilanka Ringwald 
29783d7ed1cSMilanka Ringwald         default:
29883d7ed1cSMilanka Ringwald             for (i = 0; i < client->num_reports; i++){
29983d7ed1cSMilanka Ringwald                 if (client->reports[i].boot_report){
30083d7ed1cSMilanka Ringwald                     continue;
30183d7ed1cSMilanka Ringwald                 }
30283d7ed1cSMilanka Ringwald                 if ((client->reports[i].report_id == report_id) && (client->reports[i].report_type == report_type)){
30383d7ed1cSMilanka Ringwald                     return i;
30483d7ed1cSMilanka Ringwald                 }
30583d7ed1cSMilanka Ringwald             }
30683d7ed1cSMilanka Ringwald             break;
30783d7ed1cSMilanka Ringwald     }
308ab116b1cSMilanka Ringwald     return HIDS_CLIENT_INVALID_REPORT_INDEX;
309ab116b1cSMilanka Ringwald }
310ab116b1cSMilanka Ringwald 
311021192e1SMilanka Ringwald static uint8_t hids_client_add_characteristic(hids_client_t * client, gatt_client_characteristic_t * characteristic, uint8_t report_id, hid_report_type_t report_type, bool boot_report){
312ab116b1cSMilanka Ringwald 
31319146789SMilanka Ringwald     uint8_t report_index = find_external_report_index_for_value_handle(client, characteristic->value_handle);
314ab116b1cSMilanka Ringwald     if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
3153322b222SMilanka Ringwald         return report_index;
316ab116b1cSMilanka Ringwald     }
31728da36a6SMilanka Ringwald     report_index = client->num_reports;
318ab116b1cSMilanka Ringwald 
31928da36a6SMilanka Ringwald     if (report_index < HIDS_CLIENT_NUM_REPORTS) {
32028da36a6SMilanka Ringwald         client->reports[report_index].value_handle = characteristic->value_handle;
32128da36a6SMilanka Ringwald         client->reports[report_index].end_handle = characteristic->end_handle;
32228da36a6SMilanka Ringwald         client->reports[report_index].properties = characteristic->properties;
323ab116b1cSMilanka Ringwald 
32428da36a6SMilanka Ringwald         client->reports[report_index].service_index = client->service_index;
32528da36a6SMilanka Ringwald         client->reports[report_index].report_id = report_id;
32628da36a6SMilanka Ringwald         client->reports[report_index].report_type = report_type;
327021192e1SMilanka Ringwald         client->reports[report_index].boot_report = boot_report;
32870093cf5SMilanka Ringwald 
329021192e1SMilanka Ringwald         log_info("add index %d, id %d, type %d, value handle 0x%02x, properties 0x%02x", report_index, report_id, report_type, characteristic->value_handle, characteristic->properties);
330ab116b1cSMilanka Ringwald         client->num_reports++;
3313322b222SMilanka Ringwald         return report_index;
332ab116b1cSMilanka Ringwald     } else {
333ab116b1cSMilanka Ringwald         log_info("not enough storage, increase HIDS_CLIENT_NUM_REPORTS");
3343322b222SMilanka Ringwald         return HIDS_CLIENT_INVALID_REPORT_INDEX;
335ab116b1cSMilanka Ringwald     }
336ab116b1cSMilanka Ringwald }
337ab116b1cSMilanka Ringwald 
33819146789SMilanka Ringwald static uint8_t hids_client_add_external_report(hids_client_t * client, gatt_client_characteristic_descriptor_t * characteristic_descriptor){
339556456ccSMilanka Ringwald     uint8_t report_index = client->num_external_reports;
340556456ccSMilanka Ringwald 
341556456ccSMilanka Ringwald     if (report_index < HIDS_CLIENT_NUM_REPORTS) {
34219146789SMilanka Ringwald         client->external_reports[report_index].value_handle = characteristic_descriptor->handle;
343556456ccSMilanka Ringwald         client->external_reports[report_index].service_index = client->service_index;
34419146789SMilanka Ringwald 
345556456ccSMilanka Ringwald         client->num_external_reports++;
34619146789SMilanka Ringwald         log_info("add external index %d [%d], value handle 0x%02x", report_index, client->num_external_reports, characteristic_descriptor->handle);
347556456ccSMilanka Ringwald         return report_index;
348556456ccSMilanka Ringwald     } else {
349556456ccSMilanka Ringwald         log_info("not enough storage, increase HIDS_CLIENT_NUM_REPORTS");
350556456ccSMilanka Ringwald         return HIDS_CLIENT_INVALID_REPORT_INDEX;
35170093cf5SMilanka Ringwald     }
352556456ccSMilanka Ringwald }
353556456ccSMilanka Ringwald 
35470093cf5SMilanka Ringwald 
355021192e1SMilanka Ringwald static bool hid_clients_has_reports_in_report_mode(hids_client_t * client){
356021192e1SMilanka Ringwald     uint8_t i;
357021192e1SMilanka Ringwald     for (i = 0; i < client->num_reports; i++){
358021192e1SMilanka Ringwald         if (!client->reports[i].boot_report){
359021192e1SMilanka Ringwald             return true;
360ab116b1cSMilanka Ringwald         }
361021192e1SMilanka Ringwald     }
362021192e1SMilanka Ringwald     return false;
363021192e1SMilanka Ringwald }
364021192e1SMilanka Ringwald 
365021192e1SMilanka Ringwald static bool hid_clients_has_reports_in_boot_mode(hids_client_t * client){
366021192e1SMilanka Ringwald     uint8_t i;
367021192e1SMilanka Ringwald     for (i = 0; i < client->num_reports; i++){
368021192e1SMilanka Ringwald         if (client->reports[i].boot_report){
369021192e1SMilanka Ringwald             return true;
370021192e1SMilanka Ringwald         }
371021192e1SMilanka Ringwald     }
372021192e1SMilanka Ringwald     return false;
373ab116b1cSMilanka Ringwald }
374ab116b1cSMilanka Ringwald 
3757e1e6e7dSMilanka Ringwald static uint8_t hids_client_get_next_active_report_map_index(hids_client_t * client){
3767e1e6e7dSMilanka Ringwald     uint8_t i;
3773322b222SMilanka Ringwald     for (i = client->service_index; i < client->num_instances; i++){
3783322b222SMilanka Ringwald         if (client->services[i].report_map_value_handle != 0){
379556456ccSMilanka Ringwald             return i;
3803322b222SMilanka Ringwald         }
3813322b222SMilanka Ringwald     }
382556456ccSMilanka Ringwald     client->service_index = HIDS_CLIENT_INVALID_REPORT_INDEX;
383556456ccSMilanka Ringwald     return HIDS_CLIENT_INVALID_REPORT_INDEX;
3843322b222SMilanka Ringwald }
3853322b222SMilanka Ringwald 
3863322b222SMilanka Ringwald static bool hids_client_report_query_next_report_map(hids_client_t * client){
3873322b222SMilanka Ringwald     client->service_index++;
3883322b222SMilanka Ringwald     if (hids_client_get_next_active_report_map_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
389556456ccSMilanka Ringwald         client->state = HIDS_CLIENT_STATE_W2_READ_REPORT_MAP_HID_DESCRIPTOR;
3903322b222SMilanka Ringwald         return true;
3913322b222SMilanka Ringwald     }
3923322b222SMilanka Ringwald     return false;
3933322b222SMilanka Ringwald }
3943322b222SMilanka Ringwald 
3953322b222SMilanka Ringwald static bool hids_client_report_map_query_init(hids_client_t * client){
3963322b222SMilanka Ringwald     client->service_index = 0;
3973322b222SMilanka Ringwald 
3983322b222SMilanka Ringwald     if (hids_client_get_next_active_report_map_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
3993322b222SMilanka Ringwald         client->state = HIDS_CLIENT_STATE_W2_READ_REPORT_MAP_HID_DESCRIPTOR;
4003322b222SMilanka Ringwald         return true;
4013322b222SMilanka Ringwald     }
4023322b222SMilanka Ringwald     return false;
4033322b222SMilanka Ringwald }
4043322b222SMilanka Ringwald 
4053322b222SMilanka Ringwald static bool hids_client_report_query_next_report_map_uuid(hids_client_t * client){
406021192e1SMilanka Ringwald     client->report_index++;
40719146789SMilanka Ringwald     if (client->report_index < client->num_external_reports){
4083322b222SMilanka Ringwald         client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID;
409e7bd2dbeSMilanka Ringwald         return true;
410e7bd2dbeSMilanka Ringwald     }
411e7bd2dbeSMilanka Ringwald     return false;
412e7bd2dbeSMilanka Ringwald }
413e7bd2dbeSMilanka Ringwald 
4143322b222SMilanka Ringwald static bool hids_client_report_map_uuid_query_init(hids_client_t * client){
415021192e1SMilanka Ringwald     client->report_index = 0;
41619146789SMilanka Ringwald     if (client->num_external_reports > 0){
4173322b222SMilanka Ringwald         client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID;
4187e1e6e7dSMilanka Ringwald         return true;
4197e1e6e7dSMilanka Ringwald     }
4207e1e6e7dSMilanka Ringwald     return false;
4217e1e6e7dSMilanka Ringwald }
4227e1e6e7dSMilanka Ringwald 
4237e1e6e7dSMilanka Ringwald static uint8_t hids_client_get_next_report_index(hids_client_t * client){
4247e1e6e7dSMilanka Ringwald     uint8_t i;
4257e1e6e7dSMilanka Ringwald     uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX;
426021192e1SMilanka Ringwald     switch (client->protocol_mode){
427021192e1SMilanka Ringwald         case HID_PROTOCOL_MODE_REPORT:
428021192e1SMilanka Ringwald             for (i = client->report_index; i < client->num_reports && (index == HIDS_CLIENT_INVALID_REPORT_INDEX); i++){
42970093cf5SMilanka Ringwald                 hids_client_report_t report = client->reports[i];
43070093cf5SMilanka Ringwald                 if (report.report_type == HID_REPORT_TYPE_RESERVED && report.report_id == HID_REPORT_MODE_REPORT_ID){
4317e1e6e7dSMilanka Ringwald                     index = i;
432021192e1SMilanka Ringwald                     client->service_index = report.service_index;
433021192e1SMilanka Ringwald                 }
434021192e1SMilanka Ringwald             }
435021192e1SMilanka Ringwald             break;
436021192e1SMilanka Ringwald         case HID_PROTOCOL_MODE_BOOT:
437021192e1SMilanka Ringwald             for (i = client->report_index; i < client->num_reports && (index == HIDS_CLIENT_INVALID_REPORT_INDEX); i++){
438021192e1SMilanka Ringwald                 hids_client_report_t report = client->reports[i];
439021192e1SMilanka Ringwald                 if (report.boot_report){
440021192e1SMilanka Ringwald                     index = i;
441021192e1SMilanka Ringwald                     client->service_index = report.service_index;
442021192e1SMilanka Ringwald                 }
443021192e1SMilanka Ringwald             }
444021192e1SMilanka Ringwald             break;
445021192e1SMilanka Ringwald         default:
4467e1e6e7dSMilanka Ringwald             break;
44770093cf5SMilanka Ringwald     }
448021192e1SMilanka Ringwald 
449021192e1SMilanka Ringwald     client->report_index = index;
4507e1e6e7dSMilanka Ringwald     return index;
4517e1e6e7dSMilanka Ringwald }
4527e1e6e7dSMilanka Ringwald 
4537e1e6e7dSMilanka Ringwald static bool hids_client_report_query_next_report(hids_client_t * client){
454021192e1SMilanka Ringwald     client->report_index++;
4557e1e6e7dSMilanka Ringwald     if (hids_client_get_next_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
45628da36a6SMilanka Ringwald         client->state = HIDS_CLIENT_STATE_W2_FIND_REPORT;
457e7bd2dbeSMilanka Ringwald         return true;
45870093cf5SMilanka Ringwald     }
4597e1e6e7dSMilanka Ringwald     return false;
4607e1e6e7dSMilanka Ringwald }
4617e1e6e7dSMilanka Ringwald 
4627e1e6e7dSMilanka Ringwald static bool hids_client_report_query_init(hids_client_t * client){
463021192e1SMilanka Ringwald     client->report_index = 0;
4647e1e6e7dSMilanka Ringwald 
4657e1e6e7dSMilanka Ringwald     if (hids_client_get_next_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
46628da36a6SMilanka Ringwald         client->state = HIDS_CLIENT_STATE_W2_FIND_REPORT;
4677e1e6e7dSMilanka Ringwald         return true;
4687e1e6e7dSMilanka Ringwald     }
469e7bd2dbeSMilanka Ringwald     return false;
47070093cf5SMilanka Ringwald }
471ab116b1cSMilanka Ringwald 
472021192e1SMilanka Ringwald static uint8_t hids_client_get_next_notification_report_index(hids_client_t * client){
473021192e1SMilanka Ringwald     uint8_t i;
474021192e1SMilanka Ringwald     uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX;
475021192e1SMilanka Ringwald 
476021192e1SMilanka Ringwald     switch (client->protocol_mode){
477021192e1SMilanka Ringwald         case HID_PROTOCOL_MODE_REPORT:
478021192e1SMilanka Ringwald             for (i = client->report_index; i < client->num_reports && (index == HIDS_CLIENT_INVALID_REPORT_INDEX); i++){
479021192e1SMilanka Ringwald                 hids_client_report_t report = client->reports[i];
480021192e1SMilanka Ringwald                 if (report.report_type != HID_REPORT_TYPE_INPUT){
481021192e1SMilanka Ringwald                     continue;
482021192e1SMilanka Ringwald                 }
483021192e1SMilanka Ringwald                 if (!report.boot_report){
484021192e1SMilanka Ringwald                     index = i;
485021192e1SMilanka Ringwald                 }
486021192e1SMilanka Ringwald             }
487021192e1SMilanka Ringwald             break;
488021192e1SMilanka Ringwald 
489021192e1SMilanka Ringwald         case HID_PROTOCOL_MODE_BOOT:
490021192e1SMilanka Ringwald             for (i = client->report_index; i < client->num_reports && (index == HIDS_CLIENT_INVALID_REPORT_INDEX); i++){
491021192e1SMilanka Ringwald                 hids_client_report_t report = client->reports[i];
492021192e1SMilanka Ringwald                 if (report.report_type != HID_REPORT_TYPE_INPUT){
493021192e1SMilanka Ringwald                     continue;
494021192e1SMilanka Ringwald                 }
495021192e1SMilanka Ringwald                 if (report.boot_report){
496021192e1SMilanka Ringwald                     index = i;
497021192e1SMilanka Ringwald                 }
498021192e1SMilanka Ringwald             }
499021192e1SMilanka Ringwald             break;
500021192e1SMilanka Ringwald 
501021192e1SMilanka Ringwald         default:
502021192e1SMilanka Ringwald             break;
503021192e1SMilanka Ringwald     }
504021192e1SMilanka Ringwald 
505021192e1SMilanka Ringwald     client->report_index = index;
506021192e1SMilanka Ringwald     return index;
507021192e1SMilanka Ringwald }
508021192e1SMilanka Ringwald 
509021192e1SMilanka Ringwald static bool hids_client_report_next_notification_report_index(hids_client_t * client){
510021192e1SMilanka Ringwald     client->report_index++;
511021192e1SMilanka Ringwald     if (hids_client_get_next_notification_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
512021192e1SMilanka Ringwald         client->state = HIDS_CLIENT_STATE_W2_ENABLE_INPUT_REPORTS;
513021192e1SMilanka Ringwald         return true;
514021192e1SMilanka Ringwald     }
515021192e1SMilanka Ringwald     return false;
516021192e1SMilanka Ringwald }
517021192e1SMilanka Ringwald 
518021192e1SMilanka Ringwald static bool hids_client_report_notifications_init(hids_client_t * client){
519021192e1SMilanka Ringwald     client->report_index = 0;
520021192e1SMilanka Ringwald 
521021192e1SMilanka Ringwald     if (hids_client_get_next_notification_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
522021192e1SMilanka Ringwald         client->state = HIDS_CLIENT_STATE_W2_ENABLE_INPUT_REPORTS;
523021192e1SMilanka Ringwald         return true;
524021192e1SMilanka Ringwald     }
525021192e1SMilanka Ringwald     return false;
526021192e1SMilanka Ringwald }
527021192e1SMilanka Ringwald 
528cf26c8fbSMilanka Ringwald static hids_client_t * hids_create_client(hci_con_handle_t con_handle, uint16_t cid){
529cf26c8fbSMilanka Ringwald     hids_client_t * client = btstack_memory_hids_client_get();
530cf26c8fbSMilanka Ringwald     if (!client){
531cf26c8fbSMilanka Ringwald         log_error("Not enough memory to create client");
532cf26c8fbSMilanka Ringwald         return NULL;
533cf26c8fbSMilanka Ringwald     }
534cf26c8fbSMilanka Ringwald     client->state = HIDS_CLIENT_STATE_IDLE;
535cf26c8fbSMilanka Ringwald     client->cid = cid;
536cf26c8fbSMilanka Ringwald     client->con_handle = con_handle;
537fc975d0eSMilanka Ringwald 
538cf26c8fbSMilanka Ringwald     btstack_linked_list_add(&clients, (btstack_linked_item_t *) client);
539cf26c8fbSMilanka Ringwald     return client;
540fc975d0eSMilanka Ringwald }
541fc975d0eSMilanka Ringwald 
542cf26c8fbSMilanka Ringwald static void hids_finalize_client(hids_client_t * client){
543021192e1SMilanka Ringwald     // stop listening
544021192e1SMilanka Ringwald     uint8_t i;
545021192e1SMilanka Ringwald     for (i = 0; i < client->num_reports; i++){
546021192e1SMilanka Ringwald         gatt_client_stop_listening_for_characteristic_value_updates(&client->reports[i].notification_listener);
547021192e1SMilanka Ringwald     }
548021192e1SMilanka Ringwald 
549da142a6fSMilanka Ringwald     hids_client_descriptor_storage_delete(client);
550cf26c8fbSMilanka Ringwald     btstack_linked_list_remove(&clients, (btstack_linked_item_t *) client);
551cf26c8fbSMilanka Ringwald     btstack_memory_hids_client_free(client);
552fc975d0eSMilanka Ringwald }
553cf26c8fbSMilanka Ringwald 
554cf26c8fbSMilanka Ringwald 
555cf26c8fbSMilanka Ringwald static void hids_emit_connection_established(hids_client_t * client, uint8_t status){
5566bcfb631SMilanka Ringwald     uint8_t event[8];
557cf26c8fbSMilanka Ringwald     int pos = 0;
558cf26c8fbSMilanka Ringwald     event[pos++] = HCI_EVENT_GATTSERVICE_META;
559cf26c8fbSMilanka Ringwald     event[pos++] = sizeof(event) - 2;
560cf26c8fbSMilanka Ringwald     event[pos++] = GATTSERVICE_SUBEVENT_HID_SERVICE_CONNECTED;
561cf26c8fbSMilanka Ringwald     little_endian_store_16(event, pos, client->cid);
562cf26c8fbSMilanka Ringwald     pos += 2;
563cf26c8fbSMilanka Ringwald     event[pos++] = status;
5646bcfb631SMilanka Ringwald     event[pos++] = client->protocol_mode;
565cf26c8fbSMilanka Ringwald     event[pos++] = client->num_instances;
566cf26c8fbSMilanka Ringwald     (*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
567cf26c8fbSMilanka Ringwald }
568cf26c8fbSMilanka Ringwald 
569021192e1SMilanka Ringwald static void hids_client_setup_report_event(hids_client_t * client, uint8_t report_index, uint8_t *buffer, uint16_t report_len){
570021192e1SMilanka Ringwald     uint16_t pos = 0;
571021192e1SMilanka Ringwald     buffer[pos++] = HCI_EVENT_GATTSERVICE_META;
572021192e1SMilanka Ringwald     pos++;  // skip len
573021192e1SMilanka Ringwald     buffer[pos++] = GATTSERVICE_SUBEVENT_HID_REPORT;
574021192e1SMilanka Ringwald     little_endian_store_16(buffer, pos, client->cid);
575021192e1SMilanka Ringwald     pos += 2;
576021192e1SMilanka Ringwald     buffer[pos++] = client->reports[report_index].service_index;
577021192e1SMilanka Ringwald     buffer[pos++] = client->reports[report_index].report_id;
578021192e1SMilanka Ringwald     little_endian_store_16(buffer, pos, report_len + 1);
579021192e1SMilanka Ringwald     pos += 2;
580021192e1SMilanka Ringwald     buffer[pos++] = client->reports[report_index].report_id;
58183d7ed1cSMilanka Ringwald     buffer[1] = pos + (report_len + 1) - 2;
58283d7ed1cSMilanka Ringwald 
583021192e1SMilanka Ringwald }
584021192e1SMilanka Ringwald 
585f4d3b82aSMilanka Ringwald static void hids_client_emit_hid_information_event(hids_client_t * client, const uint8_t *value, uint16_t value_len){
586f4d3b82aSMilanka Ringwald     if (value_len != 4) return;
587f4d3b82aSMilanka Ringwald 
588f4d3b82aSMilanka Ringwald     uint8_t event[11];
589f4d3b82aSMilanka Ringwald     int pos = 0;
590f4d3b82aSMilanka Ringwald     event[pos++] = HCI_EVENT_GATTSERVICE_META;
591f4d3b82aSMilanka Ringwald     event[pos++] = sizeof(event) - 2;
592f4d3b82aSMilanka Ringwald     event[pos++] = GATTSERVICE_SUBEVENT_HID_INFORMATION;
593f4d3b82aSMilanka Ringwald     little_endian_store_16(event, pos, client->cid);
594f4d3b82aSMilanka Ringwald     pos += 2;
595f4d3b82aSMilanka Ringwald     event[pos++] = client->service_index;
596f4d3b82aSMilanka Ringwald 
597f4d3b82aSMilanka Ringwald     memcpy(event+pos, value, 3);
598f4d3b82aSMilanka Ringwald     pos += 3;
599f4d3b82aSMilanka Ringwald     event[pos++] = (value[3] & 0x02) >> 1;
600f4d3b82aSMilanka Ringwald     event[pos++] = value[3] & 0x01;
601f4d3b82aSMilanka Ringwald 
602f4d3b82aSMilanka Ringwald     (*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
603f4d3b82aSMilanka Ringwald }
604f4d3b82aSMilanka Ringwald 
605*af2241c2SMilanka Ringwald static void hids_client_emit_protocol_mode_event(hids_client_t * client, const uint8_t *value, uint16_t value_len){
606*af2241c2SMilanka Ringwald     if (value_len != 1) return;
607*af2241c2SMilanka Ringwald 
608*af2241c2SMilanka Ringwald     uint8_t event[11];
609*af2241c2SMilanka Ringwald     int pos = 0;
610*af2241c2SMilanka Ringwald     event[pos++] = HCI_EVENT_GATTSERVICE_META;
611*af2241c2SMilanka Ringwald     event[pos++] = sizeof(event) - 2;
612*af2241c2SMilanka Ringwald     event[pos++] = GATTSERVICE_SUBEVENT_HID_PROTOCOL_MODE;
613*af2241c2SMilanka Ringwald     little_endian_store_16(event, pos, client->cid);
614*af2241c2SMilanka Ringwald     pos += 2;
615*af2241c2SMilanka Ringwald     event[pos++] = client->service_index;
616*af2241c2SMilanka Ringwald     event[pos++] = value[0];
617*af2241c2SMilanka Ringwald     (*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
618*af2241c2SMilanka Ringwald }
619f4d3b82aSMilanka Ringwald 
62083d7ed1cSMilanka Ringwald static void handle_notification_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
621021192e1SMilanka Ringwald     UNUSED(packet_type);
622021192e1SMilanka Ringwald     UNUSED(channel);
62383d7ed1cSMilanka Ringwald 
624021192e1SMilanka Ringwald     if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
625021192e1SMilanka Ringwald 
626021192e1SMilanka Ringwald     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
627021192e1SMilanka Ringwald     btstack_assert(client != NULL);
628021192e1SMilanka Ringwald 
629021192e1SMilanka Ringwald     uint8_t report_index = find_report_index_for_value_handle(client, gatt_event_notification_get_value_handle(packet));
630021192e1SMilanka Ringwald     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
631021192e1SMilanka Ringwald         return;
632021192e1SMilanka Ringwald     }
633021192e1SMilanka Ringwald 
63483d7ed1cSMilanka Ringwald     uint8_t * in_place_event = &packet[-2];
635021192e1SMilanka Ringwald     hids_client_setup_report_event(client, report_index, in_place_event, gatt_event_notification_get_value_length(packet));
63683d7ed1cSMilanka Ringwald     (*client->client_handler)(HCI_EVENT_GATTSERVICE_META, client->cid, in_place_event, size + 2);
637021192e1SMilanka Ringwald }
638021192e1SMilanka Ringwald 
63983d7ed1cSMilanka Ringwald static void handle_report_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
64083d7ed1cSMilanka Ringwald     UNUSED(packet_type);
64183d7ed1cSMilanka Ringwald     UNUSED(channel);
64283d7ed1cSMilanka Ringwald 
64383d7ed1cSMilanka Ringwald     if (hci_event_packet_get_type(packet) != GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT) return;
64483d7ed1cSMilanka Ringwald 
64583d7ed1cSMilanka Ringwald     hids_client_t * client = hids_get_client_for_con_handle(gatt_event_characteristic_value_query_result_get_handle(packet));
64683d7ed1cSMilanka Ringwald     btstack_assert(client != NULL);
64783d7ed1cSMilanka Ringwald 
64883d7ed1cSMilanka Ringwald     if (client->state != HIDS_CLIENT_W4_GET_REPORT_RESULT){
64983d7ed1cSMilanka Ringwald         return;
65083d7ed1cSMilanka Ringwald     }
65183d7ed1cSMilanka Ringwald     client->state = HIDS_CLIENT_STATE_CONNECTED;
65283d7ed1cSMilanka Ringwald 
65383d7ed1cSMilanka Ringwald     uint8_t report_index = find_report_index_for_value_handle(client, gatt_event_characteristic_value_query_result_get_value_handle(packet));
65483d7ed1cSMilanka Ringwald     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
65583d7ed1cSMilanka Ringwald         return;
65683d7ed1cSMilanka Ringwald     }
65783d7ed1cSMilanka Ringwald 
65883d7ed1cSMilanka Ringwald     uint8_t * in_place_event = &packet[-2];
65983d7ed1cSMilanka Ringwald     hids_client_setup_report_event(client, report_index, in_place_event, gatt_event_characteristic_value_query_result_get_value_length(packet));
66083d7ed1cSMilanka Ringwald     (*client->client_handler)(HCI_EVENT_GATTSERVICE_META, client->cid, in_place_event, size + 2);
66183d7ed1cSMilanka Ringwald }
662cf26c8fbSMilanka Ringwald 
663cf26c8fbSMilanka Ringwald static void hids_run_for_client(hids_client_t * client){
664cf26c8fbSMilanka Ringwald     uint8_t att_status;
6656bcfb631SMilanka Ringwald     gatt_client_service_t service;
6666bcfb631SMilanka Ringwald     gatt_client_characteristic_t characteristic;
667cf26c8fbSMilanka Ringwald 
668cf26c8fbSMilanka Ringwald     switch (client->state){
669cf26c8fbSMilanka Ringwald         case HIDS_CLIENT_STATE_W2_QUERY_SERVICE:
670556456ccSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
671556456ccSMilanka Ringwald             printf("\n\nQuery Services:\n");
672556456ccSMilanka Ringwald #endif
673cf26c8fbSMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_SERVICE_RESULT;
67419146789SMilanka Ringwald 
67519146789SMilanka Ringwald             // result in GATT_EVENT_SERVICE_QUERY_RESULT
676cf26c8fbSMilanka Ringwald             att_status = gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, client->con_handle, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE);
677cf26c8fbSMilanka Ringwald             UNUSED(att_status);
678cf26c8fbSMilanka Ringwald             break;
679cf26c8fbSMilanka Ringwald 
680cf26c8fbSMilanka Ringwald         case HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC:
681556456ccSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
682556456ccSMilanka Ringwald             printf("\n\nQuery Characteristics of service %d:\n", client->service_index);
683556456ccSMilanka Ringwald #endif
6846bcfb631SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT;
6856bcfb631SMilanka Ringwald 
6866bcfb631SMilanka Ringwald             service.start_group_handle = client->services[client->service_index].start_handle;
6876bcfb631SMilanka Ringwald             service.end_group_handle = client->services[client->service_index].end_handle;
68819146789SMilanka Ringwald 
68919146789SMilanka Ringwald             // result in GATT_EVENT_CHARACTERISTIC_QUERY_RESULT
6906bcfb631SMilanka Ringwald             att_status = gatt_client_discover_characteristics_for_service(&handle_gatt_client_event, client->con_handle, &service);
6916bcfb631SMilanka Ringwald 
6926bcfb631SMilanka Ringwald             UNUSED(att_status);
6936bcfb631SMilanka Ringwald             break;
6946bcfb631SMilanka Ringwald 
695021192e1SMilanka Ringwald         case HIDS_CLIENT_STATE_W2_SET_BOOT_PROTOCOL_MODE:
6962901a9b7SMilanka 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);
6976bcfb631SMilanka Ringwald             UNUSED(att_status);
6982901a9b7SMilanka Ringwald 
6992901a9b7SMilanka Ringwald             client->protocol_mode = client->required_protocol_mode;
700021192e1SMilanka Ringwald             if (hids_client_report_query_init(client)){
701021192e1SMilanka Ringwald                 break;
702021192e1SMilanka Ringwald             }
703021192e1SMilanka Ringwald 
704021192e1SMilanka Ringwald             hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
705021192e1SMilanka Ringwald             hids_finalize_client(client);
706cf26c8fbSMilanka Ringwald             break;
707cf26c8fbSMilanka Ringwald 
70828da36a6SMilanka Ringwald         case HIDS_CLIENT_STATE_W2_READ_REPORT_MAP_HID_DESCRIPTOR:
709556456ccSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
710556456ccSMilanka Ringwald             printf("\n\nRead REPORT_MAP (Handle 0x%04X) HID Descriptor of service %d:\n", client->services[client->service_index].report_map_value_handle, client->service_index);
711556456ccSMilanka Ringwald #endif
71228da36a6SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_HID_DESCRIPTOR;
713556456ccSMilanka Ringwald 
71419146789SMilanka Ringwald             // result in GATT_EVENT_LONG_CHARACTERISTIC_VALUE_QUERY_RESULT
71519146789SMilanka Ringwald             att_status = gatt_client_read_long_value_of_characteristic_using_value_handle(&handle_gatt_client_event, client->con_handle, client->services[client->service_index].report_map_value_handle);
716e7bd2dbeSMilanka Ringwald             UNUSED(att_status);
717e7bd2dbeSMilanka Ringwald             break;
718e7bd2dbeSMilanka Ringwald 
719e7bd2dbeSMilanka Ringwald         case HIDS_CLIENT_STATE_W2_REPORT_MAP_DISCOVER_CHARACTERISTIC_DESCRIPTORS:
720556456ccSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
721556456ccSMilanka Ringwald             printf("\nDiscover REPORT_MAP (Handle 0x%04X) Characteristic Descriptors of service %d:\n", client->services[client->service_index].report_map_value_handle, client->service_index);
722556456ccSMilanka Ringwald #endif
723e7bd2dbeSMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTORS_RESULT;
724e7bd2dbeSMilanka Ringwald 
7253322b222SMilanka Ringwald             characteristic.value_handle = client->services[client->service_index].report_map_value_handle;
7263322b222SMilanka Ringwald             characteristic.end_handle = client->services[client->service_index].report_map_end_handle;
7273322b222SMilanka Ringwald 
72819146789SMilanka Ringwald             // result in GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT
729e7bd2dbeSMilanka Ringwald             att_status = gatt_client_discover_characteristic_descriptors(&handle_gatt_client_event, client->con_handle, &characteristic);
730e7bd2dbeSMilanka Ringwald             UNUSED(att_status);
731e7bd2dbeSMilanka Ringwald             break;
732e7bd2dbeSMilanka Ringwald 
73328da36a6SMilanka Ringwald         case HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID:
734556456ccSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
73519146789SMilanka Ringwald             printf("\nRead external chr UUID (Handle 0x%04X) Characteristic Descriptors, service index %d:\n", client->external_reports[client->report_index].value_handle, client->service_index);
736556456ccSMilanka Ringwald #endif
73728da36a6SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_EXTERNAL_REPORT_REFERENCE_UUID;
738e7bd2dbeSMilanka Ringwald 
73919146789SMilanka Ringwald             // result in GATT_EVENT_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT
74019146789SMilanka Ringwald             att_status = gatt_client_read_characteristic_descriptor_using_descriptor_handle(&handle_gatt_client_event, client->con_handle, client->external_reports[client->report_index].value_handle);
741e7bd2dbeSMilanka Ringwald             UNUSED(att_status);
742e7bd2dbeSMilanka Ringwald             break;
743e7bd2dbeSMilanka Ringwald 
7443322b222SMilanka Ringwald         case HIDS_CLIENT_STATE_W2_DISCOVER_EXTERNAL_REPORT_CHARACTERISTIC:
745556456ccSMilanka Ringwald  #ifdef ENABLE_TESTING_SUPPORT
74619146789SMilanka Ringwald             printf("\nDiscover External Report Characteristic:\n");
747556456ccSMilanka Ringwald #endif
7483322b222SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_EXTERNAL_REPORT_CHARACTERISTIC_RESULT;
749e7bd2dbeSMilanka Ringwald 
7503322b222SMilanka Ringwald             service.start_group_handle = 0x0001;
7513322b222SMilanka Ringwald             service.end_group_handle = 0xffff;
75219146789SMilanka Ringwald 
75319146789SMilanka Ringwald             // Result in GATT_EVENT_CHARACTERISTIC_QUERY_RESULT
7543322b222SMilanka Ringwald             att_status = gatt_client_discover_characteristics_for_service(&handle_gatt_client_event, client->con_handle, &service);
755e7bd2dbeSMilanka Ringwald             UNUSED(att_status);
756e7bd2dbeSMilanka Ringwald             break;
757e7bd2dbeSMilanka Ringwald 
75828da36a6SMilanka Ringwald         case HIDS_CLIENT_STATE_W2_FIND_REPORT:
759556456ccSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
76083d7ed1cSMilanka Ringwald             printf("\nQuery Report Characteristic Descriptors [%d, %d, 0x%04X]:\n",
761556456ccSMilanka Ringwald                 client->report_index,
76283d7ed1cSMilanka Ringwald                 client->reports[client->report_index].service_index,
76383d7ed1cSMilanka Ringwald                 client->reports[client->report_index].value_handle);
764556456ccSMilanka Ringwald #endif
76528da36a6SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_REPORT_FOUND;
766*af2241c2SMilanka Ringwald             client->handle = 0;
767556456ccSMilanka Ringwald 
768556456ccSMilanka Ringwald             characteristic.value_handle = client->reports[client->report_index].value_handle;
769556456ccSMilanka Ringwald             characteristic.end_handle = client->reports[client->report_index].end_handle;
770556456ccSMilanka Ringwald             characteristic.properties = client->reports[client->report_index].properties;
771556456ccSMilanka Ringwald 
77219146789SMilanka Ringwald             // result in GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT
77370093cf5SMilanka Ringwald             att_status = gatt_client_discover_characteristic_descriptors(&handle_gatt_client_event, client->con_handle, &characteristic);
77470093cf5SMilanka Ringwald             UNUSED(att_status);
77570093cf5SMilanka Ringwald             break;
77670093cf5SMilanka Ringwald 
77728da36a6SMilanka Ringwald         case HIDS_CLIENT_STATE_W2_READ_REPORT_ID_AND_TYPE:
77828da36a6SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_REPORT_ID_AND_TYPE;
77970093cf5SMilanka Ringwald 
78019146789SMilanka Ringwald             // result in GATT_EVENT_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT
781*af2241c2SMilanka Ringwald             att_status = gatt_client_read_characteristic_descriptor_using_descriptor_handle(&handle_gatt_client_event, client->con_handle, client->handle);
782*af2241c2SMilanka Ringwald             client->handle = 0;
78370093cf5SMilanka Ringwald             UNUSED(att_status);
78470093cf5SMilanka Ringwald             break;
78570093cf5SMilanka Ringwald 
786021192e1SMilanka Ringwald         case HIDS_CLIENT_STATE_W2_ENABLE_INPUT_REPORTS:
787021192e1SMilanka Ringwald             client->state = HIDS_CLIENT_STATE_W4_INPUT_REPORTS_ENABLED;
788021192e1SMilanka Ringwald 
789021192e1SMilanka Ringwald             characteristic.value_handle = client->reports[client->report_index].value_handle;
790021192e1SMilanka Ringwald             characteristic.end_handle = client->reports[client->report_index].end_handle;
791021192e1SMilanka Ringwald             characteristic.properties = client->reports[client->report_index].properties;
792021192e1SMilanka Ringwald 
79319146789SMilanka Ringwald             // end of write marked in GATT_EVENT_QUERY_COMPLETE
794021192e1SMilanka Ringwald             att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
795021192e1SMilanka Ringwald 
796021192e1SMilanka Ringwald             if (att_status != ERROR_CODE_SUCCESS){
797021192e1SMilanka Ringwald                 if (hids_client_report_next_notification_report_index(client)){
798021192e1SMilanka Ringwald                     hids_run_for_client(client);
799021192e1SMilanka Ringwald                     break;
800021192e1SMilanka Ringwald                 }
801021192e1SMilanka Ringwald                 client->state = HIDS_CLIENT_STATE_CONNECTED;
802021192e1SMilanka Ringwald                 hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
803021192e1SMilanka Ringwald             } else {
804021192e1SMilanka Ringwald                 gatt_client_listen_for_characteristic_value_updates(
805021192e1SMilanka Ringwald                     &client->reports[client->report_index].notification_listener,
80683d7ed1cSMilanka Ringwald                     &handle_notification_event, client->con_handle, &characteristic);
807021192e1SMilanka Ringwald 
808021192e1SMilanka Ringwald                 client->state = HIDS_CLIENT_STATE_CONNECTED;
809021192e1SMilanka Ringwald                 hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
810021192e1SMilanka Ringwald             }
811021192e1SMilanka Ringwald             UNUSED(att_status);
812021192e1SMilanka Ringwald             break;
813021192e1SMilanka Ringwald 
81483d7ed1cSMilanka Ringwald 
81583d7ed1cSMilanka Ringwald         case HIDS_CLIENT_W2_SEND_REPORT:
81683d7ed1cSMilanka Ringwald 
81783d7ed1cSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
81883d7ed1cSMilanka Ringwald             printf("    Send report [%d, %d, 0x%04X]:\n",
81983d7ed1cSMilanka Ringwald                 client->report_index,
82083d7ed1cSMilanka Ringwald                 client->reports[client->report_index].service_index, client->reports[client->report_index].value_handle);
82183d7ed1cSMilanka Ringwald #endif
82283d7ed1cSMilanka Ringwald 
82383d7ed1cSMilanka Ringwald             client->state = HIDS_CLIENT_STATE_CONNECTED;
82483d7ed1cSMilanka Ringwald 
82583d7ed1cSMilanka Ringwald             att_status = gatt_client_write_value_of_characteristic_without_response(client->con_handle,
82683d7ed1cSMilanka Ringwald                 client->reports[client->report_index].value_handle,
82783d7ed1cSMilanka Ringwald                 client->report_len, (uint8_t *)client->report);
82883d7ed1cSMilanka Ringwald             UNUSED(att_status);
82983d7ed1cSMilanka Ringwald             break;
83083d7ed1cSMilanka Ringwald 
83183d7ed1cSMilanka Ringwald         case HIDS_CLIENT_W2_SEND_GET_REPORT:
83283d7ed1cSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
833f4d3b82aSMilanka Ringwald             printf("    Get report [index %d, ID %d, Service %d, handle 0x%04X]:\n",
834f4d3b82aSMilanka Ringwald                 client->report_index,
83583d7ed1cSMilanka Ringwald                 client->reports[client->report_index].report_id,
83683d7ed1cSMilanka Ringwald                 client->reports[client->report_index].service_index, client->reports[client->report_index].value_handle);
83783d7ed1cSMilanka Ringwald #endif
83883d7ed1cSMilanka Ringwald 
83983d7ed1cSMilanka Ringwald             client->state = HIDS_CLIENT_W4_GET_REPORT_RESULT;
840f4d3b82aSMilanka Ringwald             // result in GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT
84183d7ed1cSMilanka Ringwald             att_status = gatt_client_read_value_of_characteristic_using_value_handle(
84283d7ed1cSMilanka Ringwald                 &handle_report_event,
84383d7ed1cSMilanka Ringwald                 client->con_handle,
84483d7ed1cSMilanka Ringwald                 client->reports[client->report_index].value_handle);
84583d7ed1cSMilanka Ringwald             UNUSED(att_status);
84683d7ed1cSMilanka Ringwald             break;
84783d7ed1cSMilanka Ringwald 
84883d7ed1cSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
84983d7ed1cSMilanka Ringwald         case HIDS_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION:
85083d7ed1cSMilanka Ringwald             client->state = HIDS_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT;
85183d7ed1cSMilanka Ringwald 
852f4d3b82aSMilanka Ringwald             // result in GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT
85383d7ed1cSMilanka Ringwald             att_status = gatt_client_read_value_of_characteristic_using_value_handle(
85483d7ed1cSMilanka Ringwald                 &handle_gatt_client_event,
85583d7ed1cSMilanka Ringwald                 client->con_handle,
85683d7ed1cSMilanka Ringwald                 client->reports[client->report_index].ccc_handle);
85783d7ed1cSMilanka Ringwald 
85883d7ed1cSMilanka Ringwald             break;
85983d7ed1cSMilanka Ringwald #endif
860*af2241c2SMilanka Ringwald         case HIDS_CLIENT_W2_READ_VALUE_OF_CHARACTERISTIC:
861*af2241c2SMilanka Ringwald             client->state = HIDS_CLIENT_W4_VALUE_OF_CHARACTERISTIC_RESULT;
86283d7ed1cSMilanka Ringwald 
863f4d3b82aSMilanka Ringwald             // result in GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT
864f4d3b82aSMilanka Ringwald             att_status = gatt_client_read_value_of_characteristic_using_value_handle(
865f4d3b82aSMilanka Ringwald                 &handle_gatt_client_event,
866f4d3b82aSMilanka Ringwald                 client->con_handle,
867*af2241c2SMilanka Ringwald                 client->handle);
868f4d3b82aSMilanka Ringwald             break;
8696bcfb631SMilanka Ringwald         default:
8706bcfb631SMilanka Ringwald             break;
8716bcfb631SMilanka Ringwald     }
8726bcfb631SMilanka Ringwald }
8736bcfb631SMilanka Ringwald 
874cf26c8fbSMilanka Ringwald static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
875cf26c8fbSMilanka Ringwald     UNUSED(packet_type);
876cf26c8fbSMilanka Ringwald     UNUSED(channel);
877cf26c8fbSMilanka Ringwald     UNUSED(size);
878cf26c8fbSMilanka Ringwald 
8796bcfb631SMilanka Ringwald     hids_client_t * client = NULL;
880cf26c8fbSMilanka Ringwald     uint8_t att_status;
8816bcfb631SMilanka Ringwald     gatt_client_service_t service;
8826bcfb631SMilanka Ringwald     gatt_client_characteristic_t characteristic;
88370093cf5SMilanka Ringwald     gatt_client_characteristic_descriptor_t characteristic_descriptor;
884ab116b1cSMilanka Ringwald 
885021192e1SMilanka Ringwald     // hids_client_report_t * boot_keyboard_report;
886021192e1SMilanka Ringwald     // hids_client_report_t * boot_mouse_report;
887e7bd2dbeSMilanka Ringwald     const uint8_t * characteristic_descriptor_value;
8883322b222SMilanka Ringwald     uint8_t i;
8893322b222SMilanka Ringwald     uint8_t report_index;
890da142a6fSMilanka Ringwald 
891f4d3b82aSMilanka Ringwald     const uint8_t * value;
892f4d3b82aSMilanka Ringwald     uint16_t value_len;
893cf26c8fbSMilanka Ringwald 
894cf26c8fbSMilanka Ringwald     switch(hci_event_packet_get_type(packet)){
895cf26c8fbSMilanka Ringwald         case GATT_EVENT_SERVICE_QUERY_RESULT:
896cf26c8fbSMilanka Ringwald             client = hids_get_client_for_con_handle(gatt_event_service_query_result_get_handle(packet));
897cf26c8fbSMilanka Ringwald             btstack_assert(client != NULL);
898cf26c8fbSMilanka Ringwald 
8996bcfb631SMilanka Ringwald             if (client->state != HIDS_CLIENT_STATE_W4_SERVICE_RESULT) {
9006bcfb631SMilanka Ringwald                 hids_emit_connection_established(client, GATT_CLIENT_IN_WRONG_STATE);
9016bcfb631SMilanka Ringwald                 hids_finalize_client(client);
9026bcfb631SMilanka Ringwald                 break;
9036bcfb631SMilanka Ringwald             }
9046bcfb631SMilanka Ringwald 
905021192e1SMilanka Ringwald             if (client->num_instances < MAX_NUM_HID_SERVICES){
906021192e1SMilanka Ringwald                 uint8_t index = client->num_instances;
9076bcfb631SMilanka Ringwald                 gatt_event_service_query_result_get_service(packet, &service);
908021192e1SMilanka Ringwald                 client->services[index].start_handle = service.start_group_handle;
909021192e1SMilanka Ringwald                 client->services[index].end_handle = service.end_group_handle;
9106bcfb631SMilanka Ringwald                 client->num_instances++;
911021192e1SMilanka Ringwald 
912708c69d2SMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
913708c69d2SMilanka Ringwald                 printf("HID Service: start handle 0x%04X, end handle 0x%04X\n", client->services[index].start_handle, client->services[index].end_handle);
914708c69d2SMilanka Ringwald #endif
915021192e1SMilanka Ringwald                 hids_client_descriptor_storage_init(client, index);
916021192e1SMilanka Ringwald             }  else {
917021192e1SMilanka Ringwald                 log_info("%d hid services found, only first %d can be stored, increase MAX_NUM_HID_SERVICES", client->num_instances + 1, MAX_NUM_HID_SERVICES);
918da142a6fSMilanka Ringwald             }
9196bcfb631SMilanka Ringwald             break;
9206bcfb631SMilanka Ringwald 
9216bcfb631SMilanka Ringwald         case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
9226bcfb631SMilanka Ringwald             client = hids_get_client_for_con_handle(gatt_event_characteristic_query_result_get_handle(packet));
9236bcfb631SMilanka Ringwald             btstack_assert(client != NULL);
9246bcfb631SMilanka Ringwald             gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
92519146789SMilanka Ringwald 
92619146789SMilanka Ringwald             report_index = HIDS_CLIENT_INVALID_REPORT_INDEX;
92719146789SMilanka Ringwald             if (client->state == HIDS_CLIENT_STATE_W4_EXTERNAL_REPORT_CHARACTERISTIC_RESULT){
92819146789SMilanka Ringwald                 if (!external_report_index_for_uuid_exists(client, characteristic.uuid16)){
92919146789SMilanka Ringwald                     break;
93019146789SMilanka Ringwald                 }
93119146789SMilanka Ringwald             }
93219146789SMilanka Ringwald 
93319146789SMilanka Ringwald             switch (characteristic.uuid16){
93419146789SMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE:
93519146789SMilanka Ringwald                     client->protocol_mode_value_handle = characteristic.value_handle;
936*af2241c2SMilanka Ringwald                     client->services[client->service_index].protocol_mode_value_handle = characteristic.value_handle;
93719146789SMilanka Ringwald                     break;
93819146789SMilanka Ringwald 
93919146789SMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT:
94019146789SMilanka Ringwald                     report_index = hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT, true);
94119146789SMilanka Ringwald                     break;
94219146789SMilanka Ringwald 
94319146789SMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT:
94419146789SMilanka Ringwald                     report_index = hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT, true);
94519146789SMilanka Ringwald                     break;
94619146789SMilanka Ringwald 
94719146789SMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT:
948f4d3b82aSMilanka Ringwald                     report_index = hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_OUTPUT, true);
94919146789SMilanka Ringwald                     break;
95019146789SMilanka Ringwald 
95119146789SMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_REPORT:
95219146789SMilanka Ringwald                     report_index = hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_ID, HID_REPORT_TYPE_RESERVED, false);
95319146789SMilanka Ringwald                     break;
95419146789SMilanka Ringwald 
95519146789SMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP:
95619146789SMilanka Ringwald                     client->services[client->service_index].report_map_value_handle = characteristic.value_handle;
95719146789SMilanka Ringwald                     client->services[client->service_index].report_map_end_handle = characteristic.end_handle;
95819146789SMilanka Ringwald                     break;
95919146789SMilanka Ringwald 
96019146789SMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION:
961f4d3b82aSMilanka Ringwald                     client->services[client->service_index].hid_information_value_handle = characteristic.value_handle;
96219146789SMilanka Ringwald                     break;
96319146789SMilanka Ringwald 
96419146789SMilanka Ringwald                 case ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT:
965f4d3b82aSMilanka Ringwald                     client->services[client->service_index].control_point_value_handle = characteristic.value_handle;
96619146789SMilanka Ringwald                     break;
96719146789SMilanka Ringwald 
96819146789SMilanka Ringwald                 default:
96919146789SMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
97019146789SMilanka Ringwald                     printf("    TODO: Found external characteristic 0x%04X\n", characteristic.uuid16);
97119146789SMilanka Ringwald #endif
97219146789SMilanka Ringwald                     return;
97319146789SMilanka Ringwald             }
97419146789SMilanka Ringwald 
97519146789SMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
97619146789SMilanka Ringwald             printf("HID Characteristic %s:  \n    Attribute Handle 0x%04X, Properties 0x%02X, Handle 0x%04X, UUID 0x%04X, service %d",
97719146789SMilanka Ringwald                 hid_characteristic_name(characteristic.uuid16),
97819146789SMilanka Ringwald                 characteristic.start_handle,
97919146789SMilanka Ringwald                 characteristic.properties,
98019146789SMilanka Ringwald                 characteristic.value_handle, characteristic.uuid16,
98119146789SMilanka Ringwald                 client->service_index);
98219146789SMilanka Ringwald 
98319146789SMilanka Ringwald             if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
98419146789SMilanka Ringwald                 printf(", report index 0x%02X", report_index);
98519146789SMilanka Ringwald             }
98619146789SMilanka Ringwald             printf("\n");
98719146789SMilanka Ringwald #endif
988cf26c8fbSMilanka Ringwald             break;
989cf26c8fbSMilanka Ringwald 
9908cec2b74SMilanka Ringwald         case GATT_EVENT_LONG_CHARACTERISTIC_VALUE_QUERY_RESULT:
991e7bd2dbeSMilanka Ringwald             // Map Report characteristic value == HID Descriptor
9928cec2b74SMilanka Ringwald             client = hids_get_client_for_con_handle(gatt_event_long_characteristic_value_query_result_get_handle(packet));
993e7bd2dbeSMilanka Ringwald             btstack_assert(client != NULL);
994da142a6fSMilanka Ringwald 
995f4d3b82aSMilanka Ringwald             value = gatt_event_long_characteristic_value_query_result_get_value(packet);
996f4d3b82aSMilanka Ringwald             value_len = gatt_event_long_characteristic_value_query_result_get_value_length(packet);
9978cec2b74SMilanka Ringwald 
998556456ccSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
9998cec2b74SMilanka Ringwald             // printf("Report Map HID Desc [%d] for service %d\n", descriptor_len, client->service_index);
1000f4d3b82aSMilanka Ringwald             printf_hexdump(value, value_len);
1001556456ccSMilanka Ringwald #endif
1002f4d3b82aSMilanka Ringwald             for (i = 0; i < value_len; i++){
1003f4d3b82aSMilanka Ringwald                 bool stored = hids_client_descriptor_storage_store(client, client->service_index, value[i]);
1004da142a6fSMilanka Ringwald                 if (!stored){
1005da142a6fSMilanka Ringwald                     client->services[client->service_index].hid_descriptor_status = ERROR_CODE_MEMORY_CAPACITY_EXCEEDED;
1006da142a6fSMilanka Ringwald                     break;
1007da142a6fSMilanka Ringwald                 }
1008da142a6fSMilanka Ringwald             }
1009e7bd2dbeSMilanka Ringwald             break;
1010e7bd2dbeSMilanka Ringwald 
101170093cf5SMilanka Ringwald         case GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT:
101270093cf5SMilanka Ringwald             client = hids_get_client_for_con_handle(gatt_event_all_characteristic_descriptors_query_result_get_handle(packet));
101370093cf5SMilanka Ringwald             btstack_assert(client != NULL);
101470093cf5SMilanka Ringwald             gatt_event_all_characteristic_descriptors_query_result_get_characteristic_descriptor(packet, &characteristic_descriptor);
101570093cf5SMilanka Ringwald 
1016e7bd2dbeSMilanka Ringwald             switch (client->state) {
1017e7bd2dbeSMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTORS_RESULT:
1018e7bd2dbeSMilanka Ringwald                     // setup for descriptor value query
1019e7bd2dbeSMilanka Ringwald                     if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_EXTERNAL_REPORT_REFERENCE){
102019146789SMilanka Ringwald                         report_index = hids_client_add_external_report(client, &characteristic_descriptor);
10213322b222SMilanka Ringwald 
1022556456ccSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
1023556456ccSMilanka Ringwald                         if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
1024556456ccSMilanka Ringwald                             printf("    External Report Reference Characteristic Descriptor: Handle 0x%04X, UUID 0x%04X, service %d, report index 0x%02X\n",
1025556456ccSMilanka Ringwald                                 characteristic_descriptor.handle,
1026556456ccSMilanka Ringwald                                 characteristic_descriptor.uuid16,
1027556456ccSMilanka Ringwald                                 client->service_index, report_index);
1028556456ccSMilanka Ringwald                         }
1029556456ccSMilanka Ringwald #endif
1030e7bd2dbeSMilanka Ringwald                     }
1031e7bd2dbeSMilanka Ringwald                     break;
103228da36a6SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_REPORT_FOUND:
103370093cf5SMilanka Ringwald                     // setup for descriptor value query
103470093cf5SMilanka Ringwald                     if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_REPORT_REFERENCE){
1035*af2241c2SMilanka Ringwald                         client->handle = characteristic_descriptor.handle;
1036556456ccSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
1037556456ccSMilanka Ringwald                         printf("    Report Characteristic Report Reference Characteristic Descriptor:  Handle 0x%04X, UUID 0x%04X\n",
1038556456ccSMilanka Ringwald                             characteristic_descriptor.handle,
1039556456ccSMilanka Ringwald                             characteristic_descriptor.uuid16);
104019146789SMilanka Ringwald #endif
1041556456ccSMilanka Ringwald                     }
1042556456ccSMilanka Ringwald 
104319146789SMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
1044556456ccSMilanka Ringwald                     if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_GATT_CLIENT_CHARACTERISTIC_CONFIGURATION){
104583d7ed1cSMilanka Ringwald                         client->reports[client->report_index].ccc_handle = characteristic_descriptor.handle;
1046556456ccSMilanka Ringwald                         printf("    Report Client Characteristic Configuration Descriptor: Handle 0x%04X, UUID 0x%04X\n",
1047556456ccSMilanka Ringwald                             characteristic_descriptor.handle,
1048556456ccSMilanka Ringwald                             characteristic_descriptor.uuid16);
1049556456ccSMilanka Ringwald                     }
1050556456ccSMilanka Ringwald #endif
105170093cf5SMilanka Ringwald                     break;
1052556456ccSMilanka Ringwald 
1053e7bd2dbeSMilanka Ringwald                 default:
1054e7bd2dbeSMilanka Ringwald                     break;
1055e7bd2dbeSMilanka Ringwald             }
1056e7bd2dbeSMilanka Ringwald             break;
105770093cf5SMilanka Ringwald 
105883d7ed1cSMilanka Ringwald         case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT:
105983d7ed1cSMilanka Ringwald             client = hids_get_client_for_con_handle(gatt_event_characteristic_value_query_result_get_handle(packet));
106083d7ed1cSMilanka Ringwald             btstack_assert(client != NULL);
106183d7ed1cSMilanka Ringwald 
1062f4d3b82aSMilanka Ringwald             value = gatt_event_characteristic_value_query_result_get_value(packet);
1063f4d3b82aSMilanka Ringwald             value_len = gatt_event_characteristic_value_query_result_get_value_length(packet);
1064f4d3b82aSMilanka Ringwald 
1065*af2241c2SMilanka Ringwald 
1066f4d3b82aSMilanka Ringwald             switch (client->state){
1067f4d3b82aSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
1068f4d3b82aSMilanka Ringwald                 case HIDS_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT:
106983d7ed1cSMilanka Ringwald                     printf("    Received CCC value: ");
1070f4d3b82aSMilanka Ringwald                     printf_hexdump(value,  value_len);
107183d7ed1cSMilanka Ringwald                     break;
107283d7ed1cSMilanka Ringwald #endif
1073*af2241c2SMilanka Ringwald                 case HIDS_CLIENT_W4_VALUE_OF_CHARACTERISTIC_RESULT:{
1074*af2241c2SMilanka Ringwald                     uint16_t value_handle = gatt_event_characteristic_value_query_result_get_value_handle(packet);
1075*af2241c2SMilanka Ringwald                     if (value_handle == client->services[client->service_index].hid_information_value_handle){
1076f4d3b82aSMilanka Ringwald                         hids_client_emit_hid_information_event(client, value, value_len);
1077f4d3b82aSMilanka Ringwald                         break;
1078*af2241c2SMilanka Ringwald                     }
1079*af2241c2SMilanka Ringwald                     if (value_handle == client->services[client->service_index].protocol_mode_value_handle){
1080*af2241c2SMilanka Ringwald                         hids_client_emit_protocol_mode_event(client, value, value_len);
1081*af2241c2SMilanka Ringwald                         break;
1082*af2241c2SMilanka Ringwald                     }
1083*af2241c2SMilanka Ringwald                     break;
1084*af2241c2SMilanka Ringwald                 }
1085f4d3b82aSMilanka Ringwald                 default:
1086f4d3b82aSMilanka Ringwald                     break;
1087f4d3b82aSMilanka Ringwald             }
1088f4d3b82aSMilanka Ringwald 
1089f4d3b82aSMilanka Ringwald             break;
109083d7ed1cSMilanka Ringwald 
109170093cf5SMilanka Ringwald         case GATT_EVENT_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT:
109270093cf5SMilanka Ringwald             client = hids_get_client_for_con_handle(gatt_event_characteristic_descriptor_query_result_get_handle(packet));
109370093cf5SMilanka Ringwald             btstack_assert(client != NULL);
1094e7bd2dbeSMilanka Ringwald 
1095e7bd2dbeSMilanka Ringwald             if (gatt_event_characteristic_descriptor_query_result_get_descriptor_length(packet) != 2){
1096e7bd2dbeSMilanka Ringwald                 break;
1097e7bd2dbeSMilanka Ringwald             }
10987e1e6e7dSMilanka Ringwald 
1099e7bd2dbeSMilanka Ringwald             characteristic_descriptor_value = gatt_event_characteristic_descriptor_query_result_get_descriptor(packet);
1100e7bd2dbeSMilanka Ringwald             switch (client->state) {
110128da36a6SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_EXTERNAL_REPORT_REFERENCE_UUID:
1102e7bd2dbeSMilanka Ringwald                     // get external report characteristic uuid
110319146789SMilanka Ringwald                     report_index = find_external_report_index_for_value_handle(client, gatt_event_characteristic_descriptor_query_result_get_descriptor_handle(packet));
11043322b222SMilanka Ringwald                     if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
110519146789SMilanka Ringwald                         client->external_reports[report_index].external_report_reference_uuid = little_endian_read_16(characteristic_descriptor_value, 0);
1106556456ccSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
110719146789SMilanka Ringwald                         printf("    Update external_report_reference_uuid of report index 0x%02X, service index 0x%02X, UUID 0x%02X\n",
110819146789SMilanka Ringwald                             report_index, client->service_index, client->external_reports[report_index].external_report_reference_uuid);
1109556456ccSMilanka Ringwald #endif
11103322b222SMilanka Ringwald                     }
1111e7bd2dbeSMilanka Ringwald                     break;
1112e7bd2dbeSMilanka Ringwald 
111328da36a6SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_REPORT_ID_AND_TYPE:
111419146789SMilanka Ringwald 
111519146789SMilanka Ringwald                     if (client->report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
1116021192e1SMilanka Ringwald                         client->reports[client->report_index].report_id = characteristic_descriptor_value[0];
1117021192e1SMilanka Ringwald                         client->reports[client->report_index].report_type = (hid_report_type_t)characteristic_descriptor_value[1];
111819146789SMilanka Ringwald     #ifdef ENABLE_TESTING_SUPPORT
111983d7ed1cSMilanka Ringwald                         printf("    Update report ID and type [%d, %d] of report index 0x%02X, service index 0x%02X\n",
1120021192e1SMilanka Ringwald                             client->reports[client->report_index].report_id,
1121021192e1SMilanka Ringwald                             client->reports[client->report_index].report_type,
112283d7ed1cSMilanka Ringwald                             client->report_index, client->service_index);
112319146789SMilanka Ringwald     #endif
112419146789SMilanka Ringwald                     }
1125e7bd2dbeSMilanka Ringwald                     break;
1126e7bd2dbeSMilanka Ringwald 
1127e7bd2dbeSMilanka Ringwald                 default:
1128e7bd2dbeSMilanka Ringwald                     break;
112970093cf5SMilanka Ringwald             }
113070093cf5SMilanka Ringwald             break;
113170093cf5SMilanka Ringwald 
1132cf26c8fbSMilanka Ringwald         case GATT_EVENT_QUERY_COMPLETE:
1133cf26c8fbSMilanka Ringwald             client = hids_get_client_for_con_handle(gatt_event_query_complete_get_handle(packet));
1134cf26c8fbSMilanka Ringwald             btstack_assert(client != NULL);
1135cf26c8fbSMilanka Ringwald 
1136cf26c8fbSMilanka Ringwald             att_status = gatt_event_query_complete_get_att_status(packet);
1137cf26c8fbSMilanka Ringwald 
1138cf26c8fbSMilanka Ringwald             switch (client->state){
1139cf26c8fbSMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_SERVICE_RESULT:
11406bcfb631SMilanka Ringwald                     if (att_status != ATT_ERROR_SUCCESS){
11416bcfb631SMilanka Ringwald                         hids_emit_connection_established(client, att_status);
11426bcfb631SMilanka Ringwald                         hids_finalize_client(client);
1143cf26c8fbSMilanka Ringwald                         break;
11446bcfb631SMilanka Ringwald                     }
11456bcfb631SMilanka Ringwald 
11466bcfb631SMilanka Ringwald                     if (client->num_instances == 0){
11476bcfb631SMilanka Ringwald                         hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
11486bcfb631SMilanka Ringwald                         hids_finalize_client(client);
11496bcfb631SMilanka Ringwald                         break;
11506bcfb631SMilanka Ringwald                     }
11516bcfb631SMilanka Ringwald 
11526bcfb631SMilanka Ringwald                     client->service_index = 0;
115319146789SMilanka Ringwald                     client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
11546bcfb631SMilanka Ringwald                     break;
11556bcfb631SMilanka Ringwald 
11566bcfb631SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT:
11576bcfb631SMilanka Ringwald                     if (att_status != ATT_ERROR_SUCCESS){
11586bcfb631SMilanka Ringwald                         hids_emit_connection_established(client, att_status);
11596bcfb631SMilanka Ringwald                         hids_finalize_client(client);
11606bcfb631SMilanka Ringwald                         break;
11616bcfb631SMilanka Ringwald                     }
11626bcfb631SMilanka Ringwald 
116370093cf5SMilanka Ringwald                     if ((client->service_index + 1) < client->num_instances){
116470093cf5SMilanka Ringwald                         // discover characteristics of next service
116570093cf5SMilanka Ringwald                         client->service_index++;
116670093cf5SMilanka Ringwald                         client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
1167021192e1SMilanka Ringwald                         break;
1168021192e1SMilanka Ringwald                     }
1169021192e1SMilanka Ringwald 
1170021192e1SMilanka Ringwald                     switch (client->required_protocol_mode){
1171021192e1SMilanka Ringwald                         case HID_PROTOCOL_MODE_REPORT:
1172021192e1SMilanka Ringwald                             client->protocol_mode = HID_PROTOCOL_MODE_REPORT;
1173021192e1SMilanka Ringwald                             if (hid_clients_has_reports_in_report_mode(client)){
1174021192e1SMilanka Ringwald                                 client->protocol_mode = HID_PROTOCOL_MODE_REPORT;
1175021192e1SMilanka Ringwald                                 break;
1176021192e1SMilanka Ringwald                             }
1177021192e1SMilanka Ringwald                             hids_emit_connection_established(client, att_status);
1178021192e1SMilanka Ringwald                             hids_finalize_client(client);
1179021192e1SMilanka Ringwald                             return;
1180021192e1SMilanka Ringwald 
1181021192e1SMilanka Ringwald                         case HID_PROTOCOL_MODE_REPORT_WITH_FALLBACK_TO_BOOT:
1182021192e1SMilanka Ringwald                             if (hid_clients_has_reports_in_report_mode(client)){
1183021192e1SMilanka Ringwald                                 client->protocol_mode = HID_PROTOCOL_MODE_REPORT;
1184021192e1SMilanka Ringwald                                 break;
1185021192e1SMilanka Ringwald                             }
1186021192e1SMilanka Ringwald                             if (hid_clients_has_reports_in_boot_mode(client)){
1187021192e1SMilanka Ringwald                                 if (client->protocol_mode_value_handle != 0){
1188021192e1SMilanka Ringwald                                     client->state = HIDS_CLIENT_STATE_W2_SET_BOOT_PROTOCOL_MODE;
1189021192e1SMilanka Ringwald                                     break;
1190021192e1SMilanka Ringwald                                 }
1191021192e1SMilanka Ringwald                                 hids_emit_connection_established(client, att_status);
1192021192e1SMilanka Ringwald                                 hids_finalize_client(client);
1193021192e1SMilanka Ringwald                                 return;
1194021192e1SMilanka Ringwald                             }
1195021192e1SMilanka Ringwald                             break;
1196021192e1SMilanka Ringwald                         default:
1197021192e1SMilanka Ringwald                             if (hid_clients_has_reports_in_boot_mode(client)){
1198021192e1SMilanka Ringwald                                 if (client->protocol_mode_value_handle != 0){
1199021192e1SMilanka Ringwald                                     client->state = HIDS_CLIENT_STATE_W2_SET_BOOT_PROTOCOL_MODE;
1200021192e1SMilanka Ringwald                                     break;
1201021192e1SMilanka Ringwald                                 }
1202021192e1SMilanka Ringwald                                 hids_emit_connection_established(client, att_status);
1203021192e1SMilanka Ringwald                                 hids_finalize_client(client);
1204021192e1SMilanka Ringwald                                 return;
1205021192e1SMilanka Ringwald                             }
1206021192e1SMilanka Ringwald                             break;
1207021192e1SMilanka Ringwald                     }
1208021192e1SMilanka Ringwald 
1209021192e1SMilanka Ringwald                     if (client->state == HIDS_CLIENT_STATE_W2_SET_BOOT_PROTOCOL_MODE){
1210021192e1SMilanka Ringwald                         break;
1211021192e1SMilanka Ringwald                     }
12123322b222SMilanka Ringwald 
12137e1e6e7dSMilanka Ringwald                     // 1. we need to get HID Descriptor and
12147e1e6e7dSMilanka Ringwald                     // 2. get external Report characteristics if referenced from Report Map
12157e1e6e7dSMilanka Ringwald                     if (hids_client_report_map_query_init(client)){
121670093cf5SMilanka Ringwald                         break;
121770093cf5SMilanka Ringwald                     }
121870093cf5SMilanka Ringwald                     hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
121970093cf5SMilanka Ringwald                     hids_finalize_client(client);
12206bcfb631SMilanka Ringwald                     break;
12216bcfb631SMilanka Ringwald 
12223322b222SMilanka Ringwald 
122328da36a6SMilanka Ringwald                 // HID descriptor found
122428da36a6SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_HID_DESCRIPTOR:
1225e7bd2dbeSMilanka Ringwald                     if (att_status != ATT_ERROR_SUCCESS){
1226e7bd2dbeSMilanka Ringwald                         hids_emit_connection_established(client, att_status);
1227e7bd2dbeSMilanka Ringwald                         hids_finalize_client(client);
1228e7bd2dbeSMilanka Ringwald                         break;
1229e7bd2dbeSMilanka Ringwald                     }
1230e7bd2dbeSMilanka Ringwald                     client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_DISCOVER_CHARACTERISTIC_DESCRIPTORS;
1231e7bd2dbeSMilanka Ringwald                     break;
1232e7bd2dbeSMilanka Ringwald 
123328da36a6SMilanka Ringwald                 // found all descriptors, check if there is one with EXTERNAL_REPORT_REFERENCE
1234e7bd2dbeSMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTORS_RESULT:
12353322b222SMilanka Ringwald                     // go for next report map
12363322b222SMilanka Ringwald                     if (hids_client_report_query_next_report_map(client)){
1237e7bd2dbeSMilanka Ringwald                         break;
1238e7bd2dbeSMilanka Ringwald                     }
1239e7bd2dbeSMilanka Ringwald 
12403322b222SMilanka Ringwald                     // read UUIDS for external characteristics
12413322b222SMilanka Ringwald                     if (hids_client_report_map_uuid_query_init(client)){
1242e7bd2dbeSMilanka Ringwald                         break;
1243e7bd2dbeSMilanka Ringwald                     }
1244e7bd2dbeSMilanka Ringwald 
1245e7bd2dbeSMilanka Ringwald                     // discover characteristic descriptor for all Report characteristics,
1246e7bd2dbeSMilanka Ringwald                     // then read value of characteristic descriptor to get Report ID
12477e1e6e7dSMilanka Ringwald                     if (hids_client_report_query_init(client)){
1248e7bd2dbeSMilanka Ringwald                         break;
1249e7bd2dbeSMilanka Ringwald                     }
1250e7bd2dbeSMilanka Ringwald 
1251e7bd2dbeSMilanka Ringwald                     hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
1252e7bd2dbeSMilanka Ringwald                     hids_finalize_client(client);
1253e7bd2dbeSMilanka Ringwald                     break;
1254e7bd2dbeSMilanka Ringwald 
125528da36a6SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_REPORT_MAP_EXTERNAL_REPORT_REFERENCE_UUID:
1256e7bd2dbeSMilanka Ringwald                     // go for next map report
12573322b222SMilanka Ringwald                     if (hids_client_report_query_next_report_map_uuid(client)){
1258e7bd2dbeSMilanka Ringwald                         break;
1259e7bd2dbeSMilanka Ringwald                     }
1260e7bd2dbeSMilanka Ringwald 
12613322b222SMilanka Ringwald                     // update external characteristics with correct value handle and end handle
12623322b222SMilanka Ringwald                     client->state = HIDS_CLIENT_STATE_W2_DISCOVER_EXTERNAL_REPORT_CHARACTERISTIC;
1263e7bd2dbeSMilanka Ringwald                     break;
1264e7bd2dbeSMilanka Ringwald 
12653322b222SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_EXTERNAL_REPORT_CHARACTERISTIC_RESULT:
1266e7bd2dbeSMilanka Ringwald                     // discover characteristic descriptor for all Report characteristics,
1267e7bd2dbeSMilanka Ringwald                     // then read value of characteristic descriptor to get Report ID
12687e1e6e7dSMilanka Ringwald                     if (hids_client_report_query_init(client)){
1269e7bd2dbeSMilanka Ringwald                         break;
1270e7bd2dbeSMilanka Ringwald                     }
1271e7bd2dbeSMilanka Ringwald 
1272e7bd2dbeSMilanka Ringwald                     hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
1273e7bd2dbeSMilanka Ringwald                     hids_finalize_client(client);
1274e7bd2dbeSMilanka Ringwald                     break;
1275e7bd2dbeSMilanka Ringwald 
127628da36a6SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_REPORT_FOUND:
1277*af2241c2SMilanka Ringwald                     if (client->handle != 0){
127828da36a6SMilanka Ringwald                         client->state = HIDS_CLIENT_STATE_W2_READ_REPORT_ID_AND_TYPE;
127970093cf5SMilanka Ringwald                         break;
128070093cf5SMilanka Ringwald                     }
128170093cf5SMilanka Ringwald 
128270093cf5SMilanka Ringwald                     // go for next report
12837e1e6e7dSMilanka Ringwald                     if (hids_client_report_query_next_report(client)){
128470093cf5SMilanka Ringwald                         break;
128570093cf5SMilanka Ringwald                     }
1286835a13f1SMilanka Ringwald                     client->state = HIDS_CLIENT_STATE_CONNECTED;
128770093cf5SMilanka Ringwald                     hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
128870093cf5SMilanka Ringwald                     break;
128970093cf5SMilanka Ringwald 
129028da36a6SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_REPORT_ID_AND_TYPE:
129170093cf5SMilanka Ringwald                     // go for next report
12927e1e6e7dSMilanka Ringwald                     if (hids_client_report_query_next_report(client)){
129370093cf5SMilanka Ringwald                         break;
129470093cf5SMilanka Ringwald                     }
1295021192e1SMilanka Ringwald                     if (hids_client_report_notifications_init(client)){
1296021192e1SMilanka Ringwald                         break;
1297021192e1SMilanka Ringwald                     }
1298835a13f1SMilanka Ringwald                     client->state = HIDS_CLIENT_STATE_CONNECTED;
129970093cf5SMilanka Ringwald                     hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
130070093cf5SMilanka Ringwald                     break;
130170093cf5SMilanka Ringwald 
1302021192e1SMilanka Ringwald                 case HIDS_CLIENT_STATE_W4_INPUT_REPORTS_ENABLED:
1303021192e1SMilanka Ringwald                     if (hids_client_report_next_notification_report_index(client)){
13042901a9b7SMilanka Ringwald                         break;
13052901a9b7SMilanka Ringwald                     }
1306835a13f1SMilanka Ringwald                     client->state = HIDS_CLIENT_STATE_CONNECTED;
13076bcfb631SMilanka Ringwald                     hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
13086bcfb631SMilanka Ringwald                     break;
1309f4d3b82aSMilanka Ringwald 
1310835a13f1SMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
1311835a13f1SMilanka Ringwald                 case HIDS_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT:
1312835a13f1SMilanka Ringwald                     client->state = HIDS_CLIENT_W2_SEND_GET_REPORT;
1313835a13f1SMilanka Ringwald                     break;
1314835a13f1SMilanka Ringwald #endif
1315f4d3b82aSMilanka Ringwald 
1316*af2241c2SMilanka Ringwald                 case HIDS_CLIENT_W4_VALUE_OF_CHARACTERISTIC_RESULT:
1317f4d3b82aSMilanka Ringwald                     client->state = HIDS_CLIENT_STATE_CONNECTED;
1318f4d3b82aSMilanka Ringwald                     break;
1319f4d3b82aSMilanka Ringwald 
1320cf26c8fbSMilanka Ringwald                 default:
1321cf26c8fbSMilanka Ringwald                     break;
1322cf26c8fbSMilanka Ringwald             }
1323cf26c8fbSMilanka Ringwald             break;
1324cf26c8fbSMilanka Ringwald 
1325cf26c8fbSMilanka Ringwald         default:
1326cf26c8fbSMilanka Ringwald             break;
1327cf26c8fbSMilanka Ringwald     }
13286bcfb631SMilanka Ringwald 
13296bcfb631SMilanka Ringwald     if (client != NULL){
13306bcfb631SMilanka Ringwald         hids_run_for_client(client);
13316bcfb631SMilanka Ringwald     }
1332cf26c8fbSMilanka Ringwald }
1333cf26c8fbSMilanka Ringwald 
13346bcfb631SMilanka 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){
1335cf26c8fbSMilanka Ringwald     btstack_assert(packet_handler != NULL);
1336cf26c8fbSMilanka Ringwald 
1337cf26c8fbSMilanka Ringwald     hids_client_t * client = hids_get_client_for_con_handle(con_handle);
1338cf26c8fbSMilanka Ringwald     if (client != NULL){
1339cf26c8fbSMilanka Ringwald         return ERROR_CODE_COMMAND_DISALLOWED;
1340cf26c8fbSMilanka Ringwald     }
1341cf26c8fbSMilanka Ringwald 
1342cf26c8fbSMilanka Ringwald     uint16_t cid = hids_get_next_cid();
1343cf26c8fbSMilanka Ringwald     if (hids_cid != NULL) {
1344cf26c8fbSMilanka Ringwald         *hids_cid = cid;
1345cf26c8fbSMilanka Ringwald     }
1346cf26c8fbSMilanka Ringwald 
1347cf26c8fbSMilanka Ringwald     client = hids_create_client(con_handle, cid);
1348cf26c8fbSMilanka Ringwald     if (client == NULL) {
1349cf26c8fbSMilanka Ringwald         return BTSTACK_MEMORY_ALLOC_FAILED;
1350cf26c8fbSMilanka Ringwald     }
1351cf26c8fbSMilanka Ringwald 
13526bcfb631SMilanka Ringwald     client->required_protocol_mode = protocol_mode;
1353cf26c8fbSMilanka Ringwald     client->client_handler = packet_handler;
1354cf26c8fbSMilanka Ringwald     client->state = HIDS_CLIENT_STATE_W2_QUERY_SERVICE;
1355cf26c8fbSMilanka Ringwald 
1356cf26c8fbSMilanka Ringwald     hids_run_for_client(client);
1357cf26c8fbSMilanka Ringwald     return ERROR_CODE_SUCCESS;
1358cf26c8fbSMilanka Ringwald }
1359cf26c8fbSMilanka Ringwald 
1360cf26c8fbSMilanka Ringwald uint8_t hids_client_disconnect(uint16_t hids_cid){
1361cf26c8fbSMilanka Ringwald     hids_client_t * client = hids_get_client_for_cid(hids_cid);
1362cf26c8fbSMilanka Ringwald     if (client == NULL){
1363cf26c8fbSMilanka Ringwald         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
1364cf26c8fbSMilanka Ringwald     }
1365cf26c8fbSMilanka Ringwald     // finalize connection
1366cf26c8fbSMilanka Ringwald     hids_finalize_client(client);
1367cf26c8fbSMilanka Ringwald     return ERROR_CODE_SUCCESS;
1368cf26c8fbSMilanka Ringwald }
1369cf26c8fbSMilanka Ringwald 
13701624214bSMilanka Ringwald uint8_t hids_client_send_report(uint16_t hids_cid, uint8_t report_id, const uint8_t * report, uint8_t report_len){
13711624214bSMilanka Ringwald     hids_client_t * client = hids_get_client_for_cid(hids_cid);
13721624214bSMilanka Ringwald     if (client == NULL){
13731624214bSMilanka Ringwald         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
13741624214bSMilanka Ringwald     }
13751624214bSMilanka Ringwald 
13761624214bSMilanka Ringwald     if (client->state != HIDS_CLIENT_STATE_CONNECTED) {
13771624214bSMilanka Ringwald         return ERROR_CODE_COMMAND_DISALLOWED;
13781624214bSMilanka Ringwald     }
13791624214bSMilanka Ringwald 
13802fe59e00SMilanka Ringwald     uint8_t report_index = find_report_index_for_report_id_and_type(client, report_id, HID_REPORT_TYPE_OUTPUT);
138183d7ed1cSMilanka Ringwald 
13821624214bSMilanka Ringwald     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
13831624214bSMilanka Ringwald         return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE;
13841624214bSMilanka Ringwald     }
13851624214bSMilanka Ringwald 
13861624214bSMilanka Ringwald     uint16_t mtu;
13871624214bSMilanka Ringwald     uint8_t status = gatt_client_get_mtu(client->con_handle, &mtu);
13881624214bSMilanka Ringwald 
13891624214bSMilanka Ringwald     if (status != ERROR_CODE_SUCCESS){
13901624214bSMilanka Ringwald         return status;
13911624214bSMilanka Ringwald     }
13921624214bSMilanka Ringwald 
13931624214bSMilanka Ringwald     if (mtu - 2 < report_len){
13941624214bSMilanka Ringwald         return ERROR_CODE_PARAMETER_OUT_OF_MANDATORY_RANGE;
13951624214bSMilanka Ringwald     }
13961624214bSMilanka Ringwald 
13971624214bSMilanka Ringwald     client->state = HIDS_CLIENT_W2_SEND_REPORT;
1398021192e1SMilanka Ringwald     client->report_index = report_index;
13991624214bSMilanka Ringwald     client->report = report;
14001624214bSMilanka Ringwald     client->report_len = report_len;
14011624214bSMilanka Ringwald 
14021624214bSMilanka Ringwald     hids_run_for_client(client);
14031624214bSMilanka Ringwald     return ERROR_CODE_SUCCESS;
14041624214bSMilanka Ringwald }
14051624214bSMilanka Ringwald 
140683d7ed1cSMilanka Ringwald uint8_t hids_client_send_get_report(uint16_t hids_cid, uint8_t report_id){
140783d7ed1cSMilanka Ringwald     hids_client_t * client = hids_get_client_for_cid(hids_cid);
140883d7ed1cSMilanka Ringwald     if (client == NULL){
140983d7ed1cSMilanka Ringwald         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
141083d7ed1cSMilanka Ringwald     }
141183d7ed1cSMilanka Ringwald 
141283d7ed1cSMilanka Ringwald     if (client->state != HIDS_CLIENT_STATE_CONNECTED) {
141383d7ed1cSMilanka Ringwald         return ERROR_CODE_COMMAND_DISALLOWED;
141483d7ed1cSMilanka Ringwald     }
141583d7ed1cSMilanka Ringwald 
1416835a13f1SMilanka Ringwald     uint8_t report_index = find_report_index_for_report_id(client, report_id);
141783d7ed1cSMilanka Ringwald     if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
141883d7ed1cSMilanka Ringwald         return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE;
141983d7ed1cSMilanka Ringwald     }
142083d7ed1cSMilanka Ringwald 
142183d7ed1cSMilanka Ringwald     client->report_index = report_index;
142283d7ed1cSMilanka Ringwald 
142383d7ed1cSMilanka Ringwald #ifdef ENABLE_TESTING_SUPPORT
142483d7ed1cSMilanka Ringwald     client->state = HIDS_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION;
142583d7ed1cSMilanka Ringwald #else
142683d7ed1cSMilanka Ringwald     client->state = HIDS_CLIENT_W2_SEND_GET_REPORT;
142783d7ed1cSMilanka Ringwald #endif
142883d7ed1cSMilanka Ringwald     hids_run_for_client(client);
142983d7ed1cSMilanka Ringwald     return ERROR_CODE_SUCCESS;
143083d7ed1cSMilanka Ringwald }
143183d7ed1cSMilanka Ringwald 
143283d7ed1cSMilanka Ringwald 
1433f4d3b82aSMilanka Ringwald uint8_t hids_client_get_hid_information(uint16_t hids_cid, uint8_t service_index){
1434f4d3b82aSMilanka Ringwald     hids_client_t * client = hids_get_client_for_cid(hids_cid);
1435f4d3b82aSMilanka Ringwald     if (client == NULL){
1436f4d3b82aSMilanka Ringwald         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
1437f4d3b82aSMilanka Ringwald     }
1438f4d3b82aSMilanka Ringwald 
1439f4d3b82aSMilanka Ringwald     if (client->state != HIDS_CLIENT_STATE_CONNECTED) {
1440f4d3b82aSMilanka Ringwald         return ERROR_CODE_COMMAND_DISALLOWED;
1441f4d3b82aSMilanka Ringwald     }
1442f4d3b82aSMilanka Ringwald 
1443f4d3b82aSMilanka Ringwald     if (service_index >= client->num_instances){
1444f4d3b82aSMilanka Ringwald         return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE;
1445f4d3b82aSMilanka Ringwald     }
1446f4d3b82aSMilanka Ringwald 
1447f4d3b82aSMilanka Ringwald     client->service_index = service_index;
1448*af2241c2SMilanka Ringwald     client->handle = client->services[client->service_index].hid_information_value_handle;
1449*af2241c2SMilanka Ringwald 
1450*af2241c2SMilanka Ringwald     client->state = HIDS_CLIENT_W2_READ_VALUE_OF_CHARACTERISTIC;
1451*af2241c2SMilanka Ringwald     hids_run_for_client(client);
1452*af2241c2SMilanka Ringwald     return ERROR_CODE_SUCCESS;
1453*af2241c2SMilanka Ringwald }
1454*af2241c2SMilanka Ringwald 
1455*af2241c2SMilanka Ringwald uint8_t hids_client_get_protocol_mode(uint16_t hids_cid, uint8_t service_index){
1456*af2241c2SMilanka Ringwald     hids_client_t * client = hids_get_client_for_cid(hids_cid);
1457*af2241c2SMilanka Ringwald     if (client == NULL){
1458*af2241c2SMilanka Ringwald         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
1459*af2241c2SMilanka Ringwald     }
1460*af2241c2SMilanka Ringwald 
1461*af2241c2SMilanka Ringwald     if (client->state != HIDS_CLIENT_STATE_CONNECTED) {
1462*af2241c2SMilanka Ringwald         return ERROR_CODE_COMMAND_DISALLOWED;
1463*af2241c2SMilanka Ringwald     }
1464*af2241c2SMilanka Ringwald 
1465*af2241c2SMilanka Ringwald     if (service_index >= client->num_instances){
1466*af2241c2SMilanka Ringwald         return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE;
1467*af2241c2SMilanka Ringwald     }
1468*af2241c2SMilanka Ringwald 
1469*af2241c2SMilanka Ringwald     client->service_index = service_index;
1470*af2241c2SMilanka Ringwald     client->handle = client->services[client->service_index].protocol_mode_value_handle;
1471*af2241c2SMilanka Ringwald 
1472*af2241c2SMilanka Ringwald     client->state = HIDS_CLIENT_W2_READ_VALUE_OF_CHARACTERISTIC;
1473f4d3b82aSMilanka Ringwald     hids_run_for_client(client);
1474f4d3b82aSMilanka Ringwald     return ERROR_CODE_SUCCESS;
1475f4d3b82aSMilanka Ringwald }
1476f4d3b82aSMilanka Ringwald 
1477021192e1SMilanka Ringwald void hids_client_init(uint8_t * hid_descriptor_storage, uint16_t hid_descriptor_storage_len){
1478021192e1SMilanka Ringwald     hids_client_descriptor_storage = hid_descriptor_storage;
1479021192e1SMilanka Ringwald     hids_client_descriptor_storage_len = hid_descriptor_storage_len;
1480021192e1SMilanka Ringwald }
1481cf26c8fbSMilanka Ringwald 
1482cf26c8fbSMilanka Ringwald void hids_client_deinit(void){}
1483