| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/device/hid/hid_preparsed_data.h" |
| |
| #include <cstddef> |
| #include <cstdint> |
| |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/notreached.h" |
| #include "components/device_event_log/device_event_log.h" |
| |
| namespace device { |
| |
| namespace { |
| |
| // Windows parses HID report descriptors into opaque _HIDP_PREPARSED_DATA |
| // objects. The internal structure of _HIDP_PREPARSED_DATA is reserved for |
| // internal system use. The structs below are inferred and may be wrong or |
| // incomplete. |
| // https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/preparsed-data |
| // |
| // _HIDP_PREPARSED_DATA begins with a fixed-sized header containing information |
| // about a single top-level HID collection. The header is followed by a |
| // variable-sized array describing the fields that make up each report. |
| // |
| // Input report items appear first in the array, followed by output report items |
| // and feature report items. The number of items of each type is given by |
| // |input_item_count|, |output_item_count| and |feature_item_count|. The sum of |
| // these counts should equal |item_count|. The total size in bytes of all report |
| // items is |size_bytes|. |
| #pragma pack(push, 1) |
| struct PreparsedDataHeader { |
| // Unknown constant value. _HIDP_PREPARSED_DATA identifier? |
| uint64_t magic; |
| |
| // Top-level collection usage information. |
| uint16_t usage; |
| uint16_t usage_page; |
| |
| uint16_t unknown[3]; |
| |
| // Number of report items for input reports. Includes unused items. |
| uint16_t input_item_count; |
| |
| uint16_t unknown2; |
| |
| // Maximum input report size, in bytes. Includes the report ID byte. Zero if |
| // there are no input reports. |
| uint16_t input_report_byte_length; |
| |
| uint16_t unknown3; |
| |
| // Number of report items for output reports. Includes unused items. |
| uint16_t output_item_count; |
| |
| uint16_t unknown4; |
| |
| // Maximum output report size, in bytes. Includes the report ID byte. Zero if |
| // there are no output reports. |
| uint16_t output_report_byte_length; |
| |
| uint16_t unknown5; |
| |
| // Number of report items for feature reports. Includes unused items. |
| uint16_t feature_item_count; |
| |
| // Total number of report items (input, output, and feature). Unused items are |
| // excluded. |
| uint16_t item_count; |
| |
| // Maximum feature report size, in bytes. Includes the report ID byte. Zero if |
| // there are no feature reports. |
| uint16_t feature_report_byte_length; |
| |
| // Total size of all report items, in bytes. |
| uint16_t size_bytes; |
| |
| uint16_t unknown6; |
| }; |
| #pragma pack(pop) |
| static_assert(sizeof(PreparsedDataHeader) == 44, |
| "PreparsedDataHeader has incorrect size"); |
| |
| #pragma pack(push, 1) |
| struct PreparsedDataItem { |
| // Usage page for |usage_minimum| and |usage_maximum|. |
| uint16_t usage_page; |
| |
| // Report ID for the report containing this item. |
| uint8_t report_id; |
| |
| // Bit offset from |byte_index|. |
| uint8_t bit_index; |
| |
| // Bit width of a single field defined by this item. |
| uint16_t bit_size; |
| |
| // The number of fields defined by this item. |
| uint16_t report_count; |
| |
| // Byte offset from the start of the report containing this item, including |
| // the report ID byte. |
| uint16_t byte_index; |
| |
| // The total number of bits for all fields defined by this item. |
| uint16_t bit_count; |
| |
| // The bit field for the corresponding main item in the HID report. This bit |
| // field is defined in the Device Class Definition for HID v1.11 section |
| // 6.2.2.5. |
| // https://www.usb.org/document-library/device-class-definition-hid-111 |
| uint32_t bit_field; |
| |
| uint32_t unknown; |
| |
| // Usage information for the collection containing this item. |
| uint16_t link_usage_page; |
| uint16_t link_usage; |
| |
| uint32_t unknown2[9]; |
| |
| // The usage range for this item. |
| uint16_t usage_minimum; |
| uint16_t usage_maximum; |
| |
| // The string descriptor index range associated with this item. If the item |
| // has no string descriptors, |string_minimum| and |string_maximum| are set to |
| // zero. |
| uint16_t string_minimum; |
| uint16_t string_maximum; |
| |
| // The designator index range associated with this item. If the item has no |
| // designators, |designator_minimum| and |designator_maximum| are set to zero. |
| uint16_t designator_minimum; |
| uint16_t designator_maximum; |
| |
| // The data index range associated with this item. |
| uint16_t data_index_minimum; |
| uint16_t data_index_maximum; |
| |
| uint32_t unknown3; |
| |
| // The range of fields defined by this item in logical units. |
| int32_t logical_minimum; |
| int32_t logical_maximum; |
| |
| // The range of fields defined by this item in units defined by |unit| and |
| // |unit_exponent|. If this item does not use physical units, |
| // |physical_minimum| and |physical_maximum| are set to zero. |
| int32_t physical_minimum; |
| int32_t physical_maximum; |
| |
| // The unit definition for this item. The format for this definition is |
| // described in the Device Class Definition for HID v1.11 section 6.2.2.7. |
| // https://www.usb.org/document-library/device-class-definition-hid-111 |
| uint32_t unit; |
| uint32_t unit_exponent; |
| }; |
| #pragma pack(pop) |
| static_assert(sizeof(PreparsedDataItem) == 104, |
| "PreparsedDataItem has incorrect size"); |
| |
| bool ValidatePreparsedDataHeader(const PreparsedDataHeader& header) { |
| static bool has_dumped_without_crashing = false; |
| |
| // _HIDP_PREPARSED_DATA objects are expected to start with a known constant |
| // value. |
| constexpr uint64_t kHidPreparsedDataMagic = 0x52444B2050646948; |
| |
| // Require a matching magic value. The details of _HIDP_PREPARSED_DATA are |
| // proprietary and the magic constant may change. If DCHECKS are on, trigger |
| // a CHECK failure and crash. Otherwise, generate a non-crash dump. |
| DCHECK_EQ(header.magic, kHidPreparsedDataMagic); |
| if (header.magic != kHidPreparsedDataMagic) { |
| HID_LOG(ERROR) << "Unexpected magic value."; |
| if (has_dumped_without_crashing) { |
| base::debug::DumpWithoutCrashing(); |
| has_dumped_without_crashing = true; |
| } |
| return false; |
| } |
| |
| if (header.input_report_byte_length == 0 && header.input_item_count > 0) |
| return false; |
| if (header.output_report_byte_length == 0 && header.output_item_count > 0) |
| return false; |
| if (header.feature_report_byte_length == 0 && header.feature_item_count > 0) |
| return false; |
| |
| // Calculate the expected total size of report items in the |
| // _HIDP_PREPARSED_DATA object. Use the individual item counts for each report |
| // type instead of the total |item_count|. In some cases additional items are |
| // allocated but are not used for any reports. Unused items are excluded from |
| // |item_count| but are included in the item counts for each report type and |
| // contribute to the total size of the object. See crbug.com/1199890 for more |
| // information. |
| uint16_t total_item_size = |
| (header.input_item_count + header.output_item_count + |
| header.feature_item_count) * |
| sizeof(PreparsedDataItem); |
| if (total_item_size != header.size_bytes) |
| return false; |
| return true; |
| } |
| |
| bool ValidatePreparsedDataItem(const PreparsedDataItem& item) { |
| // Check that the item does not overlap with the report ID byte. |
| if (item.byte_index == 0) |
| return false; |
| |
| // Check that the bit index does not exceed the maximum bit index in one byte. |
| if (item.bit_index >= CHAR_BIT) |
| return false; |
| |
| // Check that the item occupies at least one bit in the report. |
| if (item.report_count == 0 || item.bit_size == 0 || item.bit_count == 0) |
| return false; |
| |
| return true; |
| } |
| |
| HidServiceWin::PreparsedData::ReportItem MakeReportItemFromPreparsedData( |
| const PreparsedDataItem& item) { |
| size_t bit_index = (item.byte_index - 1) * CHAR_BIT + item.bit_index; |
| return {item.report_id, item.bit_field, |
| item.bit_size, item.report_count, |
| item.usage_page, item.usage_minimum, |
| item.usage_maximum, item.designator_minimum, |
| item.designator_maximum, item.string_minimum, |
| item.string_maximum, item.logical_minimum, |
| item.logical_maximum, item.physical_minimum, |
| item.physical_maximum, item.unit, |
| item.unit_exponent, bit_index}; |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<HidPreparsedData> HidPreparsedData::Create( |
| HANDLE device_handle) { |
| PHIDP_PREPARSED_DATA preparsed_data; |
| if (!HidD_GetPreparsedData(device_handle, &preparsed_data) || |
| !preparsed_data) { |
| HID_PLOG(EVENT) << "Failed to get device data"; |
| return nullptr; |
| } |
| |
| HIDP_CAPS capabilities; |
| if (HidP_GetCaps(preparsed_data, &capabilities) != HIDP_STATUS_SUCCESS) { |
| HID_PLOG(EVENT) << "Failed to get device capabilities"; |
| HidD_FreePreparsedData(preparsed_data); |
| return nullptr; |
| } |
| |
| return base::WrapUnique(new HidPreparsedData(preparsed_data, capabilities)); |
| } |
| |
| HidPreparsedData::HidPreparsedData(PHIDP_PREPARSED_DATA preparsed_data, |
| HIDP_CAPS capabilities) |
| : preparsed_data_(preparsed_data), capabilities_(capabilities) { |
| DCHECK(preparsed_data_); |
| } |
| |
| HidPreparsedData::~HidPreparsedData() { |
| HidD_FreePreparsedData(preparsed_data_); |
| } |
| |
| const HIDP_CAPS& HidPreparsedData::GetCaps() const { |
| return capabilities_; |
| } |
| |
| std::vector<HidServiceWin::PreparsedData::ReportItem> |
| HidPreparsedData::GetReportItems(HIDP_REPORT_TYPE report_type) const { |
| const auto& header = |
| *reinterpret_cast<const PreparsedDataHeader*>(preparsed_data_); |
| if (!ValidatePreparsedDataHeader(header)) |
| return {}; |
| |
| size_t min_index; |
| size_t item_count; |
| switch (report_type) { |
| case HidP_Input: |
| min_index = 0; |
| item_count = header.input_item_count; |
| break; |
| case HidP_Output: |
| min_index = header.input_item_count; |
| item_count = header.output_item_count; |
| break; |
| case HidP_Feature: |
| min_index = header.input_item_count + header.output_item_count; |
| item_count = header.feature_item_count; |
| break; |
| default: |
| return {}; |
| } |
| if (item_count == 0) |
| return {}; |
| |
| const auto* data = reinterpret_cast<const uint8_t*>(preparsed_data_); |
| const auto* items = reinterpret_cast<const PreparsedDataItem*>( |
| data + sizeof(PreparsedDataHeader)); |
| std::vector<ReportItem> report_items; |
| for (size_t i = min_index; i < min_index + item_count; ++i) { |
| if (ValidatePreparsedDataItem(items[i])) |
| report_items.push_back(MakeReportItemFromPreparsedData(items[i])); |
| } |
| |
| return report_items; |
| } |
| |
| } // namespace device |