blob: 862e37afc85b777252a097d3f56fb87212d28a81 [file] [log] [blame]
// Copyright (c) 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 "services/device/hid/hid_connection_win.h"
#include <cstring>
#include <utility>
#include "base/bind.h"
#include "base/containers/contains.h"
#include "base/files/file.h"
#include "base/macros.h"
#include "base/memory/ref_counted_memory.h"
#include "base/numerics/safe_conversions.h"
#include "base/win/object_watcher.h"
#include "components/device_event_log/device_event_log.h"
#define INITGUID
#include <hidclass.h>
#include <windows.h>
extern "C" {
#include <hidsdi.h>
}
#include <setupapi.h>
#include <winioctl.h>
namespace device {
namespace {
bool IsValidHandle(HANDLE handle) {
return handle != nullptr && handle != INVALID_HANDLE_VALUE;
}
} // namespace
HidConnectionWin::HidDeviceEntry::HidDeviceEntry(
base::flat_set<uint8_t> report_ids,
base::win::ScopedHandle file_handle)
: report_ids(std::move(report_ids)), file_handle(std::move(file_handle)) {}
HidConnectionWin::HidDeviceEntry::~HidDeviceEntry() = default;
class PendingHidTransfer : public base::win::ObjectWatcher::Delegate {
public:
typedef base::OnceCallback<void(PendingHidTransfer*, bool)> Callback;
PendingHidTransfer(scoped_refptr<base::RefCountedBytes> buffer,
Callback callback);
~PendingHidTransfer() override;
void TakeResultFromWindowsAPI(BOOL result);
OVERLAPPED* GetOverlapped() { return &overlapped_; }
// Implements base::win::ObjectWatcher::Delegate.
void OnObjectSignaled(HANDLE object) override;
private:
// The buffer isn't used by this object but it's important that a reference
// to it is held until the transfer completes.
scoped_refptr<base::RefCountedBytes> buffer_;
Callback callback_;
OVERLAPPED overlapped_;
base::win::ScopedHandle event_;
base::win::ObjectWatcher watcher_;
DISALLOW_COPY_AND_ASSIGN(PendingHidTransfer);
};
PendingHidTransfer::PendingHidTransfer(
scoped_refptr<base::RefCountedBytes> buffer,
PendingHidTransfer::Callback callback)
: buffer_(buffer),
callback_(std::move(callback)),
event_(CreateEvent(NULL, FALSE, FALSE, NULL)) {
memset(&overlapped_, 0, sizeof(OVERLAPPED));
overlapped_.hEvent = event_.Get();
}
PendingHidTransfer::~PendingHidTransfer() {
if (callback_)
std::move(callback_).Run(this, false);
}
void PendingHidTransfer::TakeResultFromWindowsAPI(BOOL result) {
if (result) {
std::move(callback_).Run(this, true);
} else if (GetLastError() == ERROR_IO_PENDING) {
watcher_.StartWatchingOnce(event_.Get(), this);
} else {
HID_PLOG(DEBUG) << "HID transfer failed";
std::move(callback_).Run(this, false);
}
}
void PendingHidTransfer::OnObjectSignaled(HANDLE event_handle) {
std::move(callback_).Run(this, true);
}
// static
scoped_refptr<HidConnection> HidConnectionWin::Create(
scoped_refptr<HidDeviceInfo> device_info,
std::vector<std::unique_ptr<HidDeviceEntry>> file_handles,
bool allow_protected_reports) {
scoped_refptr<HidConnectionWin> connection(
new HidConnectionWin(std::move(device_info), std::move(file_handles),
allow_protected_reports));
connection->SetUpInitialReads();
return std::move(connection);
}
HidConnectionWin::HidConnectionWin(
scoped_refptr<HidDeviceInfo> device_info,
std::vector<std::unique_ptr<HidDeviceEntry>> file_handles,
bool allow_protected_reports)
: HidConnection(std::move(device_info), allow_protected_reports),
file_handles_(std::move(file_handles)) {}
HidConnectionWin::~HidConnectionWin() {
DCHECK(file_handles_.empty());
DCHECK(transfers_.empty());
}
void HidConnectionWin::PlatformClose() {
for (auto& entry : file_handles_) {
CancelIo(entry->file_handle.Get());
entry->file_handle.Close();
}
file_handles_.clear();
transfers_.clear();
}
void HidConnectionWin::PlatformWrite(
scoped_refptr<base::RefCountedBytes> buffer,
WriteCallback callback) {
// The Windows API always wants either a report ID (if supported) or zero at
// the front of every output report and requires that the buffer size be equal
// to the maximum output report size supported by this collection.
size_t expected_size = device_info()->max_output_report_size() + 1;
DCHECK(buffer->size() <= expected_size);
buffer->data().resize(expected_size);
uint8_t report_id = buffer->data()[0];
HANDLE file_handle = GetHandleForReportId(report_id);
if (!IsValidHandle(file_handle)) {
HID_LOG(DEBUG) << "HID write failed due to invalid handle.";
std::move(callback).Run(false);
return;
}
transfers_.push_back(std::make_unique<PendingHidTransfer>(
buffer, base::BindOnce(&HidConnectionWin::OnWriteComplete, this,
file_handle, std::move(callback))));
transfers_.back()->TakeResultFromWindowsAPI(WriteFile(
file_handle, buffer->front(), static_cast<DWORD>(buffer->size()), NULL,
transfers_.back()->GetOverlapped()));
}
void HidConnectionWin::PlatformGetFeatureReport(uint8_t report_id,
ReadCallback callback) {
// The first byte of the destination buffer is the report ID being requested.
auto buffer = base::MakeRefCounted<base::RefCountedBytes>(
device_info()->max_feature_report_size() + 1);
buffer->data()[0] = report_id;
HANDLE file_handle = GetHandleForReportId(report_id);
if (!IsValidHandle(file_handle)) {
HID_LOG(DEBUG) << "HID read failed due to invalid handle.";
std::move(callback).Run(false, nullptr, 0);
return;
}
transfers_.push_back(std::make_unique<PendingHidTransfer>(
buffer, base::BindOnce(&HidConnectionWin::OnReadFeatureComplete, this,
file_handle, buffer, std::move(callback))));
transfers_.back()->TakeResultFromWindowsAPI(
DeviceIoControl(file_handle, IOCTL_HID_GET_FEATURE, NULL, 0,
buffer->front(), static_cast<DWORD>(buffer->size()), NULL,
transfers_.back()->GetOverlapped()));
}
void HidConnectionWin::PlatformSendFeatureReport(
scoped_refptr<base::RefCountedBytes> buffer,
WriteCallback callback) {
uint8_t report_id = buffer->data()[0];
HANDLE file_handle = GetHandleForReportId(report_id);
if (!IsValidHandle(file_handle)) {
HID_LOG(DEBUG) << "HID write failed due to invalid handle.";
std::move(callback).Run(false);
return;
}
// The Windows API always wants either a report ID (if supported) or
// zero at the front of every output report.
transfers_.push_back(std::make_unique<PendingHidTransfer>(
buffer, base::BindOnce(&HidConnectionWin::OnWriteComplete, this,
file_handle, std::move(callback))));
transfers_.back()->TakeResultFromWindowsAPI(
DeviceIoControl(file_handle, IOCTL_HID_SET_FEATURE, buffer->front(),
static_cast<DWORD>(buffer->size()), NULL, 0, NULL,
transfers_.back()->GetOverlapped()));
}
void HidConnectionWin::SetUpInitialReads() {
for (const auto& entry : file_handles_)
ReadNextInputReportOnHandle(entry->file_handle.Get());
}
void HidConnectionWin::ReadNextInputReportOnHandle(HANDLE file_handle) {
// Windows will always include the report ID (including zero if report IDs
// are not in use) in the buffer.
auto buffer = base::MakeRefCounted<base::RefCountedBytes>(
device_info()->max_input_report_size() + 1);
transfers_.push_back(std::make_unique<PendingHidTransfer>(
buffer, base::BindOnce(&HidConnectionWin::OnReadInputReport, this,
file_handle, buffer)));
transfers_.back()->TakeResultFromWindowsAPI(
ReadFile(file_handle, buffer->front(), static_cast<DWORD>(buffer->size()),
NULL, transfers_.back()->GetOverlapped()));
}
void HidConnectionWin::OnReadInputReport(
HANDLE file_handle,
scoped_refptr<base::RefCountedBytes> buffer,
PendingHidTransfer* transfer_raw,
bool signaled) {
if (!signaled) {
HID_LOG(DEBUG) << "HID read failed.";
return;
}
auto transfer = UnlinkTransfer(transfer_raw);
DWORD bytes_transferred;
if (!GetOverlappedResult(file_handle, transfer->GetOverlapped(),
&bytes_transferred, FALSE)) {
HID_PLOG(DEBUG) << "HID read failed";
return;
}
if (bytes_transferred < 1) {
HID_LOG(DEBUG) << "HID read too short.";
return;
}
uint8_t report_id = buffer->data()[0];
if (!IsReportIdProtected(report_id, HidReportType::kInput)) {
// Hold a reference to |this| to prevent a callback executed by
// ProcessInputReport from freeing this object.
scoped_refptr<HidConnection> self(this);
ProcessInputReport(buffer, bytes_transferred);
}
ReadNextInputReportOnHandle(file_handle);
}
void HidConnectionWin::OnReadFeatureComplete(
HANDLE file_handle,
scoped_refptr<base::RefCountedBytes> buffer,
ReadCallback callback,
PendingHidTransfer* transfer_raw,
bool signaled) {
if (!signaled) {
HID_LOG(DEBUG) << "HID read failed.";
std::move(callback).Run(false, nullptr, 0);
return;
}
auto transfer = UnlinkTransfer(transfer_raw);
DWORD bytes_transferred;
if (!GetOverlappedResult(file_handle, transfer->GetOverlapped(),
&bytes_transferred, FALSE)) {
HID_PLOG(DEBUG) << "HID read failed";
std::move(callback).Run(false, nullptr, 0);
return;
}
DCHECK_LE(bytes_transferred, buffer->size());
std::move(callback).Run(true, buffer, bytes_transferred);
}
void HidConnectionWin::OnWriteComplete(HANDLE file_handle,
WriteCallback callback,
PendingHidTransfer* transfer_raw,
bool signaled) {
if (!signaled) {
HID_LOG(DEBUG) << "HID write failed.";
std::move(callback).Run(false);
return;
}
auto transfer = UnlinkTransfer(transfer_raw);
DWORD bytes_transferred;
if (!GetOverlappedResult(file_handle, transfer->GetOverlapped(),
&bytes_transferred, FALSE)) {
HID_PLOG(DEBUG) << "HID write failed";
std::move(callback).Run(false);
return;
}
std::move(callback).Run(true);
}
std::unique_ptr<PendingHidTransfer> HidConnectionWin::UnlinkTransfer(
PendingHidTransfer* transfer) {
auto it = std::find_if(
transfers_.begin(), transfers_.end(),
[transfer](const std::unique_ptr<PendingHidTransfer>& this_transfer) {
return transfer == this_transfer.get();
});
DCHECK(it != transfers_.end());
std::unique_ptr<PendingHidTransfer> saved_transfer = std::move(*it);
transfers_.erase(it);
return saved_transfer;
}
HANDLE HidConnectionWin::GetHandleForReportId(uint8_t report_id) const {
for (const auto& entry : file_handles_) {
if (base::Contains(entry->report_ids, report_id))
return entry->file_handle.Get();
}
return nullptr;
}
} // namespace device