mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-03-17 10:20:50 +00:00
531 lines
18 KiB
C
531 lines
18 KiB
C
/*
|
|
* 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 BLUEKITCHEN
|
|
* GMBH 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
|
|
*/
|
|
|
|
#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 ((TagType)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_name, 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
|
|
void btstack_hid_parse_descriptor_item(hid_descriptor_item_t * item, const uint8_t * hid_descriptor, uint16_t hid_descriptor_len){
|
|
|
|
const int hid_item_sizes[] = { 0, 1, 2, 4 };
|
|
|
|
// parse item header
|
|
if (hid_descriptor_len < 1u) return;
|
|
uint16_t pos = 0;
|
|
uint8_t item_header = hid_descriptor[pos++];
|
|
item->data_size = hid_item_sizes[item_header & 0x03u];
|
|
item->item_type = (item_header & 0x0cu) >> 2u;
|
|
item->item_tag = (item_header & 0xf0u) >> 4u;
|
|
// long item
|
|
if ((item->data_size == 2u) && (item->item_tag == 0x0fu) && (item->item_type == 3u)){
|
|
if (hid_descriptor_len < 3u) 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 > 4u) return;
|
|
int i;
|
|
int sgnd = (item->item_type == Global) && (item->item_tag > 0u) && (item->item_tag < 5u);
|
|
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 > 0u)){
|
|
if (latest_byte & 0x80u) {
|
|
value -= 1u << (item->data_size*8u);
|
|
}
|
|
}
|
|
item->item_value = value;
|
|
}
|
|
|
|
static void btstack_hid_handle_global_item(btstack_hid_parser_t * parser, hid_descriptor_item_t * item){
|
|
switch((GlobalItemTag)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;
|
|
|
|
// TODO handle tags
|
|
case PhysicalMinimum:
|
|
case PhysicalMaximum:
|
|
case UnitExponent:
|
|
case Unit:
|
|
case Push:
|
|
case Pop:
|
|
break;
|
|
|
|
default:
|
|
btstack_assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void hid_find_next_usage(btstack_hid_parser_t * parser){
|
|
while ((parser->available_usages == 0u) && (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 > 2u) ? usage_item.item_value : ((parser->usage_page << 16u) | 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 + 1u;
|
|
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 ((TagType)item->item_type){
|
|
case Main:
|
|
switch ((MainItemTag)item->item_tag){
|
|
case Input:
|
|
valid_field = parser->report_type == HID_REPORT_TYPE_INPUT;
|
|
break;
|
|
case Output:
|
|
valid_field = parser->report_type == HID_REPORT_TYPE_OUTPUT;
|
|
break;
|
|
case Feature:
|
|
valid_field = parser->report_type == HID_REPORT_TYPE_FEATURE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case Global:
|
|
btstack_hid_handle_global_item(parser, item);
|
|
break;
|
|
case Local:
|
|
case Reserved:
|
|
break;
|
|
default:
|
|
btstack_assert(false);
|
|
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 += 8u;
|
|
}
|
|
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 == 0u) 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 ((TagType)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, 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
|
|
bool is_variable = (parser->descriptor_item.item_value & 2) != 0;
|
|
bool 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 - 1u) >> 3u, 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 & 0x07u)) & ((1u<<parser->global_report_size)-1u);
|
|
// 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 & 0xffffu;
|
|
if (is_signed && (unsigned_value & (1u<<(parser->global_report_size-1u)))){
|
|
*value = unsigned_value - (1u<<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 == 0u){
|
|
parser->available_usages = 0;
|
|
}
|
|
}
|
|
if (parser->available_usages) {
|
|
return;
|
|
}
|
|
if (parser->required_usages == 0u){
|
|
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 == 0u) {
|
|
parser->state = BTSTACK_HID_PARSER_COMPLETE;
|
|
}
|
|
}
|
|
}
|
|
|
|
int btstack_hid_get_report_size_for_id(int report_id, hid_report_type_t report_type, uint16_t hid_descriptor_len, const uint8_t * hid_descriptor){
|
|
int total_report_size = 0;
|
|
int report_size = 0;
|
|
int report_count = 0;
|
|
int current_report_id = 0;
|
|
|
|
while (hid_descriptor_len){
|
|
int valid_report_type = 0;
|
|
hid_descriptor_item_t item;
|
|
btstack_hid_parse_descriptor_item(&item, hid_descriptor, hid_descriptor_len);
|
|
switch (item.item_type){
|
|
case Global:
|
|
switch ((GlobalItemTag)item.item_tag){
|
|
case ReportID:
|
|
current_report_id = item.item_value;
|
|
break;
|
|
case ReportCount:
|
|
report_count = item.item_value;
|
|
break;
|
|
case ReportSize:
|
|
report_size = item.item_value;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case Main:
|
|
if (current_report_id != report_id) break;
|
|
switch ((MainItemTag)item.item_tag){
|
|
case Input:
|
|
if (report_type != HID_REPORT_TYPE_INPUT) break;
|
|
valid_report_type = 1;
|
|
break;
|
|
case Output:
|
|
if (report_type != HID_REPORT_TYPE_OUTPUT) break;
|
|
valid_report_type = 1;
|
|
break;
|
|
case Feature:
|
|
if (report_type != HID_REPORT_TYPE_FEATURE) break;
|
|
valid_report_type = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!valid_report_type) break;
|
|
total_report_size += report_count * report_size;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (total_report_size > 0 && current_report_id != report_id) break;
|
|
hid_descriptor_len -= item.item_size;
|
|
hid_descriptor += item.item_size;
|
|
}
|
|
return (total_report_size + 7)/8;
|
|
}
|
|
|
|
hid_report_id_status_t btstack_hid_id_valid(int report_id, uint16_t hid_descriptor_len, const uint8_t * hid_descriptor){
|
|
int current_report_id = 0;
|
|
while (hid_descriptor_len){
|
|
hid_descriptor_item_t item;
|
|
btstack_hid_parse_descriptor_item(&item, hid_descriptor, hid_descriptor_len);
|
|
switch (item.item_type){
|
|
case Global:
|
|
switch ((GlobalItemTag)item.item_tag){
|
|
case ReportID:
|
|
current_report_id = item.item_value;
|
|
if (current_report_id != report_id) break;
|
|
return HID_REPORT_ID_VALID;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
hid_descriptor_len -= item.item_size;
|
|
hid_descriptor += item.item_size;
|
|
}
|
|
if (current_report_id != 0) return HID_REPORT_ID_INVALID;
|
|
return HID_REPORT_ID_UNDECLARED;
|
|
}
|
|
|
|
int btstack_hid_report_id_declared(uint16_t hid_descriptor_len, const uint8_t * hid_descriptor){
|
|
while (hid_descriptor_len){
|
|
hid_descriptor_item_t item;
|
|
btstack_hid_parse_descriptor_item(&item, hid_descriptor, hid_descriptor_len);
|
|
switch (item.item_type){
|
|
case Global:
|
|
switch ((GlobalItemTag)item.item_tag){
|
|
case ReportID:
|
|
return 1;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
hid_descriptor_len -= item.item_size;
|
|
hid_descriptor += item.item_size;
|
|
}
|
|
return 0;
|
|
}
|