blob: 7b2c2fbf754e9098fe59edf8bf978ed68f850c40 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// 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_connection.h"
#include "base/containers/contains.h"
#include "base/memory/ref_counted_memory.h"
#include "base/ranges/algorithm.h"
#include "components/device_event_log/device_event_log.h"
#include "services/device/public/cpp/hid/hid_report_type.h"
#include "services/device/public/cpp/hid/hid_report_utils.h"
#include "services/device/public/mojom/hid.mojom.h"
namespace device {
namespace {
bool HasAlwaysProtectedCollection(
const std::vector<mojom::HidCollectionInfoPtr>& collections) {
return base::ranges::any_of(collections, [](const auto& collection) {
return IsAlwaysProtected(*collection->usage, HidReportType::kInput) ||
IsAlwaysProtected(*collection->usage, HidReportType::kOutput) ||
IsAlwaysProtected(*collection->usage, HidReportType::kFeature);
});
}
} // namespace
HidConnection::HidConnection(scoped_refptr<HidDeviceInfo> device_info,
bool allow_protected_reports,
bool allow_fido_reports)
: device_info_(device_info),
allow_protected_reports_(allow_protected_reports),
allow_fido_reports_(allow_fido_reports),
closed_(false) {
has_always_protected_collection_ =
HasAlwaysProtectedCollection(device_info->collections());
}
HidConnection::~HidConnection() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(closed_);
}
void HidConnection::SetClient(Client* client) {
if (client) {
DCHECK(pending_reads_.empty());
DCHECK(pending_reports_.empty());
}
client_ = client;
}
void HidConnection::Close() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!closed_);
PlatformClose();
closed_ = true;
}
void HidConnection::Read(ReadCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!client_);
if (device_info_->max_input_report_size() == 0) {
HID_LOG(USER) << "This device does not support input reports.";
std::move(callback).Run(false, nullptr, 0);
return;
}
pending_reads_.emplace(std::move(callback));
ProcessReadQueue();
}
void HidConnection::Write(scoped_refptr<base::RefCountedBytes> buffer,
WriteCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (device_info_->max_output_report_size() == 0) {
HID_LOG(USER) << "This device does not support output reports.";
std::move(callback).Run(false);
return;
}
if (buffer->size() > device_info_->max_output_report_size() + 1) {
HID_LOG(USER) << "Output report buffer too long (" << buffer->size()
<< " > " << (device_info_->max_output_report_size() + 1)
<< ").";
std::move(callback).Run(false);
return;
}
DCHECK_GE(buffer->size(), 1u);
uint8_t report_id = buffer->data()[0];
if (device_info_->has_report_id() != (report_id != 0)) {
HID_LOG(USER) << "Invalid output report ID.";
std::move(callback).Run(false);
return;
}
if (IsReportProtected(report_id, HidReportType::kOutput)) {
HID_LOG(USER) << "Attempt to set a protected output report.";
std::move(callback).Run(false);
return;
}
PlatformWrite(buffer, std::move(callback));
}
void HidConnection::GetFeatureReport(uint8_t report_id, ReadCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (device_info_->max_feature_report_size() == 0) {
HID_LOG(USER) << "This device does not support feature reports.";
std::move(callback).Run(false, nullptr, 0);
return;
}
if (device_info_->has_report_id() != (report_id != 0)) {
HID_LOG(USER) << "Invalid feature report ID.";
std::move(callback).Run(false, nullptr, 0);
return;
}
if (IsReportProtected(report_id, HidReportType::kFeature)) {
HID_LOG(USER) << "Attempt to get a protected feature report.";
std::move(callback).Run(false, nullptr, 0);
return;
}
PlatformGetFeatureReport(report_id, std::move(callback));
}
void HidConnection::SendFeatureReport(
scoped_refptr<base::RefCountedBytes> buffer,
WriteCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (device_info_->max_feature_report_size() == 0) {
HID_LOG(USER) << "This device does not support feature reports.";
std::move(callback).Run(false);
return;
}
DCHECK_GE(buffer->size(), 1u);
uint8_t report_id = buffer->data()[0];
if (device_info_->has_report_id() != (report_id != 0)) {
HID_LOG(USER) << "Invalid feature report ID.";
std::move(callback).Run(false);
return;
}
if (IsReportProtected(report_id, HidReportType::kFeature)) {
HID_LOG(USER) << "Attempt to set a protected feature report.";
std::move(callback).Run(false);
return;
}
PlatformSendFeatureReport(buffer, std::move(callback));
}
bool HidConnection::IsReportProtected(uint8_t report_id,
HidReportType report_type) const {
const mojom::HidDeviceInfo& device = *device_info_->device();
if (!allow_protected_reports_) {
// If |allow_fido_reports_| is true, allow access to reports in collections
// with a usage from the FIDO usage page. FIDO reports are normally blocked
// by the HID blocklist.
if (allow_fido_reports_) {
auto* collection_info =
FindCollectionWithReport(device, report_id, report_type);
if (collection_info &&
collection_info->usage->usage_page == mojom::kPageFido) {
return false;
}
}
// Deny access to reports that match HID blocklist rules.
if (report_type == HidReportType::kInput) {
if (device.protected_input_report_ids.has_value() &&
base::Contains(*device.protected_input_report_ids, report_id)) {
return true;
}
} else if (report_type == HidReportType::kOutput) {
if (device.protected_output_report_ids.has_value() &&
base::Contains(*device.protected_output_report_ids, report_id)) {
return true;
}
} else if (report_type == HidReportType::kFeature) {
if (device.protected_feature_report_ids.has_value() &&
base::Contains(*device.protected_feature_report_ids, report_id)) {
return true;
}
}
}
// Some types of reports are always blocked regardless of
// |allow_protected_reports_|.
auto* collection_info =
FindCollectionWithReport(device, report_id, report_type);
if (collection_info) {
return IsAlwaysProtected(*collection_info->usage, report_type);
}
return has_always_protected_collection_;
}
void HidConnection::ProcessInputReport(
scoped_refptr<base::RefCountedBytes> buffer,
size_t size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GE(size, 1u);
uint8_t report_id = buffer->data()[0];
if (IsReportProtected(report_id, HidReportType::kInput)) {
return;
}
if (client_) {
client_->OnInputReport(buffer, size);
} else {
pending_reports_.emplace(buffer, size);
ProcessReadQueue();
}
}
void HidConnection::ProcessReadQueue() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!client_);
// Hold a reference to |this| to prevent a callback from freeing this object
// during the loop.
scoped_refptr<HidConnection> self(this);
while (pending_reads_.size() && pending_reports_.size()) {
ReadCallback callback = std::move(pending_reads_.front());
auto [buffer, size] = std::move(pending_reports_.front());
pending_reads_.pop();
pending_reports_.pop();
std::move(callback).Run(true, std::move(buffer), size);
}
}
} // namespace device