| // 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) |
| : UdevRule(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 |