diff --git a/src/btstack.h b/src/btstack.h index ae12e4a9f..3aac45a0d 100644 --- a/src/btstack.h +++ b/src/btstack.h @@ -56,6 +56,7 @@ #include "btstack_debug.h" #include "btstack_defines.h" #include "btstack_event.h" +#include "btstack_hid_parser.h" #include "btstack_linked_list.h" #include "btstack_memory.h" #include "btstack_memory_pool.h" diff --git a/src/btstack_hid_parser.c b/src/btstack_hid_parser.c new file mode 100644 index 000000000..130cc2be5 --- /dev/null +++ b/src/btstack_hid_parser.c @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2017 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#define __BTSTACK_FILE__ "btstack_hid_parser.c" + +#include + +#include "btstack_hid_parser.h" +#include "btstack_util.h" +#include "btstack_debug.h" + +// Not implemented: +// - Support for Push/Pop +// - Optional Pretty Print of HID Descripor +// - Support to query descriptort for contained usages, e.g. to detect keyboard or mouse + +// #define HID_PARSER_PRETTY_PRINT + +/* + * btstack_hid_parser.c + */ + + +typedef enum { + Main=0, + Global, + Local, + Reserved +} TagType; + +typedef enum { + Input=8, + Output, + Coll, + Feature, + EndColl +} MainItemTag; + +typedef enum { + UsagePage, + LogicalMinimum, + LogicalMaximum, + PhysicalMinimum, + PhysicalMaximum, + UnitExponent, + Unit, + ReportSize, + ReportID, + ReportCount, + Push, + Pop +} GlobalItemTag; + +typedef enum { + Usage, + UsageMinimum, + UsageMaximum, + DesignatorIndex, + DesignatorMinimum, + DesignatorMaximum, + StringIndex, + StringMinimum, + StringMaximum, + Delimiter +} LocalItemTag; + +const int hid_item_sizes[] = { 0, 1, 2, 4 }; + +#ifdef HID_PARSER_PRETTY_PRINT + +static const char * type_names[] = { + "Main", + "Global", + "Local", + "Reserved" +}; +static const char * main_tags[] = { + "", + "", + "", + "", + "", + "", + "", + "", + "Input ", + "Output", + "Collection", + "Feature", + "End Collection", + "Reserved", + "Reserved", + "Reserved" +}; +static const char * global_tags[] = { + "Usage Page", + "Logical Minimum", + "Logical Maximum", + "Physical Minimum", + "Physical Maximum", + "Unit Exponent", + "Unit", + "Report Size", + "Report ID", + "Report Count", + "Push", + "Pop", + "Reserved", + "Reserved", + "Reserved", + "Reserved" +}; +static const char * local_tags[] = { + "Usage", + "Usage Minimum", + "Usage Maximum", + "Designator Index", + "Designator Minimum", + "Designator Maximum", + "String Index", + "String Minimum", + "String Maximum", + "Delimiter", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved" +}; +#endif + +static void hid_pretty_print_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){ +#ifdef HID_PARSER_PRETTY_PRINT + const char ** item_tag_table; + switch (item->item_type){ + case Main: + item_tag_table = main_tags; + break; + case Global: + item_tag_table = global_tags; + break; + case Local: + item_tag_table = local_tags; + break; + default: + item_tag_table = NULL; + break; + } + const char * item_tag_name = "Invalid"; + if (item_tag_table){ + item_tag_name = item_tag_table[item->item_tag]; + } + 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); +#else + UNUSED(parser); + UNUSED(item); +#endif +} + +// parse descriptor item and read up to 32-bit bit value +static void btstack_hid_parse_descriptor_item(hid_descriptor_item_t * item, const uint8_t * hid_descriptor, uint16_t hid_descriptor_len){ + // parse item header + if (hid_descriptor_len < 1) return; + uint16_t pos = 0; + uint8_t item_header = hid_descriptor[pos++]; + item->data_size = hid_item_sizes[item_header & 0x03]; + item->item_type = (item_header & 0x0c) >> 2; + item->item_tag = (item_header & 0xf0) >> 4; + // long item + if (item->data_size == 2 && item->item_tag == 0x0f && item->item_type == 3){ + if (hid_descriptor_len < 3) return; + item->data_size = hid_descriptor[pos++]; + item->item_tag = hid_descriptor[pos++]; + } + item->item_size = pos + item->data_size; + item->item_value = 0; + + // read item value + if (hid_descriptor_len < item->item_size) return; + if (item->data_size > 4) return; + int i; + int sgnd = item->item_type == Global && item->item_tag > 0 && item->item_tag < 5; + int32_t value = 0; + uint8_t latest_byte = 0; + for (i=0;idata_size;i++){ + latest_byte = hid_descriptor[pos++]; + value = (latest_byte << (8*i)) | value; + } + if (sgnd && item->data_size > 0){ + if (latest_byte & 0x80) { + value -= 1 << (item->data_size*8); + } + } + item->item_value = value; +} + +static void btstack_hid_handle_global_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){ + switch(item->item_tag){ + case UsagePage: + parser->global_usage_page = item->item_value; + break; + case LogicalMinimum: + parser->global_logical_minimum = item->item_value; + break; + case LogicalMaximum: + parser->global_logical_maximum = item->item_value; + break; + case ReportSize: + parser->global_report_size = item->item_value; + break; + case ReportID: + if (parser->active_record && parser->global_report_id != item->item_value){ + // log_debug("New report, don't match anymore"); + parser->active_record = 0; + } + parser->global_report_id = item->item_value; + // log_info("- Report ID: %02x", parser->global_report_id); + break; + case ReportCount: + parser->global_report_count = item->item_value; + break; + default: + break; + } +} + +static void hid_find_next_usage(btstack_hid_parser_t * parser){ + while (parser->available_usages == 0 && parser->usage_pos < parser->descriptor_pos){ + hid_descriptor_item_t usage_item; + // parser->usage_pos < parser->descriptor_pos < parser->descriptor_len + btstack_hid_parse_descriptor_item(&usage_item, &parser->descriptor[parser->usage_pos], parser->descriptor_len - parser->usage_pos); + if (usage_item.item_type == Global && usage_item.item_tag == UsagePage){ + parser->usage_page = usage_item.item_value; + } + if (usage_item.item_type == Local){ + uint32_t usage_value = (usage_item.data_size > 2) ? usage_item.item_value : (parser->usage_page << 16) | usage_item.item_value; + switch (usage_item.item_tag){ + case Usage: + parser->available_usages = 1; + parser->usage_minimum = usage_value; + break; + case UsageMinimum: + parser->usage_minimum = usage_value; + parser->have_usage_min = 1; + break; + case UsageMaximum: + parser->usage_maximum = usage_value; + parser->have_usage_max = 1; + break; + default: + break; + } + if (parser->have_usage_min && parser->have_usage_max){ + parser->available_usages = parser->usage_maximum - parser->usage_minimum + 1; + parser->have_usage_min = 0; + parser->have_usage_max = 0; + } + } + parser->usage_pos += usage_item.item_size; + } +} + +static void hid_process_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){ + hid_pretty_print_item(parser, item); + int valid_field = 0; + switch (item->item_type){ + case Main: + switch (item->item_tag){ + case Input: + valid_field = parser->report_type == BTSTACK_HID_REPORT_TYPE_INPUT; + break; + case Output: + valid_field = parser->report_type == BTSTACK_HID_REPORT_TYPE_OUTPUT; + break; + case Feature: + valid_field = parser->report_type == BTSTACK_HID_REPORT_TYPE_FEATURE; + break; + default: + break; + } + break; + case Global: + btstack_hid_handle_global_item(parser, item); + break; + case Local: + break; + } + if (!valid_field) return; + + // verify record id + if (parser->global_report_id && !parser->active_record){ + if (parser->report[0] != parser->global_report_id){ + return; + } + parser->report_pos_in_bit += 8; + } + parser->active_record = 1; + // handle constant fields used for padding + if (item->item_value & 1){ + int item_bits = parser->global_report_size * parser->global_report_count; +#ifdef HID_PARSER_PRETTY_PRINT + log_info("- Skip %u constant bits", item_bits); +#endif + parser->report_pos_in_bit += item_bits; + return; + } + // Empty Item + if (parser->global_report_count == 0) return; + // let's start + parser->required_usages = parser->global_report_count; +} + +static void hid_post_process_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){ + if (item->item_type == Main){ + // reset usage + parser->usage_pos = parser->descriptor_pos; + parser->usage_page = parser->global_usage_page; + } + parser->descriptor_pos += item->item_size; +} + +static void btstack_hid_parser_find_next_usage(btstack_hid_parser_t * parser){ + while (parser->state == BTSTACK_HID_PARSER_SCAN_FOR_REPORT_ITEM){ + if (parser->descriptor_pos >= parser->descriptor_len){ + // end of descriptor + parser->state = BTSTACK_HID_PARSER_COMPLETE; + break; + } + btstack_hid_parse_descriptor_item(&parser->descriptor_item, &parser->descriptor[parser->descriptor_pos], parser->descriptor_len - parser->descriptor_pos); + hid_process_item(parser, &parser->descriptor_item); + if (parser->required_usages){ + hid_find_next_usage(parser); + if (parser->available_usages) { + parser->state = BTSTACK_HID_PARSER_USAGES_AVAILABLE; + } else { + log_error("no usages found"); + parser->state = BTSTACK_HID_PARSER_COMPLETE; + } + } else { + hid_post_process_item(parser, &parser->descriptor_item); + } + } +} + +// PUBLIC API + +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){ + + memset(parser, 0, sizeof(btstack_hid_parser_t)); + + parser->descriptor = hid_descriptor; + parser->descriptor_len = hid_descriptor_len; + parser->report_type = hid_report_type; + parser->report = hid_report; + parser->report_len = hid_report_len; + parser->state = BTSTACK_HID_PARSER_SCAN_FOR_REPORT_ITEM; + + btstack_hid_parser_find_next_usage(parser); +} + +int btstack_hid_parser_has_more(btstack_hid_parser_t * parser){ + return parser->state == BTSTACK_HID_PARSER_USAGES_AVAILABLE; +} + +void btstack_hid_parser_get_field(btstack_hid_parser_t * parser, uint16_t * usage_page, uint16_t * usage, int32_t * value){ + + *usage_page = parser->usage_minimum >> 16; + + // read field (up to 32 bit unsigned, up to 31 bit signed - 32 bit signed behaviour is undefined) - check report len + int is_variable = parser->descriptor_item.item_value & 2; + int is_signed = parser->global_logical_minimum < 0; + int pos_start = btstack_min( parser->report_pos_in_bit >> 3, parser->report_len); + int pos_end = btstack_min( (parser->report_pos_in_bit + parser->global_report_size - 1) >> 3, parser->report_len); + int bytes_to_read = pos_end - pos_start + 1; + int i; + uint32_t multi_byte_value = 0; + for (i=0;i < bytes_to_read;i++){ + multi_byte_value |= parser->report[pos_start+i] << (i*8); + } + uint32_t unsigned_value = (multi_byte_value >> (parser->report_pos_in_bit & 0x07)) & ((1<global_report_size)-1); + // 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); + if (is_variable){ + *usage = parser->usage_minimum & 0xffff; + if (is_signed && (unsigned_value & (1<<(parser->global_report_size-1)))){ + *value = unsigned_value - (1<global_report_size); + } else { + *value = unsigned_value; + } + } else { + *usage = unsigned_value; + *value = 1; + } + parser->required_usages--; + parser->report_pos_in_bit += parser->global_report_size; + + // next usage + if (is_variable){ + parser->usage_minimum++; + parser->available_usages--; + } else { + if (parser->required_usages == 0){ + parser->available_usages = 0; + } + } + if (parser->available_usages) { + return; + } + if (parser->required_usages == 0){ + hid_post_process_item(parser, &parser->descriptor_item); + parser->state = BTSTACK_HID_PARSER_SCAN_FOR_REPORT_ITEM; + btstack_hid_parser_find_next_usage(parser); + } else { + hid_find_next_usage(parser); + if (parser->available_usages == 0) { + parser->state = BTSTACK_HID_PARSER_COMPLETE; + } + } +} diff --git a/src/btstack_hid_parser.h b/src/btstack_hid_parser.h new file mode 100644 index 000000000..36e51a0b1 --- /dev/null +++ b/src/btstack_hid_parser.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +/* + * btstack_hid_parser.h + * + * Single-pass HID Report Parser: HID Report is directly parsed without preprocessing HID Descriptor to minimize memory + */ + +#ifndef __BTSTACK_HID_PARSER_H +#define __BTSTACK_HID_PARSER_H + +#include + +#if defined __cplusplus +extern "C" { +#endif + +typedef struct { + int32_t item_value; + uint16_t item_size; + uint8_t item_type; + uint8_t item_tag; + uint8_t data_size; +} hid_descriptor_item_t; + +typedef enum { + BTSTACK_HID_REPORT_TYPE_OTHER = 0x0, + BTSTACK_HID_REPORT_TYPE_INPUT, + BTSTACK_HID_REPORT_TYPE_OUTPUT, + BTSTACK_HID_REPORT_TYPE_FEATURE, +} btstack_hid_report_type_t; + +typedef enum { + BTSTACK_HID_PARSER_SCAN_FOR_REPORT_ITEM, + BTSTACK_HID_PARSER_USAGES_AVAILABLE, + BTSTACK_HID_PARSER_COMPLETE, +} btstack_hid_parser_state_t; + +typedef struct { + + // Descriptor + const uint8_t * descriptor; + uint16_t descriptor_len; + + // Report + btstack_hid_report_type_t report_type; + const uint8_t * report; + uint16_t report_len; + + // State + btstack_hid_parser_state_t state; + + hid_descriptor_item_t descriptor_item; + + uint16_t descriptor_pos; + uint16_t report_pos_in_bit; + + // usage pos and usage_page after last main item, used to find next usage + uint16_t usage_pos; + uint16_t usage_page; + + // usage generator + uint32_t usage_minimum; + uint32_t usage_maximum; + uint16_t available_usages; + uint8_t required_usages; + uint8_t active_record; + uint8_t have_usage_min; + uint8_t have_usage_max; + + // global + int32_t global_logical_minimum; + int32_t global_logical_maximum; + uint16_t global_usage_page; + uint8_t global_report_size; + uint8_t global_report_count; + uint8_t global_report_id; +} btstack_hid_parser_t; + +/* API_START */ + +/** + * @brief Initialize HID Parser. + * @param parser state + * @param hid_descriptor + * @param hid_descriptor_len + * @param hid_report_type + * @param hid_report + * @param hid_report_len + */ +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); + +/** + * @brief Checks if more fields are available + * @param parser + */ +int btstack_hid_parser_has_more(btstack_hid_parser_t * parser); + +/** + * @brief Get next field + * @param parser + * @param usage_page + * @param usage + * @param value provided in HID report + */ +void btstack_hid_parser_get_field(btstack_hid_parser_t * parser, uint16_t * usage_page, uint16_t * usage, int32_t * value); + +/* API_END */ + +#if defined __cplusplus +} +#endif + +#endif // __BTSTACK_HID_PARSER_H diff --git a/test/hid_parser/Makefile b/test/hid_parser/Makefile new file mode 100644 index 000000000..da61ace5c --- /dev/null +++ b/test/hid_parser/Makefile @@ -0,0 +1,30 @@ +CC = g++ + +# Requirements: cpputest.github.io + +BTSTACK_ROOT = ../.. + +CFLAGS = -DUNIT_TEST -g +CPPFLAGS = -x c++ -Wall -Wno-unused +CFLAGS += -I. -I.. -I${BTSTACK_ROOT}/src +LDFLAGS += -lCppUTest -lCppUTestExt +VPATH += ${BTSTACK_ROOT}/src + +COMMON = \ + +COMMON_OBJ = $(COMMON:.c=.o) + + +all: hid_parser_test + +hid_parser_test: btstack_hid_parser.c btstack_util.c hid_parser_test.c hci_dump.c + ${CC} ${CFLAGS} ${CPPFLAGS} ${LDFLAGS} $^ -o $@ + +test: all + ./hid_parser_test + +clean: + rm -f hid_parser_test + rm -f *.o + rm -rf *.dSYM + diff --git a/test/hid_parser/hid_parser_test.c b/test/hid_parser/hid_parser_test.c new file mode 100644 index 000000000..099197994 --- /dev/null +++ b/test/hid_parser/hid_parser_test.c @@ -0,0 +1,332 @@ + +// ***************************************************************************** +// +// HID Parser Test +// +// ***************************************************************************** + + +#include +#include +#include +#include + +#include "CppUTest/TestHarness.h" +#include "CppUTest/CommandLineTestRunner.h" + +#include "btstack_hid_parser.h" + +const uint8_t mouse_descriptor_without_report_id[] = { + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x02, /* Usage (Mouse), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x09, 0x01, /* Usage (Pointer), */ + 0xA0, /* Collection (Physical), */ + 0x05, 0x09, /* Usage Page (Button), */ + 0x19, 0x01, /* Usage Minimum (01h), */ + 0x29, 0x03, /* Usage Maximum (03h), */ + 0x14, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x05, /* Report Size (5), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x01, /* Input (Constant), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x30, /* Usage (X), */ + 0x09, 0x31, /* Usage (Y), */ + 0x15, 0x81, /* Logical Minimum (-127), */ + 0x25, 0x7F, /* Logical Maximum (127), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x06, /* Input (Variable, Relative), */ + 0xC0, /* End Collection, */ + 0xC0 /* End Collection */ +}; + +const uint8_t mouse_descriptor_with_report_id[] = { + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x02, /* Usage (Mouse), */ + 0xA1, 0x01, /* Collection (Application), */ + + 0x85, 0x01, // Report ID 1 + + 0x09, 0x01, /* Usage (Pointer), */ + 0xA0, /* Collection (Physical), */ + 0x05, 0x09, /* Usage Page (Button), */ + 0x19, 0x01, /* Usage Minimum (01h), */ + 0x29, 0x03, /* Usage Maximum (03h), */ + 0x14, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x05, /* Report Size (5), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x01, /* Input (Constant), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x30, /* Usage (X), */ + 0x09, 0x31, /* Usage (Y), */ + 0x15, 0x81, /* Logical Minimum (-127), */ + 0x25, 0x7F, /* Logical Maximum (127), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x06, /* Input (Variable, Relative), */ + 0xC0, /* End Collection, */ + 0xC0 /* End Collection */ +}; + +// from USB HID Specification 1.1, Appendix B.1 +const uint8_t hid_descriptor_keyboard_boot_mode[] = { + + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x06, // Usage (Keyboard) + 0xa1, 0x01, // Collection (Application) + + // Modifier byte + + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x05, 0x07, // Usage Page (Key codes) + 0x19, 0xe0, // Usage Minimum (Keyboard LeftControl) + 0x29, 0xe7, // Usage Maxium (Keyboard Right GUI) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x81, 0x02, // Input (Data, Variable, Absolute) + + // Reserved byte + + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x81, 0x03, // Input (Constant, Variable, Absolute) + + // LED report + padding + + 0x95, 0x05, // Report Count (5) + 0x75, 0x01, // Report Size (1) + 0x05, 0x08, // Usage Page (LEDs) + 0x19, 0x01, // Usage Minimum (Num Lock) + 0x29, 0x05, // Usage Maxium (Kana) + 0x91, 0x02, // Output (Data, Variable, Absolute) + + 0x95, 0x01, // Report Count (1) + 0x75, 0x03, // Report Size (3) + 0x91, 0x03, // Output (Constant, Variable, Absolute) + + // Keycodes + + 0x95, 0x06, // Report Count (6) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0xff, // Logical Maximum (1) + 0x05, 0x07, // Usage Page (Key codes) + 0x19, 0x00, // Usage Minimum (Reserved (no event indicated)) + 0x29, 0xff, // Usage Maxium (Reserved) + 0x81, 0x00, // Input (Data, Array) + + 0xc0, // End collection +}; + +const uint8_t combo_descriptor_with_report_ids[] = { + + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x02, /* Usage (Mouse), */ + 0xA1, 0x01, /* Collection (Application), */ + + 0x85, 0x01, // Report ID 1 + + 0x09, 0x01, /* Usage (Pointer), */ + 0xA0, /* Collection (Physical), */ + 0x05, 0x09, /* Usage Page (Button), */ + 0x19, 0x01, /* Usage Minimum (01h), */ + 0x29, 0x03, /* Usage Maximum (03h), */ + 0x14, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x05, /* Report Size (5), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x01, /* Input (Constant), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x30, /* Usage (X), */ + 0x09, 0x31, /* Usage (Y), */ + 0x15, 0x81, /* Logical Minimum (-127), */ + 0x25, 0x7F, /* Logical Maximum (127), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x06, /* Input (Variable, Relative), */ + 0xC0, /* End Collection, */ + 0xC0, /* End Collection */ + + 0xa1, 0x01, // Collection (Application) + + 0x85, 0x02, // Report ID 2 + + // Modifier byte + + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x05, 0x07, // Usage Page (Key codes) + 0x19, 0xe0, // Usage Minimum (Keyboard LeftControl) + 0x29, 0xe7, // Usage Maxium (Keyboard Right GUI) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x81, 0x02, // Input (Data, Variable, Absolute) + + // Reserved byte + + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x81, 0x03, // Input (Constant, Variable, Absolute) + + // LED report + padding + + 0x95, 0x05, // Report Count (5) + 0x75, 0x01, // Report Size (1) + 0x05, 0x08, // Usage Page (LEDs) + 0x19, 0x01, // Usage Minimum (Num Lock) + 0x29, 0x05, // Usage Maxium (Kana) + 0x91, 0x02, // Output (Data, Variable, Absolute) + + 0x95, 0x01, // Report Count (1) + 0x75, 0x03, // Report Size (3) + 0x91, 0x03, // Output (Constant, Variable, Absolute) + + // Keycodes + + 0x95, 0x06, // Report Count (6) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0xff, // Logical Maximum (1) + 0x05, 0x07, // Usage Page (Key codes) + 0x19, 0x00, // Usage Minimum (Reserved (no event indicated)) + 0x29, 0xff, // Usage Maxium (Reserved) + 0x81, 0x00, // Input (Data, Array) + + 0xc0, // En + +}; + +const uint8_t mouse_report_without_id_positive_xy[] = { 0x03, 0x02, 0x03 }; +const uint8_t mouse_report_without_id_negative_xy[] = { 0x03, 0xFE, 0xFD }; +const uint8_t mouse_report_with_id_1[] = { 0x01, 0x03, 0x02, 0x03 }; + +const uint8_t keyboard_report1[] = { 0x01, 0x00, 0x04, 0x05, 0x06, 0x00, 0x00, 0x00 }; + +const uint8_t combo_report1[] = { 0x01, 0x03, 0x02, 0x03 }; +const uint8_t combo_report2[] = { 0x02, 0x01, 0x00, 0x04, 0x05, 0x06, 0x00, 0x00, 0x00 }; + +static void expect_field(btstack_hid_parser_t * parser, uint16_t expected_usage_page, uint16_t expected_usage, int32_t expected_value){ + // printf("expected - usage page %02x, usage %04x, value %02x (bit pos %u)\n", expected_usage_page, expected_usage, expected_value, parser->report_pos_in_bit); + CHECK_EQUAL(1, btstack_hid_parser_has_more(parser)); + uint16_t usage_page; + uint16_t usage; + int32_t value; + btstack_hid_parser_get_field(parser, &usage_page, &usage, &value); + CHECK_EQUAL(expected_usage_page, usage_page); + CHECK_EQUAL(expected_usage, usage); + CHECK_EQUAL(expected_value, value); +} + +// test +TEST_GROUP(HID){ + void setup(void){ + } +}; + +TEST(HID, MouseWithoutReportID){ + static btstack_hid_parser_t hid_parser; + btstack_hid_parser_init(&hid_parser, mouse_descriptor_without_report_id, sizeof(mouse_descriptor_without_report_id), BTSTACK_HID_REPORT_TYPE_INPUT, mouse_report_without_id_positive_xy, sizeof(mouse_report_without_id_positive_xy)); + expect_field(&hid_parser, 9, 1, 1); + expect_field(&hid_parser, 9, 2, 1); + expect_field(&hid_parser, 9, 3, 0); + expect_field(&hid_parser, 1, 0x30, 2); + expect_field(&hid_parser, 1, 0x31, 3); + CHECK_EQUAL(24, hid_parser.report_pos_in_bit); + CHECK_EQUAL(0, btstack_hid_parser_has_more(&hid_parser)); +} + +TEST(HID, MouseWithoutReportIDSigned){ + static btstack_hid_parser_t hid_parser; + btstack_hid_parser_init(&hid_parser, mouse_descriptor_without_report_id, sizeof(mouse_descriptor_without_report_id), BTSTACK_HID_REPORT_TYPE_INPUT, mouse_report_without_id_negative_xy, sizeof(mouse_report_without_id_negative_xy)); + expect_field(&hid_parser, 9, 1, 1); + expect_field(&hid_parser, 9, 2, 1); + expect_field(&hid_parser, 9, 3, 0); + expect_field(&hid_parser, 1, 0x30, -2); + expect_field(&hid_parser, 1, 0x31, -3); + CHECK_EQUAL(24, hid_parser.report_pos_in_bit); + CHECK_EQUAL(0, btstack_hid_parser_has_more(&hid_parser)); +} + +TEST(HID, MouseWithReportID){ + static btstack_hid_parser_t hid_parser; + btstack_hid_parser_init(&hid_parser, mouse_descriptor_with_report_id, sizeof(mouse_descriptor_with_report_id), BTSTACK_HID_REPORT_TYPE_INPUT, mouse_report_with_id_1, sizeof(mouse_report_with_id_1)); + expect_field(&hid_parser, 9, 1, 1); + expect_field(&hid_parser, 9, 2, 1); + expect_field(&hid_parser, 9, 3, 0); + expect_field(&hid_parser, 1, 0x30, 2); + expect_field(&hid_parser, 1, 0x31, 3); + CHECK_EQUAL(32, hid_parser.report_pos_in_bit); + CHECK_EQUAL(0, btstack_hid_parser_has_more(&hid_parser)); +} + +TEST(HID, BootKeyboard){ + static btstack_hid_parser_t hid_parser; + btstack_hid_parser_init(&hid_parser, hid_descriptor_keyboard_boot_mode, sizeof(hid_descriptor_keyboard_boot_mode), BTSTACK_HID_REPORT_TYPE_INPUT, keyboard_report1, sizeof(keyboard_report1)); + expect_field(&hid_parser, 7, 0xe0, 1); + expect_field(&hid_parser, 7, 0xe1, 0); + expect_field(&hid_parser, 7, 0xe2, 0); + expect_field(&hid_parser, 7, 0xe3, 0); + expect_field(&hid_parser, 7, 0xe4, 0); + expect_field(&hid_parser, 7, 0xe5, 0); + expect_field(&hid_parser, 7, 0xe6, 0); + expect_field(&hid_parser, 7, 0xe7, 0); + expect_field(&hid_parser, 7, 0x04, 1); + expect_field(&hid_parser, 7, 0x05, 1); + expect_field(&hid_parser, 7, 0x06, 1); + expect_field(&hid_parser, 7, 0x00, 1); + expect_field(&hid_parser, 7, 0x00, 1); + expect_field(&hid_parser, 7, 0x00, 1); + CHECK_EQUAL(64, hid_parser.report_pos_in_bit); + CHECK_EQUAL(0, btstack_hid_parser_has_more(&hid_parser)); +} + +TEST(HID, Combo1){ + static btstack_hid_parser_t hid_parser; + btstack_hid_parser_init(&hid_parser, combo_descriptor_with_report_ids, sizeof(combo_descriptor_with_report_ids), BTSTACK_HID_REPORT_TYPE_INPUT, combo_report1, sizeof(combo_report1)); + expect_field(&hid_parser, 9, 1, 1); + expect_field(&hid_parser, 9, 2, 1); + expect_field(&hid_parser, 9, 3, 0); + expect_field(&hid_parser, 1, 0x30, 2); + expect_field(&hid_parser, 1, 0x31, 3); + CHECK_EQUAL(32, hid_parser.report_pos_in_bit); + CHECK_EQUAL(0, btstack_hid_parser_has_more(&hid_parser)); +} + +TEST(HID, Combo2){ + static btstack_hid_parser_t hid_parser; + btstack_hid_parser_init(&hid_parser, combo_descriptor_with_report_ids, sizeof(combo_descriptor_with_report_ids), BTSTACK_HID_REPORT_TYPE_INPUT, combo_report2, sizeof(combo_report2)); + expect_field(&hid_parser, 7, 0xe0, 1); + expect_field(&hid_parser, 7, 0xe1, 0); + expect_field(&hid_parser, 7, 0xe2, 0); + expect_field(&hid_parser, 7, 0xe3, 0); + expect_field(&hid_parser, 7, 0xe4, 0); + expect_field(&hid_parser, 7, 0xe5, 0); + expect_field(&hid_parser, 7, 0xe6, 0); + expect_field(&hid_parser, 7, 0xe7, 0); + expect_field(&hid_parser, 7, 0x04, 1); + expect_field(&hid_parser, 7, 0x05, 1); + expect_field(&hid_parser, 7, 0x06, 1); + expect_field(&hid_parser, 7, 0x00, 1); + expect_field(&hid_parser, 7, 0x00, 1); + expect_field(&hid_parser, 7, 0x00, 1); + CHECK_EQUAL(72, hid_parser.report_pos_in_bit); + CHECK_EQUAL(0, btstack_hid_parser_has_more(&hid_parser)); +} + +int main (int argc, const char * argv[]){ + // hci_dump_open("hci_dump.pklg", HCI_DUMP_PACKETLOGGER); + return CommandLineTestRunner::RunAllTests(argc, argv); +}