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