blob: 7855dc8e8cfbf3d463df271400080044153a2a66 [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 "device/hid/hid_connection_win.h"
#include <cstring>
#include <utility>
#include "base/bind.h"
#include "base/files/file.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.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 <windows.h>
#include <hidclass.h>
extern "C" {
#include <hidsdi.h>
}
#include <setupapi.h>
#include <winioctl.h>
namespace device {
struct PendingHidTransfer : public base::RefCounted<PendingHidTransfer>,
public base::win::ObjectWatcher::Delegate,
public base::MessageLoop::DestructionObserver {
typedef base::Callback<void(PendingHidTransfer*, bool)> Callback;
PendingHidTransfer(scoped_refptr<net::IOBuffer> buffer,
const Callback& callback);
void TakeResultFromWindowsAPI(BOOL result);
OVERLAPPED* GetOverlapped() { return &overlapped_; }
// Implements base::win::ObjectWatcher::Delegate.
void OnObjectSignaled(HANDLE object) override;
// Implements base::MessageLoop::DestructionObserver
void WillDestroyCurrentMessageLoop() override;
// 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<net::IOBuffer> buffer_;
Callback callback_;
OVERLAPPED overlapped_;
base::win::ScopedHandle event_;
base::win::ObjectWatcher watcher_;
private:
friend class base::RefCounted<PendingHidTransfer>;
~PendingHidTransfer() override;
DISALLOW_COPY_AND_ASSIGN(PendingHidTransfer);
};
PendingHidTransfer::PendingHidTransfer(
scoped_refptr<net::IOBuffer> buffer,
const PendingHidTransfer::Callback& callback)
: buffer_(buffer),
callback_(callback),
event_(CreateEvent(NULL, FALSE, FALSE, NULL)) {
memset(&overlapped_, 0, sizeof(OVERLAPPED));
overlapped_.hEvent = event_.Get();
}
PendingHidTransfer::~PendingHidTransfer() {
base::MessageLoop::current()->RemoveDestructionObserver(this);
}
void PendingHidTransfer::TakeResultFromWindowsAPI(BOOL result) {
if (result) {
callback_.Run(this, true);
} else if (GetLastError() == ERROR_IO_PENDING) {
base::MessageLoop::current()->AddDestructionObserver(this);
AddRef();
watcher_.StartWatchingOnce(event_.Get(), this);
} else {
HID_PLOG(EVENT) << "HID transfer failed";
callback_.Run(this, false);
}
}
void PendingHidTransfer::OnObjectSignaled(HANDLE event_handle) {
callback_.Run(this, true);
Release();
}
void PendingHidTransfer::WillDestroyCurrentMessageLoop() {
watcher_.StopWatching();
callback_.Run(this, false);
}
HidConnectionWin::HidConnectionWin(scoped_refptr<HidDeviceInfo> device_info,
base::win::ScopedHandle file)
: HidConnection(device_info) {
file_ = std::move(file);
}
HidConnectionWin::~HidConnectionWin() {
}
void HidConnectionWin::PlatformClose() {
CancelIo(file_.Get());
}
void HidConnectionWin::PlatformRead(
const HidConnection::ReadCallback& callback) {
// Windows will always include the report ID (including zero if report IDs
// are not in use) in the buffer.
scoped_refptr<net::IOBufferWithSize> buffer = new net::IOBufferWithSize(
base::checked_cast<int>(device_info()->max_input_report_size() + 1));
scoped_refptr<PendingHidTransfer> transfer(new PendingHidTransfer(
buffer,
base::Bind(&HidConnectionWin::OnReadComplete, this, buffer, callback)));
transfers_.insert(transfer);
transfer->TakeResultFromWindowsAPI(
ReadFile(file_.Get(),
buffer->data(),
static_cast<DWORD>(buffer->size()),
NULL,
transfer->GetOverlapped()));
}
void HidConnectionWin::PlatformWrite(scoped_refptr<net::IOBuffer> buffer,
size_t size,
const WriteCallback& callback) {
size_t expected_size = device_info()->max_output_report_size() + 1;
DCHECK(size <= expected_size);
// 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.
if (size < expected_size) {
scoped_refptr<net::IOBuffer> tmp_buffer = new net::IOBuffer(
base::checked_cast<int>(expected_size));
memcpy(tmp_buffer->data(), buffer->data(), size);
memset(tmp_buffer->data() + size, 0, expected_size - size);
buffer = tmp_buffer;
size = expected_size;
}
scoped_refptr<PendingHidTransfer> transfer(new PendingHidTransfer(
buffer, base::Bind(&HidConnectionWin::OnWriteComplete, this, callback)));
transfers_.insert(transfer);
transfer->TakeResultFromWindowsAPI(WriteFile(file_.Get(),
buffer->data(),
static_cast<DWORD>(size),
NULL,
transfer->GetOverlapped()));
}
void HidConnectionWin::PlatformGetFeatureReport(uint8_t report_id,
const ReadCallback& callback) {
// The first byte of the destination buffer is the report ID being requested.
scoped_refptr<net::IOBufferWithSize> buffer = new net::IOBufferWithSize(
base::checked_cast<int>(device_info()->max_feature_report_size() + 1));
buffer->data()[0] = report_id;
scoped_refptr<PendingHidTransfer> transfer(new PendingHidTransfer(
buffer,
base::Bind(
&HidConnectionWin::OnReadFeatureComplete, this, buffer, callback)));
transfers_.insert(transfer);
transfer->TakeResultFromWindowsAPI(
DeviceIoControl(file_.Get(),
IOCTL_HID_GET_FEATURE,
NULL,
0,
buffer->data(),
static_cast<DWORD>(buffer->size()),
NULL,
transfer->GetOverlapped()));
}
void HidConnectionWin::PlatformSendFeatureReport(
scoped_refptr<net::IOBuffer> buffer,
size_t size,
const WriteCallback& callback) {
// The Windows API always wants either a report ID (if supported) or
// zero at the front of every output report.
scoped_refptr<PendingHidTransfer> transfer(new PendingHidTransfer(
buffer, base::Bind(&HidConnectionWin::OnWriteComplete, this, callback)));
transfer->TakeResultFromWindowsAPI(
DeviceIoControl(file_.Get(),
IOCTL_HID_SET_FEATURE,
buffer->data(),
static_cast<DWORD>(size),
NULL,
0,
NULL,
transfer->GetOverlapped()));
}
void HidConnectionWin::OnReadComplete(scoped_refptr<net::IOBuffer> buffer,
const ReadCallback& callback,
PendingHidTransfer* transfer,
bool signaled) {
if (!signaled) {
callback.Run(false, NULL, 0);
return;
}
DWORD bytes_transferred;
if (GetOverlappedResult(
file_.Get(), transfer->GetOverlapped(), &bytes_transferred, FALSE)) {
CompleteRead(buffer, bytes_transferred, callback);
} else {
HID_PLOG(EVENT) << "HID read failed";
callback.Run(false, NULL, 0);
}
}
void HidConnectionWin::OnReadFeatureComplete(
scoped_refptr<net::IOBuffer> buffer,
const ReadCallback& callback,
PendingHidTransfer* transfer,
bool signaled) {
if (!signaled) {
callback.Run(false, NULL, 0);
return;
}
DWORD bytes_transferred;
if (GetOverlappedResult(
file_.Get(), transfer->GetOverlapped(), &bytes_transferred, FALSE)) {
callback.Run(true, buffer, bytes_transferred);
} else {
HID_PLOG(EVENT) << "HID read failed";
callback.Run(false, NULL, 0);
}
}
void HidConnectionWin::OnWriteComplete(const WriteCallback& callback,
PendingHidTransfer* transfer,
bool signaled) {
if (!signaled) {
callback.Run(false);
return;
}
DWORD bytes_transferred;
if (GetOverlappedResult(
file_.Get(), transfer->GetOverlapped(), &bytes_transferred, FALSE)) {
callback.Run(true);
} else {
HID_PLOG(EVENT) << "HID write failed";
callback.Run(false);
}
}
} // namespace device