xref: /btstack/src/btstack_hid_parser.c (revision 73cbd4d97f92d480088752e70324cf243f97d616)
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 
58 typedef enum {
59     Main=0,
60     Global,
61     Local,
62     Reserved
63 } TagType;
64 
65 typedef enum {
66     Input=8,
67     Output,
68     Coll,
69     Feature,
70     EndColl
71 } MainItemTag;
72 
73 typedef enum {
74     UsagePage,
75     LogicalMinimum,
76     LogicalMaximum,
77     PhysicalMinimum,
78     PhysicalMaximum,
79     UnitExponent,
80     Unit,
81     ReportSize,
82     ReportID,
83     ReportCount,
84     Push,
85     Pop
86 } GlobalItemTag;
87 
88 typedef enum {
89     Usage,
90     UsageMinimum,
91     UsageMaximum,
92     DesignatorIndex,
93     DesignatorMinimum,
94     DesignatorMaximum,
95     StringIndex,
96     StringMinimum,
97     StringMaximum,
98     Delimiter
99 } LocalItemTag;
100 
101 const int hid_item_sizes[] = { 0, 1, 2, 4 };
102 
103 #ifdef HID_PARSER_PRETTY_PRINT
104 
105 static const char * type_names[] = {
106     "Main",
107     "Global",
108     "Local",
109     "Reserved"
110 };
111 static const char * main_tags[] = {
112     "",
113     "",
114     "",
115     "",
116     "",
117     "",
118     "",
119     "",
120     "Input ",
121     "Output",
122     "Collection",
123     "Feature",
124     "End Collection",
125     "Reserved",
126     "Reserved",
127     "Reserved"
128 };
129 static const char * global_tags[] = {
130     "Usage Page",
131     "Logical Minimum",
132     "Logical Maximum",
133     "Physical Minimum",
134     "Physical Maximum",
135     "Unit Exponent",
136     "Unit",
137     "Report Size",
138     "Report ID",
139     "Report Count",
140     "Push",
141     "Pop",
142     "Reserved",
143     "Reserved",
144     "Reserved",
145     "Reserved"
146 };
147 static const char * local_tags[] = {
148     "Usage",
149     "Usage Minimum",
150     "Usage Maximum",
151     "Designator Index",
152     "Designator Minimum",
153     "Designator Maximum",
154     "String Index",
155     "String Minimum",
156     "String Maximum",
157     "Delimiter",
158     "Reserved",
159     "Reserved",
160     "Reserved",
161     "Reserved",
162     "Reserved",
163     "Reserved"
164 };
165 #endif
166 
167 static void hid_pretty_print_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){
168 #ifdef HID_PARSER_PRETTY_PRINT
169     const char ** item_tag_table;
170     switch (item->item_type){
171         case Main:
172             item_tag_table = main_tags;
173             break;
174         case Global:
175             item_tag_table = global_tags;
176             break;
177         case Local:
178             item_tag_table = local_tags;
179             break;
180         default:
181             item_tag_table = NULL;
182             break;
183     }
184     const char * item_tag_name = "Invalid";
185     if (item_tag_table){
186         item_tag_name = item_tag_table[item->item_tag];
187     }
188     log_info("%-15s (%-6s) // %02x 0x%0008x", item_tag_table[item->item_tag], type_names[item->item_type], parser->descriptor[parser->descriptor_pos], item->item_value);
189 #else
190     UNUSED(parser);
191     UNUSED(item);
192 #endif
193 }
194 
195 // parse descriptor item and read up to 32-bit bit value
196 static void btstack_hid_parse_descriptor_item(hid_descriptor_item_t * item, const uint8_t * hid_descriptor, uint16_t hid_descriptor_len){
197     // parse item header
198     if (hid_descriptor_len < 1) return;
199     uint16_t pos = 0;
200     uint8_t item_header = hid_descriptor[pos++];
201     item->data_size = hid_item_sizes[item_header & 0x03];
202     item->item_type = (item_header & 0x0c) >> 2;
203     item->item_tag  = (item_header & 0xf0) >> 4;
204     // long item
205     if (item->data_size == 2 && item->item_tag == 0x0f && item->item_type == 3){
206         if (hid_descriptor_len < 3) return;
207         item->data_size = hid_descriptor[pos++];
208         item->item_tag  = hid_descriptor[pos++];
209     }
210     item->item_size =  pos + item->data_size;
211     item->item_value = 0;
212 
213     // read item value
214     if (hid_descriptor_len < item->item_size) return;
215     if (item->data_size > 4) return;
216     int i;
217     int sgnd = item->item_type == Global && item->item_tag > 0 && item->item_tag < 5;
218     int32_t value = 0;
219     uint8_t latest_byte = 0;
220     for (i=0;i<item->data_size;i++){
221         latest_byte = hid_descriptor[pos++];
222         value = (latest_byte << (8*i)) | value;
223     }
224     if (sgnd && item->data_size > 0){
225         if (latest_byte & 0x80) {
226             value -= 1 << (item->data_size*8);
227         }
228     }
229     item->item_value = value;
230 }
231 
232 static void btstack_hid_handle_global_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){
233     switch(item->item_tag){
234         case UsagePage:
235             parser->global_usage_page = item->item_value;
236             break;
237         case LogicalMinimum:
238             parser->global_logical_minimum = item->item_value;
239             break;
240         case LogicalMaximum:
241             parser->global_logical_maximum = item->item_value;
242             break;
243         case ReportSize:
244             parser->global_report_size = item->item_value;
245             break;
246         case ReportID:
247             if (parser->active_record && parser->global_report_id != item->item_value){
248                 // log_debug("New report, don't match anymore");
249                 parser->active_record = 0;
250             }
251             parser->global_report_id = item->item_value;
252             // log_info("- Report ID: %02x", parser->global_report_id);
253             break;
254         case ReportCount:
255             parser->global_report_count = item->item_value;
256             break;
257         default:
258             break;
259     }
260 }
261 
262 static void hid_find_next_usage(btstack_hid_parser_t * parser){
263     while (parser->available_usages == 0 && parser->usage_pos < parser->descriptor_pos){
264         hid_descriptor_item_t usage_item;
265         // parser->usage_pos < parser->descriptor_pos < parser->descriptor_len
266         btstack_hid_parse_descriptor_item(&usage_item, &parser->descriptor[parser->usage_pos], parser->descriptor_len - parser->usage_pos);
267         if (usage_item.item_type == Global && usage_item.item_tag == UsagePage){
268             parser->usage_page = usage_item.item_value;
269         }
270         if (usage_item.item_type == Local){
271             uint32_t usage_value = (usage_item.data_size > 2) ? usage_item.item_value : (parser->usage_page << 16) | usage_item.item_value;
272             switch (usage_item.item_tag){
273                 case Usage:
274                     parser->available_usages = 1;
275                     parser->usage_minimum = usage_value;
276                     break;
277                 case UsageMinimum:
278                     parser->usage_minimum = usage_value;
279                     parser->have_usage_min = 1;
280                     break;
281                 case UsageMaximum:
282                     parser->usage_maximum = usage_value;
283                     parser->have_usage_max = 1;
284                     break;
285                 default:
286                     break;
287             }
288             if (parser->have_usage_min && parser->have_usage_max){
289                 parser->available_usages = parser->usage_maximum - parser->usage_minimum + 1;
290                 parser->have_usage_min = 0;
291                 parser->have_usage_max = 0;
292             }
293         }
294         parser->usage_pos += usage_item.item_size;
295     }
296 }
297 
298 static void hid_process_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){
299     hid_pretty_print_item(parser, item);
300     int valid_field = 0;
301     switch (item->item_type){
302         case Main:
303             switch (item->item_tag){
304                 case Input:
305                     valid_field = parser->report_type == BTSTACK_HID_REPORT_TYPE_INPUT;
306                     break;
307                 case Output:
308                     valid_field = parser->report_type == BTSTACK_HID_REPORT_TYPE_OUTPUT;
309                     break;
310                 case Feature:
311                     valid_field = parser->report_type == BTSTACK_HID_REPORT_TYPE_FEATURE;
312                     break;
313                 default:
314                     break;
315             }
316             break;
317         case Global:
318             btstack_hid_handle_global_item(parser, item);
319             break;
320         case Local:
321             break;
322     }
323     if (!valid_field) return;
324 
325     // verify record id
326     if (parser->global_report_id && !parser->active_record){
327         if (parser->report[0] != parser->global_report_id){
328             return;
329         }
330         parser->report_pos_in_bit += 8;
331     }
332     parser->active_record = 1;
333     // handle constant fields used for padding
334     if (item->item_value & 1){
335         int item_bits = parser->global_report_size * parser->global_report_count;
336 #ifdef HID_PARSER_PRETTY_PRINT
337         log_info("- Skip %u constant bits", item_bits);
338 #endif
339         parser->report_pos_in_bit += item_bits;
340         return;
341     }
342     // Empty Item
343     if (parser->global_report_count == 0) return;
344     // let's start
345     parser->required_usages = parser->global_report_count;
346 }
347 
348 static void hid_post_process_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){
349     if (item->item_type == Main){
350         // reset usage
351         parser->usage_pos  = parser->descriptor_pos;
352         parser->usage_page = parser->global_usage_page;
353     }
354     parser->descriptor_pos += item->item_size;
355 }
356 
357 static void btstack_hid_parser_find_next_usage(btstack_hid_parser_t * parser){
358     while (parser->state == BTSTACK_HID_PARSER_SCAN_FOR_REPORT_ITEM){
359         if (parser->descriptor_pos >= parser->descriptor_len){
360             // end of descriptor
361             parser->state = BTSTACK_HID_PARSER_COMPLETE;
362             break;
363         }
364         btstack_hid_parse_descriptor_item(&parser->descriptor_item, &parser->descriptor[parser->descriptor_pos], parser->descriptor_len - parser->descriptor_pos);
365         hid_process_item(parser, &parser->descriptor_item);
366         if (parser->required_usages){
367             hid_find_next_usage(parser);
368             if (parser->available_usages) {
369                 parser->state = BTSTACK_HID_PARSER_USAGES_AVAILABLE;
370             } else {
371                 log_error("no usages found");
372                 parser->state = BTSTACK_HID_PARSER_COMPLETE;
373             }
374         } else {
375             hid_post_process_item(parser, &parser->descriptor_item);
376         }
377     }
378 }
379 
380 // PUBLIC API
381 
382 void btstack_hid_parser_init(btstack_hid_parser_t * parser, const uint8_t * hid_descriptor, uint16_t hid_descriptor_len, btstack_hid_report_type_t hid_report_type, const uint8_t * hid_report, uint16_t hid_report_len){
383 
384     memset(parser, 0, sizeof(btstack_hid_parser_t));
385 
386     parser->descriptor     = hid_descriptor;
387     parser->descriptor_len = hid_descriptor_len;
388     parser->report_type    = hid_report_type;
389     parser->report         = hid_report;
390     parser->report_len     = hid_report_len;
391     parser->state          = BTSTACK_HID_PARSER_SCAN_FOR_REPORT_ITEM;
392 
393     btstack_hid_parser_find_next_usage(parser);
394 }
395 
396 int  btstack_hid_parser_has_more(btstack_hid_parser_t * parser){
397     return parser->state == BTSTACK_HID_PARSER_USAGES_AVAILABLE;
398 }
399 
400 void btstack_hid_parser_get_field(btstack_hid_parser_t * parser, uint16_t * usage_page, uint16_t * usage, int32_t * value){
401 
402     *usage_page = parser->usage_minimum >> 16;
403 
404     // read field (up to 32 bit unsigned, up to 31 bit signed - 32 bit signed behaviour is undefined) - check report len
405     int is_variable   = parser->descriptor_item.item_value & 2;
406     int is_signed     = parser->global_logical_minimum < 0;
407     int pos_start     = btstack_min(  parser->report_pos_in_bit >> 3, parser->report_len);
408     int pos_end       = btstack_min( (parser->report_pos_in_bit + parser->global_report_size - 1) >> 3, parser->report_len);
409     int bytes_to_read = pos_end - pos_start + 1;
410     int i;
411     uint32_t multi_byte_value = 0;
412     for (i=0;i < bytes_to_read;i++){
413         multi_byte_value |= parser->report[pos_start+i] << (i*8);
414     }
415     uint32_t unsigned_value = (multi_byte_value >> (parser->report_pos_in_bit & 0x07)) & ((1<<parser->global_report_size)-1);
416     // 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);
417     if (is_variable){
418         *usage      = parser->usage_minimum & 0xffff;
419         if (is_signed && (unsigned_value & (1<<(parser->global_report_size-1)))){
420             *value = unsigned_value - (1<<parser->global_report_size);
421         } else {
422             *value = unsigned_value;
423         }
424     } else {
425         *usage  = unsigned_value;
426         *value  = 1;
427     }
428     parser->required_usages--;
429     parser->report_pos_in_bit += parser->global_report_size;
430 
431     // next usage
432     if (is_variable){
433         parser->usage_minimum++;
434         parser->available_usages--;
435     } else {
436         if (parser->required_usages == 0){
437             parser->available_usages = 0;
438         }
439     }
440     if (parser->available_usages) {
441         return;
442     }
443     if (parser->required_usages == 0){
444         hid_post_process_item(parser, &parser->descriptor_item);
445         parser->state = BTSTACK_HID_PARSER_SCAN_FOR_REPORT_ITEM;
446         btstack_hid_parser_find_next_usage(parser);
447     } else {
448         hid_find_next_usage(parser);
449         if (parser->available_usages == 0) {
450             parser->state = BTSTACK_HID_PARSER_COMPLETE;
451         }
452     }
453 }
454