Commit f4dc9d14 authored by reillyg's avatar reillyg Committed by Commit bot

Add more generic filters to the chrome.hid.getDevices API.

Instead of specifying each of the USB vendor and product ID pairs an
app has permission to open this API extension allows filtering of
devices by vendor and product IDs as well as HID usage pages and
usages. The 'vendorId' and 'productId' properties of GetDevicesOptions
are still supported for backwards compatibility.

BUG=

Review URL: https://codereview.chromium.org/514923002

Cr-Commit-Position: refs/heads/master@{#292577}
parent 3a05d24b
......@@ -44,6 +44,7 @@
'usb/usb_ids_unittest.cc',
'usb/usb_service_unittest.cc',
'hid/hid_connection_unittest.cc',
'hid/hid_device_filter_unittest.cc',
'hid/hid_report_descriptor_unittest.cc',
'hid/hid_service_unittest.cc',
'hid/input_service_linux_unittest.cc',
......
......@@ -16,6 +16,8 @@ source_set("hid") {
"hid_connection_mac.h",
"hid_connection_win.cc",
"hid_connection_win.h",
"hid_device_filter.cc",
"hid_device_filter.h",
"hid_device_info.cc",
"hid_device_info.h",
"hid_report_descriptor.cc",
......
......@@ -26,6 +26,8 @@
'hid_connection_mac.h',
'hid_connection_win.cc',
'hid_connection_win.h',
'hid_device_filter.cc',
'hid_device_filter.h',
'hid_device_info.cc',
'hid_device_info.h',
'hid_report_descriptor.cc',
......
// Copyright 2014 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 "device/hid/hid_device_filter.h"
#include "device/hid/hid_device_info.h"
namespace device {
HidDeviceFilter::HidDeviceFilter()
: vendor_id_set_(false),
product_id_set_(false),
usage_page_set_(false),
usage_set_(false) {
}
HidDeviceFilter::~HidDeviceFilter() {
}
void HidDeviceFilter::SetVendorId(uint16_t vendor_id) {
vendor_id_set_ = true;
vendor_id_ = vendor_id;
}
void HidDeviceFilter::SetProductId(uint16_t product_id) {
product_id_set_ = true;
product_id_ = product_id;
}
void HidDeviceFilter::SetUsagePage(uint16_t usage_page) {
usage_page_set_ = true;
usage_page_ = usage_page;
}
void HidDeviceFilter::SetUsage(uint16_t usage) {
usage_set_ = true;
usage_ = usage;
}
bool HidDeviceFilter::Matches(const HidDeviceInfo& device_info) const {
if (vendor_id_set_) {
if (device_info.vendor_id != vendor_id_) {
return false;
}
if (product_id_set_ && device_info.product_id != product_id_) {
return false;
}
}
if (usage_page_set_) {
bool found_matching_collection = false;
for (std::vector<HidCollectionInfo>::const_iterator i =
device_info.collections.begin();
i != device_info.collections.end() && !found_matching_collection;
++i) {
const HidCollectionInfo& collection = *i;
if (collection.usage.usage_page != usage_page_) {
continue;
}
if (usage_set_ && collection.usage.usage != usage_) {
continue;
}
found_matching_collection = true;
}
if (!found_matching_collection) {
return false;
}
}
return true;
}
// static
bool HidDeviceFilter::MatchesAny(
const HidDeviceInfo& device_info,
const std::vector<HidDeviceFilter>& filters) {
for (std::vector<HidDeviceFilter>::const_iterator i = filters.begin();
i != filters.end();
++i) {
if (i->Matches(device_info)) {
return true;
}
}
return false;
}
} // namespace device
// Copyright 2014 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.
#ifndef DEVICE_HID_HID_DEVICE_FILTER_H_
#define DEVICE_HID_HID_DEVICE_FILTER_H_
#include <stdint.h>
#include <vector>
namespace device {
struct HidDeviceInfo;
class HidDeviceFilter {
public:
HidDeviceFilter();
~HidDeviceFilter();
void SetVendorId(uint16_t vendor_id);
void SetProductId(uint16_t product_id);
void SetUsagePage(uint16_t usage_page);
void SetUsage(uint16_t usage);
bool Matches(const HidDeviceInfo& device_info) const;
static bool MatchesAny(const HidDeviceInfo& device_info,
const std::vector<HidDeviceFilter>& filters);
private:
uint16_t vendor_id_;
uint16_t product_id_;
uint16_t usage_page_;
uint16_t usage_;
bool vendor_id_set_ : 1;
bool product_id_set_ : 1;
bool usage_page_set_ : 1;
bool usage_set_ : 1;
};
} // namespace device
#endif // DEVICE_HID_HID_DEVICE_FILTER_H_
// Copyright 2014 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 "device/hid/hid_device_filter.h"
#include "device/hid/hid_device_info.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
namespace {
class HidFilterTest : public testing::Test {
public:
virtual void SetUp() OVERRIDE {
device_info_.vendor_id = 0x046d;
device_info_.product_id = 0xc31c;
HidCollectionInfo collection;
collection.usage.usage_page = HidUsageAndPage::kPageKeyboard;
collection.usage.usage = 0x01;
device_info_.collections.push_back(collection);
}
protected:
HidDeviceInfo device_info_;
};
TEST_F(HidFilterTest, MatchAny) {
HidDeviceFilter filter;
ASSERT_TRUE(filter.Matches(device_info_));
}
TEST_F(HidFilterTest, MatchVendorId) {
HidDeviceFilter filter;
filter.SetVendorId(0x046d);
ASSERT_TRUE(filter.Matches(device_info_));
}
TEST_F(HidFilterTest, MatchVendorIdNegative) {
HidDeviceFilter filter;
filter.SetVendorId(0x18d1);
ASSERT_FALSE(filter.Matches(device_info_));
}
TEST_F(HidFilterTest, MatchProductId) {
HidDeviceFilter filter;
filter.SetVendorId(0x046d);
filter.SetProductId(0xc31c);
ASSERT_TRUE(filter.Matches(device_info_));
}
TEST_F(HidFilterTest, MatchProductIdNegative) {
HidDeviceFilter filter;
filter.SetVendorId(0x046d);
filter.SetProductId(0x0801);
ASSERT_FALSE(filter.Matches(device_info_));
}
TEST_F(HidFilterTest, MatchUsagePage) {
HidDeviceFilter filter;
filter.SetUsagePage(HidUsageAndPage::kPageKeyboard);
ASSERT_TRUE(filter.Matches(device_info_));
}
TEST_F(HidFilterTest, MatchUsagePageNegative) {
HidDeviceFilter filter;
filter.SetUsagePage(HidUsageAndPage::kPageLed);
ASSERT_FALSE(filter.Matches(device_info_));
}
TEST_F(HidFilterTest, MatchVendorAndUsagePage) {
HidDeviceFilter filter;
filter.SetVendorId(0x046d);
filter.SetUsagePage(HidUsageAndPage::kPageKeyboard);
ASSERT_TRUE(filter.Matches(device_info_));
}
TEST_F(HidFilterTest, MatchUsageAndPage) {
HidDeviceFilter filter;
filter.SetUsagePage(HidUsageAndPage::kPageKeyboard);
filter.SetUsage(0x01);
ASSERT_TRUE(filter.Matches(device_info_));
}
TEST_F(HidFilterTest, MatchUsageAndPageNegative) {
HidDeviceFilter filter;
filter.SetUsagePage(HidUsageAndPage::kPageKeyboard);
filter.SetUsage(0x02);
ASSERT_FALSE(filter.Matches(device_info_));
}
TEST_F(HidFilterTest, MatchEmptyFilterListNegative) {
std::vector<HidDeviceFilter> filters;
ASSERT_FALSE(HidDeviceFilter::MatchesAny(device_info_, filters));
}
TEST_F(HidFilterTest, MatchFilterList) {
std::vector<HidDeviceFilter> filters;
HidDeviceFilter filter;
filter.SetUsagePage(HidUsageAndPage::kPageKeyboard);
filters.push_back(filter);
ASSERT_TRUE(HidDeviceFilter::MatchesAny(device_info_, filters));
}
TEST_F(HidFilterTest, MatchFilterListNegative) {
std::vector<HidDeviceFilter> filters;
HidDeviceFilter filter;
filter.SetUsagePage(HidUsageAndPage::kPageLed);
filters.push_back(filter);
ASSERT_FALSE(HidDeviceFilter::MatchesAny(device_info_, filters));
}
} // namespace
} // namespace device
......@@ -8,18 +8,18 @@
#include <vector>
#include "device/hid/hid_connection.h"
#include "device/hid/hid_device_filter.h"
#include "device/hid/hid_device_info.h"
#include "device/hid/hid_service.h"
#include "extensions/browser/api/api_resource_manager.h"
#include "extensions/browser/api/extensions_api_client.h"
#include "extensions/common/api/hid.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/permissions/usb_device_permission.h"
#include "net/base/io_buffer.h"
namespace hid = extensions::core_api::hid;
using device::HidConnection;
using device::HidDeviceFilter;
using device::HidDeviceInfo;
using device::HidService;
......@@ -38,6 +38,22 @@ base::Value* PopulateHidConnection(int connection_id,
return connection_value.ToValue().release();
}
void ConvertHidDeviceFilter(linked_ptr<hid::DeviceFilter> input,
HidDeviceFilter* output) {
if (input->vendor_id) {
output->SetVendorId(*input->vendor_id);
}
if (input->product_id) {
output->SetProductId(*input->product_id);
}
if (input->usage_page) {
output->SetUsagePage(*input->usage_page);
}
if (input->usage) {
output->SetUsage(*input->usage);
}
}
} // namespace
namespace extensions {
......@@ -84,18 +100,23 @@ bool HidGetDevicesFunction::Prepare() {
}
void HidGetDevicesFunction::AsyncWorkStart() {
const uint16_t vendor_id = parameters_->options.vendor_id;
const uint16_t product_id = parameters_->options.product_id;
UsbDevicePermission::CheckParam param(
vendor_id, product_id, UsbDevicePermissionData::UNSPECIFIED_INTERFACE);
if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
APIPermission::kUsbDevice, &param)) {
LOG(WARNING) << "Insufficient permissions to access device.";
CompleteWithError(kErrorPermissionDenied);
return;
std::vector<HidDeviceFilter> filters;
if (parameters_->options.filters) {
filters.resize(parameters_->options.filters->size());
for (size_t i = 0; i < parameters_->options.filters->size(); ++i) {
ConvertHidDeviceFilter(parameters_->options.filters->at(i), &filters[i]);
}
}
if (parameters_->options.vendor_id) {
HidDeviceFilter legacy_filter;
legacy_filter.SetVendorId(*parameters_->options.vendor_id);
if (parameters_->options.product_id) {
legacy_filter.SetProductId(*parameters_->options.product_id);
}
filters.push_back(legacy_filter);
}
SetResult(device_manager_->GetApiDevices(vendor_id, product_id).release());
SetResult(device_manager_->GetApiDevices(extension(), filters).release());
AsyncWorkCompleted();
}
......@@ -116,12 +137,7 @@ void HidConnectFunction::AsyncWorkStart() {
return;
}
UsbDevicePermission::CheckParam param(
device_info.vendor_id,
device_info.product_id,
UsbDevicePermissionData::UNSPECIFIED_INTERFACE);
if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
APIPermission::kUsbDevice, &param)) {
if (!device_manager_->HasPermission(extension(), device_info)) {
LOG(WARNING) << "Insufficient permissions to access device.";
CompleteWithError(kErrorPermissionDenied);
return;
......
......@@ -8,9 +8,13 @@
#include <vector>
#include "base/lazy_instance.h"
#include "device/hid/hid_device_filter.h"
#include "device/hid/hid_service.h"
#include "extensions/browser/api/extensions_api_client.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/permissions/usb_device_permission.h"
using device::HidDeviceFilter;
using device::HidService;
using device::HidUsageAndPage;
......@@ -30,8 +34,8 @@ HidDeviceManager::GetFactoryInstance() {
}
scoped_ptr<base::ListValue> HidDeviceManager::GetApiDevices(
uint16_t vendor_id,
uint16_t product_id) {
const Extension* extension,
const std::vector<HidDeviceFilter>& filters) {
UpdateDevices();
HidService* hid_service = ExtensionsAPIClient::Get()->GetHidService();
......@@ -46,47 +50,52 @@ scoped_ptr<base::ListValue> HidDeviceManager::GetApiDevices(
device::HidDeviceInfo device_info;
if (hid_service->GetDeviceInfo(device_id, &device_info)) {
if (device_info.vendor_id == vendor_id &&
device_info.product_id == product_id) {
core_api::hid::HidDeviceInfo api_device_info;
api_device_info.device_id = resource_id;
api_device_info.vendor_id = device_info.vendor_id;
api_device_info.product_id = device_info.product_id;
api_device_info.max_input_report_size =
device_info.max_input_report_size;
api_device_info.max_output_report_size =
device_info.max_output_report_size;
api_device_info.max_feature_report_size =
device_info.max_feature_report_size;
for (std::vector<device::HidCollectionInfo>::const_iterator
collections_iter = device_info.collections.begin();
collections_iter != device_info.collections.end();
++collections_iter) {
device::HidCollectionInfo collection = *collections_iter;
// Don't expose sensitive data.
if (collection.usage.IsProtected()) {
continue;
}
core_api::hid::HidCollectionInfo* api_collection =
new core_api::hid::HidCollectionInfo();
api_collection->usage_page = collection.usage.usage_page;
api_collection->usage = collection.usage.usage;
api_collection->report_ids.resize(collection.report_ids.size());
std::copy(collection.report_ids.begin(),
collection.report_ids.end(),
api_collection->report_ids.begin());
api_device_info.collections.push_back(
make_linked_ptr(api_collection));
if (!filters.empty() &&
!HidDeviceFilter::MatchesAny(device_info, filters)) {
continue;
}
if (!HasPermission(extension, device_info)) {
continue;
}
core_api::hid::HidDeviceInfo api_device_info;
api_device_info.device_id = resource_id;
api_device_info.vendor_id = device_info.vendor_id;
api_device_info.product_id = device_info.product_id;
api_device_info.max_input_report_size = device_info.max_input_report_size;
api_device_info.max_output_report_size =
device_info.max_output_report_size;
api_device_info.max_feature_report_size =
device_info.max_feature_report_size;
for (std::vector<device::HidCollectionInfo>::const_iterator
collections_iter = device_info.collections.begin();
collections_iter != device_info.collections.end();
++collections_iter) {
const device::HidCollectionInfo& collection = *collections_iter;
// Don't expose sensitive data.
if (collection.usage.IsProtected()) {
continue;
}
// Expose devices with which user can communicate.
if (api_device_info.collections.size() > 0)
api_devices->Append(api_device_info.ToValue().release());
core_api::hid::HidCollectionInfo* api_collection =
new core_api::hid::HidCollectionInfo();
api_collection->usage_page = collection.usage.usage_page;
api_collection->usage = collection.usage.usage;
api_collection->report_ids.resize(collection.report_ids.size());
std::copy(collection.report_ids.begin(),
collection.report_ids.end(),
api_collection->report_ids.begin());
api_device_info.collections.push_back(make_linked_ptr(api_collection));
}
// Expose devices with which user can communicate.
if (api_device_info.collections.size() > 0) {
api_devices->Append(api_device_info.ToValue().release());
}
}
}
......@@ -108,6 +117,20 @@ bool HidDeviceManager::GetDeviceInfo(int resource_id,
return hid_service->GetDeviceInfo(device_iter->second, device_info);
}
bool HidDeviceManager::HasPermission(const Extension* extension,
const device::HidDeviceInfo& device_info) {
UsbDevicePermission::CheckParam usbParam(
device_info.vendor_id,
device_info.product_id,
UsbDevicePermissionData::UNSPECIFIED_INTERFACE);
if (extension->permissions_data()->CheckAPIPermissionWithParam(
APIPermission::kUsbDevice, &usbParam)) {
return true;
}
return false;
}
void HidDeviceManager::UpdateDevices() {
thread_checker_.CalledOnValidThread();
HidService* hid_service = ExtensionsAPIClient::Get()->GetHidService();
......
......@@ -16,8 +16,14 @@
#include "extensions/browser/browser_context_keyed_api_factory.h"
#include "extensions/common/api/hid.h"
namespace device {
class HidDeviceFilter;
}
namespace extensions {
class Extension;
class HidDeviceManager : public BrowserContextKeyedAPI {
public:
explicit HidDeviceManager(content::BrowserContext* context);
......@@ -31,11 +37,15 @@ class HidDeviceManager : public BrowserContextKeyedAPI {
return BrowserContextKeyedAPIFactory<HidDeviceManager>::Get(context);
}
scoped_ptr<base::ListValue> GetApiDevices(uint16_t vendor_id,
uint16_t product_id);
scoped_ptr<base::ListValue> GetApiDevices(
const Extension* extension,
const std::vector<device::HidDeviceFilter>& filters);
bool GetDeviceInfo(int resource_id, device::HidDeviceInfo* device_info);
bool HasPermission(const Extension* extension,
const device::HidDeviceInfo& device_info);
private:
friend class BrowserContextKeyedAPIFactory<HidDeviceManager>;
......
......@@ -6,33 +6,29 @@
// This API provides access to HID operations from within the context of an app.
// Using this API, apps can function as drivers for hardware devices.
namespace hid {
// HID top-level collection attributes.
// Each enumerated device interface exposes an array of these objects.
// |usagePage|: HID usage page identifier.
// |usage|: Page-defined usage identifier.
// |reportIds|: Report IDs which belong to the collection and to its children.
dictionary HidCollectionInfo {
// HID usage page identifier.
long usagePage;
// Page-defined usage identifier.
long usage;
// Report IDs which belong to the collection and to its children.
long[] reportIds;
};
// Returned by <code>getDevices</code> functions to describes a connected HID
// device. Use <code>connect</code> to connect to any of the returned devices.
// |deviceId|: Device opaque ID.
// |vendorId|: Vendor ID.
// |productId|: Product ID.
// |collections|: Top-level collections from this device's report descriptor.
// |maxInputReportSize|: Top-level collection's max input report size.
// |maxOutputReportSize|: Top-level collection's max output report size.
// |maxFeatureReportSize|: Top-level collection's max feature report size.
dictionary HidDeviceInfo {
[noinline_doc] dictionary HidDeviceInfo {
// Device opaque ID.
long deviceId;
// Vendor ID.
long vendorId;
// Product ID.
long productId;
// Top-level collections from this device's report descriptors.
HidCollectionInfo[] collections;
// Top-level collection's maximum input report size.
long maxInputReportSize;
// Top-level collection's maximum output report size.
long maxOutputReportSize;
// Top-level collection's maximum feature report size.
long maxFeatureReportSize;
};
......@@ -42,48 +38,54 @@ namespace hid {
long connectionId;
};
// Searching criteria to enumerate devices with.
[noinline_doc] dictionary DeviceFilter {
// Device vendor ID.
long? vendorId;
// Device product ID, only checked only if the vendor ID matches.
long? productId;
// HID usage page identifier.
long? usagePage;
// HID usage identifier, checked only if the HID usage page matches.
long? usage;
};
dictionary GetDevicesOptions {
long vendorId;
long productId;
[deprecated="Equivalent to setting $(ref:DeviceFilter.vendorId)."]
long? vendorId;
[deprecated="Equivalent to setting $(ref:DeviceFilter.productId)."]
long? productId;
// A device matching any given filter will be returned. An empty filter list
// will return all devices the app has permission for.
DeviceFilter[]? filters;
};
callback GetDevicesCallback = void (HidDeviceInfo[] devices);
callback ConnectCallback = void (HidConnectInfo connection);
callback DisconnectCallback = void ();
// The callback to be invoked when a <code>receive</code> call is finished.
// |reportId|: The ID of the report.
// |data|: The content of the report.
callback ReceiveCallback = void (long reportId, ArrayBuffer data);
// The callback to be invoked when a <code>receiveFeatureReport</code> call
// is finished.
// |data|: The content of the report.
callback ReceiveFeatureReportCallback = void (ArrayBuffer data);
// The callback to be invoked when a <code>send</code> or
// <code>sendFeatureReport</code> call is finished.
callback SendCallback = void();
interface Functions {
// Enumerate all the connected HID devices specified by the vendorId/
// productId/interfaceId tuple.
// Enumerate connected HID devices.
// |options|: The properties to search for on target devices.
// |callback|: Invoked with the <code>HidDeviceInfo</code> array on success.
static void getDevices(GetDevicesOptions options,
GetDevicesCallback callback);
// Open a connection to an HID device for communication.
// |deviceId|: The ID of the device to open.
// |callback|: Invoked with an <code>HidConnectInfo</code>.
static void connect(long deviceId,
ConnectCallback callback);
// Disconnect from a device. Invoking operations on a device after calling
// this is safe but has no effect.
// |connectionId|: The connection to close.
// |callback|: The callback to invoke once the device is closed.
static void disconnect(long connectionId,
optional DisconnectCallback callback);
......@@ -91,7 +93,6 @@ namespace hid {
//
// Input reports are returned to the host through the INTERRUPT IN endpoint.
// |connectionId|: The connection from which to receive a report.
// |callback|: The callback to invoke with received report.
static void receive(long connectionId,
ReceiveCallback callback);
......@@ -103,7 +104,6 @@ namespace hid {
// |connectionId|: The connection to which to send a report.
// |reportId|: The report ID to use, or <code>0</code> if none.
// |data|: The report data.
// |callback|: The callback to invoke once the write is finished.
static void send(long connectionId,
long reportId,
ArrayBuffer data,
......@@ -113,7 +113,6 @@ namespace hid {
//
// |connectionId|: The connection to read Input report from.
// |reportId|: The report ID, or zero if none.
// |callback|: The callback to invoke once the write is finished.
static void receiveFeatureReport(long connectionId,
long reportId,
ReceiveFeatureReportCallback callback);
......@@ -125,7 +124,6 @@ namespace hid {
// |connectionId|: The connection to read Input report from.
// |reportId|: The report ID to use, or <code>0</code> if none.
// |data|: The report data.
// |callback|: The callback to invoke once the write is finished.
static void sendFeatureReport(long connectionId,
long reportId,
ArrayBuffer data,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment