#include "services/device/hid/hid_preparsed_data.h"
#include <cstddef>
#include <cstdint>
#include "base/compiler_specific.h"
#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 {
#pragma pack(push, 1)
struct PreparsedDataHeader {
uint64_t magic;
uint16_t usage;
uint16_t usage_page;
uint16_t unknown[3];
uint16_t input_item_count;
uint16_t unknown2;
uint16_t input_report_byte_length;
uint16_t unknown3;
uint16_t output_item_count;
uint16_t unknown4;
uint16_t output_report_byte_length;
uint16_t unknown5;
uint16_t feature_item_count;
uint16_t item_count;
uint16_t feature_report_byte_length;
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 {
uint16_t usage_page;
uint8_t report_id;
uint8_t bit_index;
uint16_t bit_size;
uint16_t report_count;
uint16_t byte_index;
uint16_t bit_count;
uint32_t bit_field;
uint32_t unknown;
uint16_t link_usage_page;
uint16_t link_usage;
uint32_t unknown2[9];
uint16_t usage_minimum;
uint16_t usage_maximum;
uint16_t string_minimum;
uint16_t string_maximum;
uint16_t designator_minimum;
uint16_t designator_maximum;
uint16_t data_index_minimum;
uint16_t data_index_maximum;
uint32_t unknown3;
int32_t logical_minimum;
int32_t logical_maximum;
int32_t physical_minimum;
int32_t physical_maximum;
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;
constexpr uint64_t kHidPreparsedDataMagic = 0x52444B2050646948;
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;
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) {
if (item.byte_index == 0)
return false;
if (item.bit_index >= CHAR_BIT)
return false;
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};
}
}
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*>(
UNSAFE_TODO(data + sizeof(PreparsedDataHeader)));
std::vector<ReportItem> report_items;
for (size_t i = min_index; i < min_index + item_count; ++i) {
if (ValidatePreparsedDataItem(UNSAFE_TODO(items[i]))) {
report_items.push_back(
MakeReportItemFromPreparsedData(UNSAFE_TODO(items[i])));
}
}
return report_items;
}
}