blob: f1d99909391cd48148546a940b32263de875be8e [file] [log] [blame]
// Copyright 2014 The Chromium OS 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 "permission_broker/hidraw_subsystem_udev_rule.h"
#include <fcntl.h>
#include <libudev.h>
#include <linux/hidraw.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string>
#include "base/logging.h"
using std::string;
namespace permission_broker {
namespace {
const int kShortHeaderLength = 1;
const int kLongHeaderLength = 3;
enum ItemType {
TYPE_MAIN = 0,
TYPE_GLOBAL = 1,
TYPE_LOCAL = 2,
TYPE_RESERVED = 3
};
enum MainItemTag {
MAIN_TAG_DEFAULT = 0x00,
MAIN_TAG_INPUT = 0x08,
MAIN_TAG_OUTPUT = 0x09,
MAIN_TAG_COLLECTION = 0x0a,
MAIN_TAG_FEATURE = 0x0b,
MAIN_TAG_END_COLLECTION = 0x0c
};
enum GlobalItemTag {
GLOBAL_TAG_USAGE_PAGE = 0x00,
GLOBAL_TAG_LOGICAL_MINIMUM = 0x01,
GLOBAL_TAG_LOGICAL_MAXIMUM = 0x02,
GLOBAL_TAG_PHYSICAL_MINIMUM = 0x03,
GLOBAL_TAG_PHYSICAL_MAXIMUM = 0x04,
GLOBAL_TAG_UNIT_EXPONENT = 0x05,
GLOBAL_TAG_UNIT = 0x06,
GLOBAL_TAG_REPORT_SIZE = 0x07,
GLOBAL_TAG_REPORT_ID = 0x08,
GLOBAL_TAG_REPORT_COUNT = 0x09,
GLOBAL_TAG_PUSH = 0x0A,
GLOBAL_TAG_POP = 0x0B
};
enum LocalItemTag {
LOCAL_TAG_USAGE = 0x00,
LOCAL_TAG_USAGE_MINIMUM = 0x01,
LOCAL_TAG_USAGE_MAXIMUM = 0x02,
LOCAL_TAG_DESIGNATOR_INDEX = 0x03,
LOCAL_TAG_DESIGNATOR_MINIMUM = 0x04,
LOCAL_TAG_DESIGNATOR_MAXIMUM = 0x05,
LOCAL_TAG_STRING_INDEX = 0x07,
LOCAL_TAG_STRING_MINIMUM = 0x08,
LOCAL_TAG_STRING_MAXIMUM = 0x09,
LOCAL_TAG_DELIMITER = 0x0A
};
enum ReservedItemTag {
RESERVED_TAG_LONG = 0x0f,
};
struct DescriptorItem {
ItemType type;
union {
MainItemTag main;
GlobalItemTag global;
LocalItemTag local;
ReservedItemTag reserved;
uint32_t raw;
} tag;
uint32_t data_value;
int depth;
};
// Attempts to populate a ReportDescriptor for a given hidraw device.
bool GetHidReportDescriptor(struct udev_device* device,
HidReportDescriptor* descriptor) {
const char* dev_node = udev_device_get_devnode(device);
int device_fd = open(dev_node, O_RDONLY);
if (device_fd < 0) {
return false;
}
unsigned size = 0;
if (ioctl(device_fd, HIDIOCGRDESCSIZE, &size) < 0) {
close(device_fd);
return false;
}
hidraw_report_descriptor report_descriptor;
report_descriptor.size = size;
if (size > sizeof(report_descriptor.value) ||
ioctl(device_fd, HIDIOCGRDESC, &report_descriptor) < 0) {
close(device_fd);
return false;
}
close(device_fd);
if (report_descriptor.size > sizeof(descriptor->data)) {
return false;
}
descriptor->size = report_descriptor.size;
memcpy(&descriptor->data[0], &report_descriptor.value[0], descriptor->size);
return true;
}
bool ParseDescriptorItem(const HidReportDescriptor& descriptor,
int offset,
int current_depth,
DescriptorItem* item,
int* bytes_read,
int *new_depth) {
if (offset >= descriptor.size)
return false;
uint8_t header = descriptor.data[offset];
int data_size = header & 0x03;
item->type = static_cast<ItemType>((header >> 2) & 0x03);
item->tag.raw = (header & 0xf0) >> 4;
item->depth = current_depth;
item->data_value = 0;
// Long-form items are ignored.
if (item->type == TYPE_RESERVED && item->tag.reserved == RESERVED_TAG_LONG) {
if (offset + kShortHeaderLength >= descriptor.size) {
return false;
}
int long_descriptor_size = descriptor.data[offset + kShortHeaderLength];
*bytes_read = kLongHeaderLength + long_descriptor_size;
return true;
}
if (offset + data_size + 1 > descriptor.size) {
return false;
}
memcpy(&item->data_value, &descriptor.data[offset + 1], data_size);
*bytes_read = kShortHeaderLength + data_size;
if (item->type != TYPE_MAIN) {
return true;
}
if (item->tag.main == MAIN_TAG_END_COLLECTION) {
if (current_depth == 0) {
return false;
}
*new_depth = current_depth - 1;
} else if (item->tag.main == MAIN_TAG_COLLECTION) {
*new_depth = current_depth + 1;
}
return true;
}
// Attempts to extract all toplevel items from a descriptor, preserving order.
// Returns false if any header contents are missing or invalid.
bool ParseToplevelDescriptorItems(const HidReportDescriptor& descriptor,
std::vector<DescriptorItem>* items) {
int depth = 0;
int offset = 0;
while (offset < descriptor.size) {
int bytes_read;
DescriptorItem item;
if (!ParseDescriptorItem(
descriptor, offset, depth, &item, &bytes_read, &depth)) {
return false;
}
if (item.depth == 0) {
items->push_back(item);
}
offset += bytes_read;
}
return offset == descriptor.size;
}
} // namespace
HidrawSubsystemUdevRule::HidrawSubsystemUdevRule(const string& name)
: Rule(name) {
}
Rule::Result HidrawSubsystemUdevRule::ProcessDevice(
struct udev_device *device) {
const char *const subsystem = udev_device_get_subsystem(device);
if (!subsystem || strcmp(subsystem, "hidraw"))
return IGNORE;
return ProcessHidrawDevice(device);
}
// static
bool HidrawSubsystemUdevRule::ParseToplevelCollectionUsages(
const HidReportDescriptor& descriptor,
std::vector<HidUsage>* usages) {
std::vector<DescriptorItem> items;
if (!ParseToplevelDescriptorItems(descriptor, &items)) {
return false;
}
for (int i = 0; i < static_cast<int>(items.size()) - 2; ++i) {
DescriptorItem& first = items[i];
if (first.type != TYPE_GLOBAL || first.tag.global != GLOBAL_TAG_USAGE_PAGE)
continue;
DescriptorItem& second = items[i + 1];
if (second.type != TYPE_LOCAL || second.tag.local != LOCAL_TAG_USAGE)
continue;
DescriptorItem& third = items[i + 2];
if (third.type != TYPE_MAIN || third.tag.main != MAIN_TAG_COLLECTION)
continue;
usages->push_back(HidUsage(
static_cast<HidUsage::Page>(first.data_value),
static_cast<uint16_t>(second.data_value)));
}
return true;
}
// static
bool HidrawSubsystemUdevRule::GetHidToplevelUsages(
struct udev_device* device,
std::vector<HidUsage>* usages) {
const char* dev_node = udev_device_get_devnode(device);
HidReportDescriptor descriptor;
if (!GetHidReportDescriptor(device, &descriptor)) {
LOG(INFO) << "Unable to query descriptor for " << dev_node;
return false;
}
if (!ParseToplevelCollectionUsages(descriptor, usages)) {
LOG(INFO) << "Error parsing descriptor for " << dev_node;
return false;
}
return true;
}
} // namespace permission_broker