hid_parser: compact single-pass HID Parser to process HID Reports based on HID Descriptor

This commit is contained in:
Matthias Ringwald 2017-12-04 14:55:17 +01:00
parent ec3d71e355
commit 12ccb71b49
5 changed files with 965 additions and 0 deletions

View File

@ -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"

453
src/btstack_hid_parser.c Normal file
View File

@ -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 <string.h>
#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;i<item->data_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<<parser->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<<parser->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;
}
}
}

149
src/btstack_hid_parser.h Normal file
View File

@ -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 <stdint.h>
#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

30
test/hid_parser/Makefile Normal file
View File

@ -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

View File

@ -0,0 +1,332 @@
// *****************************************************************************
//
// HID Parser Test
//
// *****************************************************************************
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}