#include "hidparse.h" #include #include #include #include static uint8_t size_to_bytes(uint8_t sz) { switch (sz) { case 0: return 0; case 1: return 1; case 2: return 2; case 3: return 4; default: return 0; } } static int32_t read_int(uint8_t sz, uint8_t* raw) { switch (sz) { case 1: return *((int8_t*) raw); // ensure we cast to sign-extend case 2: return *((int16_t*) raw); case 3: return *((int32_t*) raw); } assert(0); return 0; } static uint32_t read_uint(uint8_t sz, uint8_t* raw) { switch (sz) { case 1: return *raw; case 2: return *((uint16_t*) raw); case 3: return *((uint32_t*) raw); } assert(0); return 0; } const uint8_t HID_LONG_ITEM = 0xFE; // see 6.2.2.3 // following definitions come from 6.2.2.4 #define HID_COLLECTION_ITEM 0xA #define HID_END_COLLECTION_ITEM 0xC #define HID_INPUT_ITEM 0x8 #define HID_OUTPUT_ITEM 0x9 #define HID_FEATURE_ITEM 0xB #define HID_USAGE_PAGE_ITEM 0x0 #define HID_LOGICAL_MINIMUM_ITEM 0x1 #define HID_LOGICAL_MAXIMUM_ITEM 0x2 #define HID_PHYSICAL_MINIMUM_ITEM 0x3 #define HID_PHYSICAL_MAXIMUM_ITEM 0x4 #define HID_EXPONENT_ITEM 0x5 #define HID_UNIT_ITEM 0x6 #define HID_REPORT_SIZE_ITEM 0x7 #define HID_REPORT_ID_ITEM 0x8 #define HID_REPORT_COUNT_ITEM 0x9 #define HID_PUSH_ITEM 0xA #define HID_POP_ITEM 0xB // following definitions come from 6.2.2.8 #define HID_USAGE_ITEM 0x0 #define HID_USAGE_MINIMUM_ITEM 0x1 #define HID_USAGE_MAXIMUM_ITEM 0x2 #define HID_DESIGNATOR_INDEX_ITEM 0x3 #define HID_DESIGNATOR_MINIMUM_ITEM 0x4 #define HID_DESIGNATOR_MAXIMUM_ITEM 0x5 #define HID_STRING_INDEX_ITEM 0x7 #define HID_STRING_MINIMUM_ITEM 0x8 #define HID_STRING_MAXIMUM_ITEM 0x9 #define HID_DELIMITER_ITEM 0xA #define MAX_ITEM_STACK 4 hid_item item_stack[MAX_ITEM_STACK]; uint8_t item_stack_size = 0; uint32_t usage_array[16]; uint8_t usage_count; void init_local_data(); void clear_local_data(); void set_local_minimum(uint8_t tag, uint32_t data); void set_local_maximum(uint8_t tag, uint32_t data); void push_local_item(uint8_t tag, uint32_t data); uint32_t pop_local_item(uint8_t tag); hid_item* build_item(uint8_t tag, uint8_t flags, hid_item* global_state) { hid_item* item = (hid_item*) calloc(sizeof(hid_item), 1); if (item == NULL) return NULL; item->flags = flags; item->usage = pop_local_item(HID_USAGE_ITEM); item->type = tag; // copy from global state item->report_size = global_state->report_size; item->logical_min = global_state->logical_min; item->logical_max = global_state->logical_max; item->physical_min = global_state->physical_min; item->physical_max = global_state->physical_max; item->unit = global_state->unit; item->unit_exponent = global_state->unit_exponent; item->report_id = global_state->report_id; return item; } hid_item* build_collection(uint8_t collectionType) { hid_item* col = (hid_item*) calloc(sizeof(hid_item), 1); assert(col); col->type = collectionType; col->usage = pop_local_item(HID_USAGE_ITEM); return col; } void append_item_with_head(hid_item* head, hid_item* i) { assert(head); for (; head->next; head = head->next) {} head->next = i; } void append_item(hid_item* col, hid_item* i) { assert(col); assert(i); assert(i->next == NULL); assert(i->parent == NULL); i->parent = col; hid_item* last = col->collection; if (!last) { col->collection = i; return; } append_item_with_head(last, i); } int hid_parse_reportdesc(uint8_t* rdesc_buf, uint32_t rdesc_size, hid_item** item) { hid_item current_state; uint8_t report_id = 0; uint8_t report_count = 1; uint8_t *p = rdesc_buf; uint8_t* pEnd = p + rdesc_size; hid_item* root_collection = NULL; hid_item* current_collection = NULL; // zero the entire array memset(item_stack, 0, sizeof(hid_item) * MAX_ITEM_STACK); item_stack_size = 0; memset(¤t_state, 0, sizeof(hid_item)); usage_count = 0; init_local_data(); while (p < pEnd) { /* See 6.2.2.2 Short Items */ uint8_t pfx = *p++; if (pfx == HID_LONG_ITEM) { printf("encountered long item\n"); } uint8_t size = pfx & 0x3; /* bits 0-1 */ uint8_t bytes = size_to_bytes(size); uint8_t type = (pfx >> 2) & 0x3; /* bits 3-2 */ uint8_t tag = pfx >> 4; // fprintf(stderr, "short item: size=%d, bytes=%d, type = %d, tag=%d\n", // size, bytes, type, tag); /* If it's a main item */ if (type == 0) { switch (tag) { case HID_COLLECTION_ITEM: { // fprintf(stderr, "opening collection\n"); uint8_t flags = read_uint(size, p); hid_item* col = build_collection(flags); if (!current_collection) { if (root_collection) { append_item_with_head(root_collection, col); } else { root_collection = col; } current_collection = col; } else { append_item(current_collection, col); current_collection = col; } break; } case HID_END_COLLECTION_ITEM: // fprintf(stderr, "closing collection\n"); assert(current_collection); current_collection = current_collection->parent; break; case HID_INPUT_ITEM: case HID_OUTPUT_ITEM: case HID_FEATURE_ITEM: { int i; uint8_t flags = read_uint(size, p); for (i=0; itype, // item->report_id, item->report_size); append_item(current_collection, item); } break; } default: fprintf(stderr, "bad item %d\n", tag); } /* reset all local data */ clear_local_data(); } /* Else, if it's a global item */ else if (type == 1) { switch (tag) { case HID_USAGE_PAGE_ITEM: current_state.usage = read_uint(size, p) << 16; break; case HID_LOGICAL_MINIMUM_ITEM: current_state.logical_min = read_int(size, p); break; case HID_LOGICAL_MAXIMUM_ITEM: current_state.logical_max = read_int(size, p); break; case HID_UNIT_ITEM: current_state.unit = read_uint(size, p); break; case HID_EXPONENT_ITEM: current_state.unit_exponent = read_uint(size, p); break; case HID_REPORT_ID_ITEM: report_id = read_uint(size, p); //fprintf(stderr, "report id is:%d\n", report_id); current_state.report_id = report_id; break; case HID_REPORT_SIZE_ITEM: current_state.report_size = read_uint(size, p); //fprintf(stderr, "item size is:%d\n", current_state.report_size); break; case HID_REPORT_COUNT_ITEM: report_count = read_uint(size, p); //fprintf(stderr, "item count is:%d\n", report_count); break; case HID_POP_ITEM: assert(item_stack_size > 0); current_state = item_stack[--item_stack_size]; break; case HID_PUSH_ITEM: item_stack[item_stack_size++] = current_state; break; } } else if (type == 2) { /* it's a local item */ uint32_t value = read_uint(size, p); switch (tag) { /* section 6.2.2.8, remark about Usage size; current_state only ever holds a usage page */ case HID_USAGE_ITEM: if (size < sizeof(uint32_t)) value |= current_state.usage; push_local_item(tag, value); break; case HID_USAGE_MINIMUM_ITEM: set_local_minimum(HID_USAGE_ITEM, value | current_state.usage); break; case HID_USAGE_MAXIMUM_ITEM: set_local_maximum(HID_USAGE_ITEM, value | current_state.usage); break; case HID_DESIGNATOR_MINIMUM_ITEM: set_local_minimum(HID_DESIGNATOR_INDEX_ITEM, value); break; case HID_DESIGNATOR_MAXIMUM_ITEM: set_local_maximum(HID_DESIGNATOR_INDEX_ITEM, value); break; case HID_STRING_MINIMUM_ITEM: set_local_minimum(HID_STRING_INDEX_ITEM, value); break; case HID_STRING_MAXIMUM_ITEM: set_local_maximum(HID_STRING_INDEX_ITEM, value); break; default: push_local_item(tag, value); } } else { fprintf(stderr, "bad type value: %d\n", type); } p += bytes; } clear_local_data(); // free transient parsing data *item = root_collection; return 0; } void hid_free_reportdesc(hid_item* root) { free(root); } int hid_parse_is_relative(hid_item* item) { return item->flags & 0x04; // bit 2 according to HID 6.2.2.5 } typedef struct { uint8_t allocated, count; uint32_t* d; uint32_t minimum, maximum, step; } local_data_array; local_data_array local_data_store[0xf]; void init_local_data() { int i; for (i=0; i<0xf; ++i) { memset(&local_data_store[i], 0, sizeof(local_data_array)); } } void clear_local_data() { int i; for (i=0; i<0xf; ++i) { if (local_data_store[i].d != NULL) { free(local_data_store[i].d); } memset(&local_data_store[i], 0, sizeof(local_data_array)); } } void reallocate_local_item(uint8_t tag) { local_data_array* arr = &local_data_store[tag >> 4]; arr->allocated = (arr->allocated == 0) ? 4 : arr->allocated << 1; arr->d = realloc(arr->d, sizeof(uint32_t) * arr->allocated); } void push_local_item(uint8_t tag, uint32_t data) { local_data_array* arr = &local_data_store[tag >> 4]; if (arr->allocated == arr->count) reallocate_local_item(tag); arr->d[arr->count++] = data; } uint32_t pop_local_item(uint8_t tag) { local_data_array* arr = &local_data_store[tag >> 4]; if (arr->minimum) { uint32_t result = arr->minimum + arr->step++; if (arr->maximum && (result > arr->maximum)) { fprintf(stderr, "hidparse.c:pop_local_item: range exceeded for tag %d", tag); } return result; } if (arr->count == 0) return 0; uint32_t result = arr->d[arr->count - 1]; if (arr->count > 1) --arr->count; /* pop off the back */ return result; } void set_local_minimum(uint8_t tag, uint32_t data) { local_data_array* arr = &local_data_store[tag >> 4]; arr->minimum = data; } void set_local_maximum(uint8_t tag, uint32_t data) { local_data_array* arr = &local_data_store[tag >> 4]; arr->maximum = data; }