mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-03-15 22:20:59 +00:00
hid_parser: compact single-pass HID Parser to process HID Reports based on HID Descriptor
This commit is contained in:
parent
ec3d71e355
commit
12ccb71b49
@ -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
453
src/btstack_hid_parser.c
Normal 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
149
src/btstack_hid_parser.h
Normal 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
30
test/hid_parser/Makefile
Normal 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
|
||||
|
332
test/hid_parser/hid_parser_test.c
Normal file
332
test/hid_parser/hid_parser_test.c
Normal 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);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user