Upload upstream chromium 108.0.5359.1
[platform/framework/web/chromium-efl.git] / services / device / serial / serial_device_enumerator_win.cc
1 // Copyright 2014 The Chromium Authors
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/serial/serial_device_enumerator_win.h"
6
7 #include <windows.h>  // Must be in front of other Windows header files.
8
9 #define INITGUID
10 #include <devguid.h>
11 #include <devpkey.h>
12 #include <ntddser.h>
13 #include <setupapi.h>
14 #include <stdint.h>
15
16 #include <algorithm>
17 #include <memory>
18 #include <string>
19 #include <utility>
20
21 #include "base/containers/contains.h"
22 #include "base/metrics/histogram_functions.h"
23 #include "base/scoped_generic.h"
24 #include "base/scoped_observation.h"
25 #include "base/sequence_checker.h"
26 #include "base/strings/string_number_conversions.h"
27 #include "base/strings/string_piece.h"
28 #include "base/strings/string_util.h"
29 #include "base/strings/stringprintf.h"
30 #include "base/strings/sys_string_conversions.h"
31 #include "base/strings/utf_string_conversions.h"
32 #include "base/threading/scoped_blocking_call.h"
33 #include "base/win/registry.h"
34 #include "base/win/scoped_devinfo.h"
35 #include "components/device_event_log/device_event_log.h"
36 #include "third_party/re2/src/re2/re2.h"
37
38 namespace device {
39
40 namespace {
41
42 absl::optional<std::string> GetProperty(HDEVINFO dev_info,
43                                         SP_DEVINFO_DATA* dev_info_data,
44                                         const DEVPROPKEY& property) {
45   // SetupDiGetDeviceProperty() makes an RPC which may block.
46   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
47                                                 base::BlockingType::MAY_BLOCK);
48
49   DEVPROPTYPE property_type;
50   DWORD required_size;
51   if (SetupDiGetDeviceProperty(dev_info, dev_info_data, &property,
52                                &property_type, /*PropertyBuffer=*/nullptr,
53                                /*PropertyBufferSize=*/0, &required_size,
54                                /*Flags=*/0) ||
55       GetLastError() != ERROR_INSUFFICIENT_BUFFER ||
56       property_type != DEVPROP_TYPE_STRING) {
57     return absl::nullopt;
58   }
59
60   std::wstring buffer;
61   if (!SetupDiGetDeviceProperty(
62           dev_info, dev_info_data, &property, &property_type,
63           reinterpret_cast<PBYTE>(base::WriteInto(&buffer, required_size)),
64           required_size, /*RequiredSize=*/nullptr, /*Flags=*/0)) {
65     return absl::nullopt;
66   }
67
68   return base::WideToUTF8(buffer);
69 }
70
71 // Get the port name from the registry.
72 absl::optional<std::string> GetPortName(HDEVINFO dev_info,
73                                         SP_DEVINFO_DATA* dev_info_data) {
74   HKEY key = SetupDiOpenDevRegKey(dev_info, dev_info_data, DICS_FLAG_GLOBAL, 0,
75                                   DIREG_DEV, KEY_READ);
76   if (key == INVALID_HANDLE_VALUE) {
77     SERIAL_PLOG(ERROR) << "Could not open device registry key";
78     return absl::nullopt;
79   }
80   base::win::RegKey scoped_key(key);
81
82   std::wstring port_name;
83   LONG result = scoped_key.ReadValue(L"PortName", &port_name);
84   if (result != ERROR_SUCCESS) {
85     SERIAL_LOG(ERROR) << "Failed to read port name: "
86                       << logging::SystemErrorCodeToString(result);
87     return absl::nullopt;
88   }
89
90   return base::SysWideToUTF8(port_name);
91 }
92
93 // Deduce the path for the device from the port name.
94 base::FilePath GetPath(std::string port_name) {
95   // For COM numbers less than 9, CreateFile is called with a string such as
96   // "COM1". For numbers greater than 9, a prefix of "\\.\" must be added.
97   if (port_name.length() > base::StringPiece("COM9").length())
98     return base::FilePath(LR"(\\.\)").AppendASCII(port_name);
99
100   return base::FilePath::FromUTF8Unsafe(port_name);
101 }
102
103 // Searches for the display name in the device's friendly name. Returns nullopt
104 // if the name does not match the expected pattern.
105 absl::optional<std::string> GetDisplayName(const std::string& friendly_name) {
106   std::string display_name;
107   if (!RE2::PartialMatch(friendly_name, R"((.*) \(COM[0-9]+\))",
108                          &display_name)) {
109     return absl::nullopt;
110   }
111   return display_name;
112 }
113
114 // Searches for the vendor ID in the device's instance ID. Returns nullopt if
115 // the instance ID does not match the expected pattern.
116 absl::optional<uint32_t> GetVendorID(const std::string& instance_id) {
117   std::string vendor_id_str;
118   if (!RE2::PartialMatch(instance_id, "VID_([0-9a-fA-F]+)", &vendor_id_str)) {
119     return absl::nullopt;
120   }
121
122   uint32_t vendor_id;
123   if (!base::HexStringToUInt(vendor_id_str, &vendor_id)) {
124     return absl::nullopt;
125   }
126
127   return vendor_id;
128 }
129
130 // Searches for the product ID in the device's instance ID. Returns nullopt if
131 // the instance ID does not match the expected pattern.
132 absl::optional<uint32_t> GetProductID(const std::string& instance_id) {
133   std::string product_id_str;
134   if (!RE2::PartialMatch(instance_id, "PID_([0-9a-fA-F]+)", &product_id_str)) {
135     return absl::nullopt;
136   }
137
138   uint32_t product_id;
139   if (!base::HexStringToUInt(product_id_str, &product_id)) {
140     return absl::nullopt;
141   }
142
143   return product_id;
144 }
145
146 }  // namespace
147
148 class SerialDeviceEnumeratorWin::UiThreadHelper
149     : public DeviceMonitorWin::Observer {
150  public:
151   UiThreadHelper() : task_runner_(base::SequencedTaskRunnerHandle::Get()) {
152     DETACH_FROM_SEQUENCE(sequence_checker_);
153   }
154
155   // Disallow copy and assignment.
156   UiThreadHelper(UiThreadHelper&) = delete;
157   UiThreadHelper& operator=(UiThreadHelper&) = delete;
158
159   virtual ~UiThreadHelper() {
160     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
161   }
162
163   void Initialize(base::WeakPtr<SerialDeviceEnumeratorWin> enumerator) {
164     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
165     enumerator_ = std::move(enumerator);
166     // Note that this uses GUID_DEVINTERFACE_COMPORT even though we use
167     // GUID_DEVINTERFACE_SERENUM_BUS_ENUMERATOR for enumeration because it
168     // doesn't seem to make a difference and ports which aren't enumerable by
169     // device interface don't generate WM_DEVICECHANGE events.
170     device_observation_.Observe(
171         DeviceMonitorWin::GetForDeviceInterface(GUID_DEVINTERFACE_COMPORT));
172   }
173
174   void OnDeviceAdded(const GUID& class_guid,
175                      const std::wstring& device_path) override {
176     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
177     task_runner_->PostTask(
178         FROM_HERE, base::BindOnce(&SerialDeviceEnumeratorWin::OnPathAdded,
179                                   enumerator_, device_path));
180   }
181
182   void OnDeviceRemoved(const GUID& class_guid,
183                        const std::wstring& device_path) override {
184     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
185     task_runner_->PostTask(
186         FROM_HERE, base::BindOnce(&SerialDeviceEnumeratorWin::OnPathRemoved,
187                                   enumerator_, device_path));
188   }
189
190  private:
191   SEQUENCE_CHECKER(sequence_checker_);
192
193   // Weak reference to the SerialDeviceEnumeratorWin that owns this object.
194   // Calls on |enumerator_| must be posted to |task_runner_|.
195   base::WeakPtr<SerialDeviceEnumeratorWin> enumerator_;
196   scoped_refptr<base::SequencedTaskRunner> task_runner_;
197
198   base::ScopedObservation<DeviceMonitorWin, DeviceMonitorWin::Observer>
199       device_observation_{this};
200 };
201
202 SerialDeviceEnumeratorWin::SerialDeviceEnumeratorWin(
203     scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner)
204     : helper_(new UiThreadHelper(), base::OnTaskRunnerDeleter(ui_task_runner)) {
205   // Passing a raw pointer to |helper_| is safe here because this task will
206   // reach the UI thread before any task to delete |helper_|.
207   ui_task_runner->PostTask(FROM_HERE,
208                            base::BindOnce(&UiThreadHelper::Initialize,
209                                           base::Unretained(helper_.get()),
210                                           weak_factory_.GetWeakPtr()));
211
212   DoInitialEnumeration();
213 }
214
215 SerialDeviceEnumeratorWin::~SerialDeviceEnumeratorWin() = default;
216
217 void SerialDeviceEnumeratorWin::OnPathAdded(const std::wstring& device_path) {
218   base::win::ScopedDevInfo dev_info(
219       SetupDiCreateDeviceInfoList(nullptr, nullptr));
220   if (!dev_info.is_valid())
221     return;
222
223   if (!SetupDiOpenDeviceInterface(dev_info.get(), device_path.c_str(), 0,
224                                   nullptr)) {
225     return;
226   }
227
228   SP_DEVINFO_DATA dev_info_data = {};
229   dev_info_data.cbSize = sizeof(dev_info_data);
230   if (!SetupDiEnumDeviceInfo(dev_info.get(), 0, &dev_info_data))
231     return;
232
233   EnumeratePort(dev_info.get(), &dev_info_data, /*check_port_name=*/false);
234 }
235
236 void SerialDeviceEnumeratorWin::OnPathRemoved(const std::wstring& device_path) {
237   base::win::ScopedDevInfo dev_info(
238       SetupDiCreateDeviceInfoList(nullptr, nullptr));
239   if (!dev_info.is_valid())
240     return;
241
242   if (!SetupDiOpenDeviceInterface(dev_info.get(), device_path.c_str(), 0,
243                                   nullptr)) {
244     return;
245   }
246
247   SP_DEVINFO_DATA dev_info_data = {};
248   dev_info_data.cbSize = sizeof(dev_info_data);
249   if (!SetupDiEnumDeviceInfo(dev_info.get(), 0, &dev_info_data))
250     return;
251
252   absl::optional<std::string> port_name =
253       GetPortName(dev_info.get(), &dev_info_data);
254   if (!port_name)
255     return;
256
257   auto it = paths_.find(GetPath(*port_name));
258   if (it == paths_.end())
259     return;
260
261   base::UnguessableToken token = it->second;
262
263   paths_.erase(it);
264   RemovePort(token);
265 }
266
267 void SerialDeviceEnumeratorWin::DoInitialEnumeration() {
268   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
269                                                 base::BlockingType::MAY_BLOCK);
270
271   // On Windows 10 and above most COM port drivers register using the COMPORT
272   // device interface class. Try to enumerate these first.
273   {
274     base::win::ScopedDevInfo dev_info;
275     dev_info.reset(SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT, nullptr, 0,
276                                        DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
277     if (!dev_info.is_valid())
278       return;
279
280     SP_DEVINFO_DATA dev_info_data = {.cbSize = sizeof(dev_info_data)};
281     for (DWORD i = 0; SetupDiEnumDeviceInfo(dev_info.get(), i, &dev_info_data);
282          i++) {
283       EnumeratePort(dev_info.get(), &dev_info_data, /*check_port_name=*/false);
284     }
285   }
286
287   // To detect devices which don't register with GUID_DEVINTERFACE_COMPORT also
288   // enuerate all devices in the "Ports" and "Modems" device classes. These must
289   // be checked to see if the port name starts with "COM" because it also
290   // includes LPT ports.
291   constexpr const GUID* kDeviceClasses[] = {&GUID_DEVCLASS_MODEM,
292                                             &GUID_DEVCLASS_PORTS};
293   for (const GUID* guid : kDeviceClasses) {
294     base::win::ScopedDevInfo dev_info;
295     dev_info.reset(SetupDiGetClassDevs(guid, nullptr, 0, DIGCF_PRESENT));
296     if (!dev_info.is_valid())
297       return;
298
299     SP_DEVINFO_DATA dev_info_data = {.cbSize = sizeof(dev_info_data)};
300     for (DWORD i = 0; SetupDiEnumDeviceInfo(dev_info.get(), i, &dev_info_data);
301          i++) {
302       EnumeratePort(dev_info.get(), &dev_info_data, /*check_port_name=*/true);
303     }
304   }
305 }
306
307 void SerialDeviceEnumeratorWin::EnumeratePort(HDEVINFO dev_info,
308                                               SP_DEVINFO_DATA* dev_info_data,
309                                               bool check_port_name) {
310   absl::optional<std::string> port_name = GetPortName(dev_info, dev_info_data);
311   if (!port_name)
312     return;
313
314   if (check_port_name && !base::StartsWith(*port_name, "COM"))
315     return;
316
317   // Check whether the currently enumerating port has been seen before since
318   // the method above will generate duplicate enumerations for some ports.
319   base::FilePath path = GetPath(*port_name);
320   if (base::Contains(paths_, path))
321     return;
322
323   absl::optional<std::string> instance_id =
324       GetProperty(dev_info, dev_info_data, DEVPKEY_Device_InstanceId);
325   if (!instance_id)
326     return;
327
328   // Some versions of Windows pad this string with a variable number of NUL
329   // bytes for no discernible reason.
330   instance_id = std::string(base::TrimString(
331       *instance_id, base::StringPiece("\0", 1), base::TRIM_TRAILING));
332
333   base::UnguessableToken token = base::UnguessableToken::Create();
334   auto info = mojom::SerialPortInfo::New();
335   info->token = token;
336   info->path = path;
337   info->device_instance_id = *instance_id;
338
339   // TODO(https://crbug.com/1015074): While the "bus reported device
340   // description" is usually the USB product string this is still up to the
341   // individual serial driver and could be equal to the "friendly name". It
342   // would be more reliable to read the real USB strings here.
343   info->display_name = GetProperty(dev_info, dev_info_data,
344                                    DEVPKEY_Device_BusReportedDeviceDesc);
345   if (info->display_name) {
346     // This string is also sometimes padded with a variable number of NUL bytes
347     // for no discernible reason.
348     info->display_name = std::string(base::TrimString(
349         *info->display_name, base::StringPiece("\0", 1), base::TRIM_TRAILING));
350   } else {
351     // Fall back to the "friendly name" if no "bus reported device description"
352     // is available. This name will likely be the same for all devices using the
353     // same driver.
354     absl::optional<std::string> friendly_name =
355         GetProperty(dev_info, dev_info_data, DEVPKEY_Device_FriendlyName);
356     if (!friendly_name)
357       return;
358
359     info->display_name = GetDisplayName(*friendly_name);
360   }
361
362   // The instance ID looks like "FTDIBUS\VID_0403+PID_6001+A703X87GA\0000".
363   absl::optional<uint32_t> vendor_id = GetVendorID(*instance_id);
364   absl::optional<uint32_t> product_id = GetProductID(*instance_id);
365   absl::optional<std::string> vendor_id_str, product_id_str;
366   if (vendor_id) {
367     info->has_vendor_id = true;
368     info->vendor_id = *vendor_id;
369     vendor_id_str = base::StringPrintf("%04X", *vendor_id);
370   }
371   if (product_id) {
372     info->has_product_id = true;
373     info->product_id = *product_id;
374     product_id_str = base::StringPrintf("%04X", *product_id);
375   }
376
377   SERIAL_LOG(EVENT) << "Serial device added: path=" << info->path
378                     << " instance_id=" << info->device_instance_id
379                     << " vid=" << vendor_id_str.value_or("(none)")
380                     << " pid=" << product_id_str.value_or("(none)");
381
382   paths_.insert(std::make_pair(path, token));
383   AddPort(std::move(info));
384 }
385
386 }  // namespace device