blob: 75dfb3a2beaee49a6dbedff10d7417eb49cd0d69 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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