xref: /btstack/src/btstack_hid_parser.c (revision 7cdc89a533ca236b2c2564b759993b788bae89d3)
1 /*
2  * Copyright (C) 2017 BlueKitchen GmbH
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the copyright holders nor the names of
14  *    contributors may be used to endorse or promote products derived
15  *    from this software without specific prior written permission.
16  * 4. Any redistribution, use, or modification is done solely for
17  *    personal benefit and not for any commercial purpose or for
18  *    monetary gain.
19  *
20  * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
24  * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
30  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * Please inquire about commercial licensing options at
34  * [email protected]
35  *
36  */
37 
38 #define BTSTACK_FILE__ "btstack_hid_parser.c"
39 
40 #include <string.h>
41 
42 #include "btstack_hid_parser.h"
43 #include "btstack_util.h"
44 #include "btstack_debug.h"
45 
46 // Not implemented:
47 // - Support for Push/Pop
48 // - Optional Pretty Print of HID Descripor
49 // - Support to query descriptort for contained usages, e.g. to detect keyboard or mouse
50 
51 // #define HID_PARSER_PRETTY_PRINT
52 
53 /*
54  *  btstack_hid_parser.c
55  */
56 
57 const int hid_item_sizes[] = { 0, 1, 2, 4 };
58 
59 #ifdef HID_PARSER_PRETTY_PRINT
60 
61 static const char * type_names[] = {
62     "Main",
63     "Global",
64     "Local",
65     "Reserved"
66 };
67 static const char * main_tags[] = {
68     "",
69     "",
70     "",
71     "",
72     "",
73     "",
74     "",
75     "",
76     "Input ",
77     "Output",
78     "Collection",
79     "Feature",
80     "End Collection",
81     "Reserved",
82     "Reserved",
83     "Reserved"
84 };
85 static const char * global_tags[] = {
86     "Usage Page",
87     "Logical Minimum",
88     "Logical Maximum",
89     "Physical Minimum",
90     "Physical Maximum",
91     "Unit Exponent",
92     "Unit",
93     "Report Size",
94     "Report ID",
95     "Report Count",
96     "Push",
97     "Pop",
98     "Reserved",
99     "Reserved",
100     "Reserved",
101     "Reserved"
102 };
103 static const char * local_tags[] = {
104     "Usage",
105     "Usage Minimum",
106     "Usage Maximum",
107     "Designator Index",
108     "Designator Minimum",
109     "Designator Maximum",
110     "String Index",
111     "String Minimum",
112     "String Maximum",
113     "Delimiter",
114     "Reserved",
115     "Reserved",
116     "Reserved",
117     "Reserved",
118     "Reserved",
119     "Reserved"
120 };
121 #endif
122 
123 static void hid_pretty_print_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){
124 #ifdef HID_PARSER_PRETTY_PRINT
125     const char ** item_tag_table;
126     switch (item->item_type){
127         case Main:
128             item_tag_table = main_tags;
129             break;
130         case Global:
131             item_tag_table = global_tags;
132             break;
133         case Local:
134             item_tag_table = local_tags;
135             break;
136         default:
137             item_tag_table = NULL;
138             break;
139     }
140     const char * item_tag_name = "Invalid";
141     if (item_tag_table){
142         item_tag_name = item_tag_table[item->item_tag];
143     }
144     log_info("%-15s (%-6s) // %02x 0x%0008x", item_tag_name, type_names[item->item_type], parser->descriptor[parser->descriptor_pos], item->item_value);
145 #else
146     UNUSED(parser);
147     UNUSED(item);
148 #endif
149 }
150 
151 // parse descriptor item and read up to 32-bit bit value
152 void btstack_hid_parse_descriptor_item(hid_descriptor_item_t * item, const uint8_t * hid_descriptor, uint16_t hid_descriptor_len){
153     // parse item header
154     if (hid_descriptor_len < 1) return;
155     uint16_t pos = 0;
156     uint8_t item_header = hid_descriptor[pos++];
157     item->data_size = hid_item_sizes[item_header & 0x03];
158     item->item_type = (item_header & 0x0c) >> 2;
159     item->item_tag  = (item_header & 0xf0) >> 4;
160     // long item
161     if ((item->data_size == 2) && (item->item_tag == 0x0f) && (item->item_type == 3)){
162         if (hid_descriptor_len < 3) return;
163         item->data_size = hid_descriptor[pos++];
164         item->item_tag  = hid_descriptor[pos++];
165     }
166     item->item_size =  pos + item->data_size;
167     item->item_value = 0;
168 
169     // read item value
170     if (hid_descriptor_len < item->item_size) return;
171     if (item->data_size > 4) return;
172     int i;
173     int sgnd = (item->item_type == Global) && (item->item_tag > 0) && (item->item_tag < 5);
174     int32_t value = 0;
175     uint8_t latest_byte = 0;
176     for (i=0;i<item->data_size;i++){
177         latest_byte = hid_descriptor[pos++];
178         value = (latest_byte << (8*i)) | value;
179     }
180     if (sgnd && (item->data_size > 0)){
181         if (latest_byte & 0x80) {
182             value -= 1 << (item->data_size*8);
183         }
184     }
185     item->item_value = value;
186 }
187 
188 static void btstack_hid_handle_global_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){
189     switch(item->item_tag){
190         case UsagePage:
191             parser->global_usage_page = item->item_value;
192             break;
193         case LogicalMinimum:
194             parser->global_logical_minimum = item->item_value;
195             break;
196         case LogicalMaximum:
197             parser->global_logical_maximum = item->item_value;
198             break;
199         case ReportSize:
200             parser->global_report_size = item->item_value;
201             break;
202         case ReportID:
203             if (parser->active_record && (parser->global_report_id != item->item_value)){
204                 // log_debug("New report, don't match anymore");
205                 parser->active_record = 0;
206             }
207             parser->global_report_id = item->item_value;
208             // log_info("- Report ID: %02x", parser->global_report_id);
209             break;
210         case ReportCount:
211             parser->global_report_count = item->item_value;
212             break;
213         default:
214             break;
215     }
216 }
217 
218 static void hid_find_next_usage(btstack_hid_parser_t * parser){
219     while ((parser->available_usages == 0) && (parser->usage_pos < parser->descriptor_pos)){
220         hid_descriptor_item_t usage_item;
221         // parser->usage_pos < parser->descriptor_pos < parser->descriptor_len
222         btstack_hid_parse_descriptor_item(&usage_item, &parser->descriptor[parser->usage_pos], parser->descriptor_len - parser->usage_pos);
223         if ((usage_item.item_type == Global) && (usage_item.item_tag == UsagePage)){
224             parser->usage_page = usage_item.item_value;
225         }
226         if (usage_item.item_type == Local){
227             uint32_t usage_value = (usage_item.data_size > 2) ? usage_item.item_value : ((parser->usage_page << 16) | usage_item.item_value);
228             switch (usage_item.item_tag){
229                 case Usage:
230                     parser->available_usages = 1;
231                     parser->usage_minimum = usage_value;
232                     break;
233                 case UsageMinimum:
234                     parser->usage_minimum = usage_value;
235                     parser->have_usage_min = 1;
236                     break;
237                 case UsageMaximum:
238                     parser->usage_maximum = usage_value;
239                     parser->have_usage_max = 1;
240                     break;
241                 default:
242                     break;
243             }
244             if (parser->have_usage_min && parser->have_usage_max){
245                 parser->available_usages = parser->usage_maximum - parser->usage_minimum + 1;
246                 parser->have_usage_min = 0;
247                 parser->have_usage_max = 0;
248             }
249         }
250         parser->usage_pos += usage_item.item_size;
251     }
252 }
253 
254 static void hid_process_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){
255     hid_pretty_print_item(parser, item);
256     int valid_field = 0;
257     switch (item->item_type){
258         case Main:
259             switch (item->item_tag){
260                 case Input:
261                     valid_field = parser->report_type == HID_REPORT_TYPE_INPUT;
262                     break;
263                 case Output:
264                     valid_field = parser->report_type == HID_REPORT_TYPE_OUTPUT;
265                     break;
266                 case Feature:
267                     valid_field = parser->report_type == HID_REPORT_TYPE_FEATURE;
268                     break;
269                 default:
270                     break;
271             }
272             break;
273         case Global:
274             btstack_hid_handle_global_item(parser, item);
275             break;
276         case Local:
277             break;
278     }
279     if (!valid_field) return;
280 
281     // verify record id
282     if (parser->global_report_id && !parser->active_record){
283         if (parser->report[0] != parser->global_report_id){
284             return;
285         }
286         parser->report_pos_in_bit += 8;
287     }
288     parser->active_record = 1;
289     // handle constant fields used for padding
290     if (item->item_value & 1){
291         int item_bits = parser->global_report_size * parser->global_report_count;
292 #ifdef HID_PARSER_PRETTY_PRINT
293         log_info("- Skip %u constant bits", item_bits);
294 #endif
295         parser->report_pos_in_bit += item_bits;
296         return;
297     }
298     // Empty Item
299     if (parser->global_report_count == 0) return;
300     // let's start
301     parser->required_usages = parser->global_report_count;
302 }
303 
304 static void hid_post_process_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){
305     if (item->item_type == Main){
306         // reset usage
307         parser->usage_pos  = parser->descriptor_pos;
308         parser->usage_page = parser->global_usage_page;
309     }
310     parser->descriptor_pos += item->item_size;
311 }
312 
313 static void btstack_hid_parser_find_next_usage(btstack_hid_parser_t * parser){
314     while (parser->state == BTSTACK_HID_PARSER_SCAN_FOR_REPORT_ITEM){
315         if (parser->descriptor_pos >= parser->descriptor_len){
316             // end of descriptor
317             parser->state = BTSTACK_HID_PARSER_COMPLETE;
318             break;
319         }
320         btstack_hid_parse_descriptor_item(&parser->descriptor_item, &parser->descriptor[parser->descriptor_pos], parser->descriptor_len - parser->descriptor_pos);
321         hid_process_item(parser, &parser->descriptor_item);
322         if (parser->required_usages){
323             hid_find_next_usage(parser);
324             if (parser->available_usages) {
325                 parser->state = BTSTACK_HID_PARSER_USAGES_AVAILABLE;
326             } else {
327                 log_error("no usages found");
328                 parser->state = BTSTACK_HID_PARSER_COMPLETE;
329             }
330         } else {
331             hid_post_process_item(parser, &parser->descriptor_item);
332         }
333     }
334 }
335 
336 // PUBLIC API
337 
338 void btstack_hid_parser_init(btstack_hid_parser_t * parser, const uint8_t * hid_descriptor, uint16_t hid_descriptor_len, hid_report_type_t hid_report_type, const uint8_t * hid_report, uint16_t hid_report_len){
339 
340     memset(parser, 0, sizeof(btstack_hid_parser_t));
341 
342     parser->descriptor     = hid_descriptor;
343     parser->descriptor_len = hid_descriptor_len;
344     parser->report_type    = hid_report_type;
345     parser->report         = hid_report;
346     parser->report_len     = hid_report_len;
347     parser->state          = BTSTACK_HID_PARSER_SCAN_FOR_REPORT_ITEM;
348 
349     btstack_hid_parser_find_next_usage(parser);
350 }
351 
352 int  btstack_hid_parser_has_more(btstack_hid_parser_t * parser){
353     return parser->state == BTSTACK_HID_PARSER_USAGES_AVAILABLE;
354 }
355 
356 void btstack_hid_parser_get_field(btstack_hid_parser_t * parser, uint16_t * usage_page, uint16_t * usage, int32_t * value){
357 
358     *usage_page = parser->usage_minimum >> 16;
359 
360     // read field (up to 32 bit unsigned, up to 31 bit signed - 32 bit signed behaviour is undefined) - check report len
361     int is_variable   = parser->descriptor_item.item_value & 2;
362     int is_signed     = parser->global_logical_minimum < 0;
363     int pos_start     = btstack_min(  parser->report_pos_in_bit >> 3, parser->report_len);
364     int pos_end       = btstack_min( (parser->report_pos_in_bit + parser->global_report_size - 1) >> 3, parser->report_len);
365     int bytes_to_read = pos_end - pos_start + 1;
366     int i;
367     uint32_t multi_byte_value = 0;
368     for (i=0;i < bytes_to_read;i++){
369         multi_byte_value |= parser->report[pos_start+i] << (i*8);
370     }
371     uint32_t unsigned_value = (multi_byte_value >> (parser->report_pos_in_bit & 0x07)) & ((1<<parser->global_report_size)-1);
372     // log_debug("bit pos %2u, report size %u, start %u, end %u, len %u;; unsigned value %08x", parser->report_pos_in_bit, parser->global_report_size, pos_start, pos_end, parser->report_len, unsigned_value);
373     if (is_variable){
374         *usage      = parser->usage_minimum & 0xffff;
375         if (is_signed && (unsigned_value & (1<<(parser->global_report_size-1)))){
376             *value = unsigned_value - (1<<parser->global_report_size);
377         } else {
378             *value = unsigned_value;
379         }
380     } else {
381         *usage  = unsigned_value;
382         *value  = 1;
383     }
384     parser->required_usages--;
385     parser->report_pos_in_bit += parser->global_report_size;
386 
387     // next usage
388     if (is_variable){
389         parser->usage_minimum++;
390         parser->available_usages--;
391     } else {
392         if (parser->required_usages == 0){
393             parser->available_usages = 0;
394         }
395     }
396     if (parser->available_usages) {
397         return;
398     }
399     if (parser->required_usages == 0){
400         hid_post_process_item(parser, &parser->descriptor_item);
401         parser->state = BTSTACK_HID_PARSER_SCAN_FOR_REPORT_ITEM;
402         btstack_hid_parser_find_next_usage(parser);
403     } else {
404         hid_find_next_usage(parser);
405         if (parser->available_usages == 0) {
406             parser->state = BTSTACK_HID_PARSER_COMPLETE;
407         }
408     }
409 }
410 
411 int btstack_hid_get_report_size_for_id(int report_id, hid_report_type_t report_type, uint16_t hid_descriptor_len, const uint8_t * hid_descriptor){
412     int total_report_size = 0;
413     int report_size = 0;
414     int report_count = 0;
415     int current_report_id = 0;
416 
417     while (hid_descriptor_len){
418         int valid_report_type = 0;
419         hid_descriptor_item_t item;
420         // printf("item: 0x%02x (%p)\n", *hid_descriptor, hid_descriptor);
421         btstack_hid_parse_descriptor_item(&item, hid_descriptor, hid_descriptor_len);
422         switch (item.item_type){
423             case Global:
424                 switch ((GlobalItemTag)item.item_tag){
425                     case ReportID:
426                         current_report_id = item.item_value;
427                         break;
428                     case ReportCount:
429                         if (current_report_id != report_id) break;
430                         report_count = item.item_value;
431                         break;
432                     case ReportSize:
433                         if (current_report_id != report_id) break;
434                         report_size = item.item_value;
435                         break;
436                     default:
437                         break;
438                 }
439                 break;
440             case Main:
441                 if (current_report_id != report_id) break;
442                 switch ((MainItemTag)item.item_tag){
443                     case Input:
444                         if (report_type != HID_REPORT_TYPE_INPUT) break;
445                         valid_report_type = 1;
446                         break;
447                     case Output:
448                         if (report_type != HID_REPORT_TYPE_OUTPUT) break;
449                         valid_report_type = 1;
450                         break;
451                     case Feature:
452                         if (report_type != HID_REPORT_TYPE_FEATURE) break;
453                         valid_report_type = 1;
454                         break;
455                     default:
456                         break;
457                 }
458                 if (!valid_report_type) break;
459                 total_report_size += report_count * report_size;
460                 report_size = 0;
461                 report_count = 0;
462                 break;
463             default:
464                 break;
465         }
466         hid_descriptor_len -= item.item_size;
467         hid_descriptor += item.item_size;
468     }
469     return (total_report_size + 7)/8;
470 }
471 
472 hid_report_id_status_t btstack_hid_id_valid(int report_id, uint16_t hid_descriptor_len, const uint8_t * hid_descriptor){
473     int current_report_id = 0;
474     while (hid_descriptor_len){
475         hid_descriptor_item_t item;
476         btstack_hid_parse_descriptor_item(&item, hid_descriptor, hid_descriptor_len);
477         switch (item.item_type){
478             case Global:
479                 switch ((GlobalItemTag)item.item_tag){
480                     case ReportID:
481                         current_report_id = item.item_value;
482                         // printf("current ID %d, searched ID %d\n", current_report_id, report_id);
483                         if (current_report_id != report_id) break;
484                         return HID_REPORT_ID_VALID;
485                     default:
486                         break;
487                 }
488                 break;
489             default:
490                 break;
491         }
492         hid_descriptor_len -= item.item_size;
493         hid_descriptor += item.item_size;
494     }
495     if (current_report_id != 0) return HID_REPORT_ID_INVALID;
496     return HID_REPORT_ID_UNDECLARED;
497 }
498 
499 int btstack_hid_report_id_declared(uint16_t hid_descriptor_len, const uint8_t * hid_descriptor){
500     while (hid_descriptor_len){
501         hid_descriptor_item_t item;
502         btstack_hid_parse_descriptor_item(&item, hid_descriptor, hid_descriptor_len);
503         switch (item.item_type){
504             case Global:
505                 switch ((GlobalItemTag)item.item_tag){
506                     case ReportID:
507                         return 1;
508                     default:
509                         break;
510                 }
511                 break;
512             default:
513                 break;
514         }
515         hid_descriptor_len -= item.item_size;
516         hid_descriptor += item.item_size;
517     }
518     return 0;
519 }
520