1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "services/device/hid/hid_service_win.h"
6
7 #define INITGUID
8
9 #include <dbt.h>
10 #include <devpkey.h>
11 #include <setupapi.h>
12 #include <stddef.h>
13 #include <wdmguid.h>
14 #include <winioctl.h>
15
16 #include <memory>
17 #include <utility>
18
19 #include "base/bind.h"
20 #include "base/callback_helpers.h"
21 #include "base/files/file.h"
22 #include "base/location.h"
23 #include "base/memory/free_deleter.h"
24 #include "base/sequenced_task_runner.h"
25 #include "base/strings/string_util.h"
26 #include "base/strings/utf_string_conversions.h"
27 #include "base/task/thread_pool.h"
28 #include "base/threading/sequenced_task_runner_handle.h"
29 #include "base/win/scoped_devinfo.h"
30 #include "base/win/win_util.h"
31 #include "components/device_event_log/device_event_log.h"
32 #include "services/device/hid/hid_connection_win.h"
33 #include "services/device/hid/hid_device_info.h"
34
35 namespace device {
36
37 namespace {
38
39 // Looks up the value of a GUID-type device property specified by |property| for
40 // the device described by |device_info_data|. On success, returns true and sets
41 // |property_buffer| to the property value. Returns false if the property is not
42 // present or has a different type.
GetDeviceGuidProperty(HDEVINFO device_info_set,SP_DEVINFO_DATA & device_info_data,const DEVPROPKEY & property,GUID * property_buffer)43 bool GetDeviceGuidProperty(HDEVINFO device_info_set,
44 SP_DEVINFO_DATA& device_info_data,
45 const DEVPROPKEY& property,
46 GUID* property_buffer) {
47 DEVPROPTYPE property_type;
48 if (!SetupDiGetDeviceProperty(
49 device_info_set, &device_info_data, &property, &property_type,
50 reinterpret_cast<PBYTE>(property_buffer), sizeof(*property_buffer),
51 /*RequiredSize=*/nullptr, /*Flags=*/0) ||
52 property_type != DEVPROP_TYPE_GUID) {
53 return false;
54 }
55 return true;
56 }
57
58 // Looks up information about the device described by |device_interface_data|
59 // in |device_info_set|. On success, returns true and sets |device_info_data|
60 // and |device_path|. Returns false if an error occurred.
GetDeviceInfoAndPathFromInterface(HDEVINFO device_info_set,SP_DEVICE_INTERFACE_DATA & device_interface_data,SP_DEVINFO_DATA * device_info_data,std::wstring * device_path)61 bool GetDeviceInfoAndPathFromInterface(
62 HDEVINFO device_info_set,
63 SP_DEVICE_INTERFACE_DATA& device_interface_data,
64 SP_DEVINFO_DATA* device_info_data,
65 std::wstring* device_path) {
66 // Get the required buffer size. When called with
67 // DeviceInterfaceDetailData == nullptr and DeviceInterfaceDetailSize == 0,
68 // SetupDiGetDeviceInterfaceDetail returns the required buffer size at
69 // RequiredSize and fails with GetLastError() == ERROR_INSUFFICIENT_BUFFER.
70 DWORD required_size;
71 if (SetupDiGetDeviceInterfaceDetail(device_info_set, &device_interface_data,
72 /*DeviceInterfaceDetailData=*/nullptr,
73 /*DeviceInterfaceDetailSize=*/0,
74 &required_size,
75 /*DeviceInfoData=*/nullptr) ||
76 GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
77 return false;
78 }
79
80 std::unique_ptr<SP_DEVICE_INTERFACE_DETAIL_DATA, base::FreeDeleter>
81 device_interface_detail_data(
82 static_cast<SP_DEVICE_INTERFACE_DETAIL_DATA*>(malloc(required_size)));
83 device_interface_detail_data->cbSize = sizeof(*device_interface_detail_data);
84
85 // Call the function again with the correct buffer size to get the detailed
86 // data for this device.
87 if (!SetupDiGetDeviceInterfaceDetail(device_info_set, &device_interface_data,
88 device_interface_detail_data.get(),
89 required_size, /*RequiredSize=*/nullptr,
90 device_info_data)) {
91 return false;
92 }
93
94 // Windows uses case-insensitive paths and may return paths that differ only
95 // by case. Canonicalize the device path by converting to lowercase.
96 std::wstring path = device_interface_detail_data->DevicePath;
97 DCHECK(base::IsStringASCII(path));
98 *device_path = base::ToLowerASCII(path);
99 return true;
100 }
101
102 // Returns a device info set containing only the device described by
103 // |device_path|, or an invalid ScopedDevInfo if there was an error while
104 // creating the device set. The device info is returned in |device_info_data|.
GetDeviceInfoFromPath(const std::wstring & device_path,SP_DEVINFO_DATA * device_info_data)105 base::win::ScopedDevInfo GetDeviceInfoFromPath(
106 const std::wstring& device_path,
107 SP_DEVINFO_DATA* device_info_data) {
108 base::win::ScopedDevInfo device_info_set(SetupDiGetClassDevs(
109 &GUID_DEVINTERFACE_HID, /*Enumerator=*/nullptr,
110 /*hwndParent=*/0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT));
111 if (!device_info_set.is_valid())
112 return base::win::ScopedDevInfo();
113
114 SP_DEVICE_INTERFACE_DATA device_interface_data;
115 device_interface_data.cbSize = sizeof(device_interface_data);
116 if (!SetupDiOpenDeviceInterface(device_info_set.get(), device_path.c_str(),
117 /*OpenFlags=*/0, &device_interface_data)) {
118 return base::win::ScopedDevInfo();
119 }
120
121 std::wstring intf_device_path;
122 GetDeviceInfoAndPathFromInterface(device_info_set.get(),
123 device_interface_data, device_info_data,
124 &intf_device_path);
125 DCHECK_EQ(intf_device_path, device_path);
126 return device_info_set;
127 }
128
129 } // namespace
130
HidServiceWin()131 HidServiceWin::HidServiceWin()
132 : task_runner_(base::SequencedTaskRunnerHandle::Get()),
133 blocking_task_runner_(
134 base::ThreadPool::CreateSequencedTaskRunner(kBlockingTaskTraits)),
135 device_observer_(this) {
136 DeviceMonitorWin* device_monitor =
137 DeviceMonitorWin::GetForDeviceInterface(GUID_DEVINTERFACE_HID);
138 if (device_monitor)
139 device_observer_.Add(device_monitor);
140
141 blocking_task_runner_->PostTask(
142 FROM_HERE, base::BindOnce(&HidServiceWin::EnumerateBlocking,
143 weak_factory_.GetWeakPtr(), task_runner_));
144 }
145
~HidServiceWin()146 HidServiceWin::~HidServiceWin() {}
147
Connect(const std::string & device_guid,ConnectCallback callback)148 void HidServiceWin::Connect(const std::string& device_guid,
149 ConnectCallback callback) {
150 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
151 const auto& map_entry = devices().find(device_guid);
152 if (map_entry == devices().end()) {
153 task_runner_->PostTask(FROM_HERE,
154 base::BindOnce(std::move(callback), nullptr));
155 return;
156 }
157 scoped_refptr<HidDeviceInfo> device_info = map_entry->second;
158
159 base::win::ScopedHandle file(OpenDevice(device_info->platform_device_id()));
160 if (!file.IsValid()) {
161 HID_PLOG(EVENT) << "Failed to open device";
162 task_runner_->PostTask(FROM_HERE,
163 base::BindOnce(std::move(callback), nullptr));
164 return;
165 }
166
167 task_runner_->PostTask(
168 FROM_HERE,
169 base::BindOnce(std::move(callback),
170 HidConnectionWin::Create(device_info, std::move(file))));
171 }
172
GetWeakPtr()173 base::WeakPtr<HidService> HidServiceWin::GetWeakPtr() {
174 return weak_factory_.GetWeakPtr();
175 }
176
177 // static
EnumerateBlocking(base::WeakPtr<HidServiceWin> service,scoped_refptr<base::SequencedTaskRunner> task_runner)178 void HidServiceWin::EnumerateBlocking(
179 base::WeakPtr<HidServiceWin> service,
180 scoped_refptr<base::SequencedTaskRunner> task_runner) {
181 base::win::ScopedDevInfo dev_info(SetupDiGetClassDevs(
182 &GUID_DEVINTERFACE_HID, /*Enumerator=*/nullptr,
183 /*hwndParent=*/nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
184
185 if (dev_info.is_valid()) {
186 SP_DEVICE_INTERFACE_DATA device_interface_data = {0};
187 device_interface_data.cbSize = sizeof(device_interface_data);
188
189 for (int device_index = 0; SetupDiEnumDeviceInterfaces(
190 dev_info.get(), /*DeviceInfoData=*/nullptr, &GUID_DEVINTERFACE_HID,
191 device_index, &device_interface_data);
192 ++device_index) {
193 SP_DEVINFO_DATA dev_info_data = {0};
194 dev_info_data.cbSize = sizeof(dev_info_data);
195 std::wstring device_path;
196 if (!GetDeviceInfoAndPathFromInterface(dev_info.get(),
197 device_interface_data,
198 &dev_info_data, &device_path)) {
199 continue;
200 }
201
202 // Get the container ID for the physical device.
203 GUID container_id;
204 if (!GetDeviceGuidProperty(dev_info.get(), dev_info_data,
205 DEVPKEY_Device_ContainerId, &container_id)) {
206 continue;
207 }
208 std::string physical_device_id =
209 base::WideToUTF8(base::win::WStringFromGUID(container_id));
210
211 AddDeviceBlocking(service, task_runner, device_path, physical_device_id);
212 }
213 }
214
215 task_runner->PostTask(
216 FROM_HERE,
217 base::BindOnce(&HidServiceWin::FirstEnumerationComplete, service));
218 }
219
220 // static
CollectInfoFromButtonCaps(PHIDP_PREPARSED_DATA preparsed_data,HIDP_REPORT_TYPE report_type,USHORT button_caps_length,mojom::HidCollectionInfo * collection_info)221 void HidServiceWin::CollectInfoFromButtonCaps(
222 PHIDP_PREPARSED_DATA preparsed_data,
223 HIDP_REPORT_TYPE report_type,
224 USHORT button_caps_length,
225 mojom::HidCollectionInfo* collection_info) {
226 if (button_caps_length > 0) {
227 std::unique_ptr<HIDP_BUTTON_CAPS[]> button_caps(
228 new HIDP_BUTTON_CAPS[button_caps_length]);
229 if (HidP_GetButtonCaps(report_type, &button_caps[0], &button_caps_length,
230 preparsed_data) == HIDP_STATUS_SUCCESS) {
231 for (size_t i = 0; i < button_caps_length; i++) {
232 int report_id = button_caps[i].ReportID;
233 if (report_id != 0) {
234 collection_info->report_ids.push_back(report_id);
235 }
236 }
237 }
238 }
239 }
240
241 // static
CollectInfoFromValueCaps(PHIDP_PREPARSED_DATA preparsed_data,HIDP_REPORT_TYPE report_type,USHORT value_caps_length,mojom::HidCollectionInfo * collection_info)242 void HidServiceWin::CollectInfoFromValueCaps(
243 PHIDP_PREPARSED_DATA preparsed_data,
244 HIDP_REPORT_TYPE report_type,
245 USHORT value_caps_length,
246 mojom::HidCollectionInfo* collection_info) {
247 if (value_caps_length > 0) {
248 std::unique_ptr<HIDP_VALUE_CAPS[]> value_caps(
249 new HIDP_VALUE_CAPS[value_caps_length]);
250 if (HidP_GetValueCaps(report_type, &value_caps[0], &value_caps_length,
251 preparsed_data) == HIDP_STATUS_SUCCESS) {
252 for (size_t i = 0; i < value_caps_length; i++) {
253 int report_id = value_caps[i].ReportID;
254 if (report_id != 0) {
255 collection_info->report_ids.push_back(report_id);
256 }
257 }
258 }
259 }
260 }
261
262 // static
AddDeviceBlocking(base::WeakPtr<HidServiceWin> service,scoped_refptr<base::SequencedTaskRunner> task_runner,const std::wstring & device_path,const std::string & physical_device_id)263 void HidServiceWin::AddDeviceBlocking(
264 base::WeakPtr<HidServiceWin> service,
265 scoped_refptr<base::SequencedTaskRunner> task_runner,
266 const std::wstring& device_path,
267 const std::string& physical_device_id) {
268 base::win::ScopedHandle device_handle(OpenDevice(device_path));
269 if (!device_handle.IsValid()) {
270 return;
271 }
272
273 HIDD_ATTRIBUTES attrib = {0};
274 attrib.Size = sizeof(attrib);
275 if (!HidD_GetAttributes(device_handle.Get(), &attrib)) {
276 HID_LOG(EVENT) << "Failed to get device attributes.";
277 return;
278 }
279
280 PHIDP_PREPARSED_DATA preparsed_data = nullptr;
281 if (!HidD_GetPreparsedData(device_handle.Get(), &preparsed_data) ||
282 !preparsed_data) {
283 HID_LOG(EVENT) << "Failed to get device data.";
284 return;
285 }
286
287 HIDP_CAPS capabilities = {0};
288 if (HidP_GetCaps(preparsed_data, &capabilities) != HIDP_STATUS_SUCCESS) {
289 HID_LOG(EVENT) << "Failed to get device capabilities.";
290 HidD_FreePreparsedData(preparsed_data);
291 return;
292 }
293
294 // Whether or not the device includes report IDs in its reports the size
295 // of the report ID is included in the value provided by Windows. This
296 // appears contrary to the MSDN documentation.
297 size_t max_input_report_size = 0;
298 size_t max_output_report_size = 0;
299 size_t max_feature_report_size = 0;
300 if (capabilities.InputReportByteLength > 0) {
301 max_input_report_size = capabilities.InputReportByteLength - 1;
302 }
303 if (capabilities.OutputReportByteLength > 0) {
304 max_output_report_size = capabilities.OutputReportByteLength - 1;
305 }
306 if (capabilities.FeatureReportByteLength > 0) {
307 max_feature_report_size = capabilities.FeatureReportByteLength - 1;
308 }
309
310 auto collection_info = mojom::HidCollectionInfo::New();
311 collection_info->usage =
312 mojom::HidUsageAndPage::New(capabilities.Usage, capabilities.UsagePage);
313 CollectInfoFromButtonCaps(preparsed_data, HidP_Input,
314 capabilities.NumberInputButtonCaps,
315 collection_info.get());
316 CollectInfoFromButtonCaps(preparsed_data, HidP_Output,
317 capabilities.NumberOutputButtonCaps,
318 collection_info.get());
319 CollectInfoFromButtonCaps(preparsed_data, HidP_Feature,
320 capabilities.NumberFeatureButtonCaps,
321 collection_info.get());
322 CollectInfoFromValueCaps(preparsed_data, HidP_Input,
323 capabilities.NumberInputValueCaps,
324 collection_info.get());
325 CollectInfoFromValueCaps(preparsed_data, HidP_Output,
326 capabilities.NumberOutputValueCaps,
327 collection_info.get());
328 CollectInfoFromValueCaps(preparsed_data, HidP_Feature,
329 capabilities.NumberFeatureValueCaps,
330 collection_info.get());
331
332 // 1023 characters plus NULL terminator is more than enough for a USB string
333 // descriptor which is limited to 126 characters.
334 base::char16 buffer[1024];
335 std::string product_name;
336 if (HidD_GetProductString(device_handle.Get(), &buffer[0], sizeof(buffer))) {
337 // NULL termination guaranteed by the API.
338 product_name = base::UTF16ToUTF8(buffer);
339 }
340 std::string serial_number;
341 if (HidD_GetSerialNumberString(device_handle.Get(), &buffer[0],
342 sizeof(buffer))) {
343 // NULL termination guaranteed by the API.
344 serial_number = base::UTF16ToUTF8(buffer);
345 }
346
347 // This populates the HidDeviceInfo instance without a raw report descriptor.
348 // The descriptor is unavailable on Windows because HID devices are exposed to
349 // user-space as individual top-level collections.
350 scoped_refptr<HidDeviceInfo> device_info(new HidDeviceInfo(
351 device_path, physical_device_id, attrib.VendorID, attrib.ProductID,
352 product_name, serial_number,
353 // TODO(reillyg): Detect Bluetooth. crbug.com/443335
354 mojom::HidBusType::kHIDBusTypeUSB, std::move(collection_info),
355 max_input_report_size, max_output_report_size, max_feature_report_size));
356
357 HidD_FreePreparsedData(preparsed_data);
358 task_runner->PostTask(FROM_HERE, base::BindOnce(&HidServiceWin::AddDevice,
359 service, device_info));
360 }
361
OnDeviceAdded(const GUID & class_guid,const std::wstring & device_path)362 void HidServiceWin::OnDeviceAdded(const GUID& class_guid,
363 const std::wstring& device_path) {
364 SP_DEVINFO_DATA device_info_data = {0};
365 device_info_data.cbSize = sizeof(device_info_data);
366 auto device_info_set = GetDeviceInfoFromPath(device_path, &device_info_data);
367 if (!device_info_set.is_valid())
368 return;
369
370 GUID container_id;
371 if (!GetDeviceGuidProperty(device_info_set.get(), device_info_data,
372 DEVPKEY_Device_ContainerId, &container_id)) {
373 return;
374 }
375 std::string physical_device_id =
376 base::WideToUTF8(base::win::WStringFromGUID(container_id));
377
378 blocking_task_runner_->PostTask(
379 FROM_HERE, base::BindOnce(&HidServiceWin::AddDeviceBlocking,
380 weak_factory_.GetWeakPtr(), task_runner_,
381 device_path, physical_device_id));
382 }
383
OnDeviceRemoved(const GUID & class_guid,const std::wstring & device_path)384 void HidServiceWin::OnDeviceRemoved(const GUID& class_guid,
385 const std::wstring& device_path) {
386 // Execute a no-op closure on the file task runner to synchronize with any
387 // devices that are still being enumerated.
388 blocking_task_runner_->PostTaskAndReply(
389 FROM_HERE, base::DoNothing(),
390 base::BindOnce(&HidServiceWin::RemoveDevice, weak_factory_.GetWeakPtr(),
391 device_path));
392 }
393
394 // static
OpenDevice(const std::wstring & device_path)395 base::win::ScopedHandle HidServiceWin::OpenDevice(
396 const std::wstring& device_path) {
397 base::win::ScopedHandle file(
398 CreateFile(device_path.c_str(), GENERIC_WRITE | GENERIC_READ,
399 FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
400 FILE_FLAG_OVERLAPPED, nullptr));
401 if (!file.IsValid() && GetLastError() == ERROR_ACCESS_DENIED) {
402 file.Set(CreateFile(device_path.c_str(), GENERIC_READ, FILE_SHARE_READ,
403 nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr));
404 }
405 return file;
406 }
407
408 } // namespace device
409