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.
5 #include "services/device/serial/serial_device_enumerator_win.h"
7 #include <windows.h> // Must be in front of other Windows header files.
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"
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);
49 DEVPROPTYPE property_type;
51 if (SetupDiGetDeviceProperty(dev_info, dev_info_data, &property,
52 &property_type, /*PropertyBuffer=*/nullptr,
53 /*PropertyBufferSize=*/0, &required_size,
55 GetLastError() != ERROR_INSUFFICIENT_BUFFER ||
56 property_type != DEVPROP_TYPE_STRING) {
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)) {
68 return base::WideToUTF8(buffer);
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,
76 if (key == INVALID_HANDLE_VALUE) {
77 SERIAL_PLOG(ERROR) << "Could not open device registry key";
80 base::win::RegKey scoped_key(key);
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);
90 return base::SysWideToUTF8(port_name);
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);
100 return base::FilePath::FromUTF8Unsafe(port_name);
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]+\))",
109 return absl::nullopt;
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;
123 if (!base::HexStringToUInt(vendor_id_str, &vendor_id)) {
124 return absl::nullopt;
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;
139 if (!base::HexStringToUInt(product_id_str, &product_id)) {
140 return absl::nullopt;
148 class SerialDeviceEnumeratorWin::UiThreadHelper
149 : public DeviceMonitorWin::Observer {
151 UiThreadHelper() : task_runner_(base::SequencedTaskRunnerHandle::Get()) {
152 DETACH_FROM_SEQUENCE(sequence_checker_);
155 // Disallow copy and assignment.
156 UiThreadHelper(UiThreadHelper&) = delete;
157 UiThreadHelper& operator=(UiThreadHelper&) = delete;
159 virtual ~UiThreadHelper() {
160 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
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));
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));
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));
191 SEQUENCE_CHECKER(sequence_checker_);
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_;
198 base::ScopedObservation<DeviceMonitorWin, DeviceMonitorWin::Observer>
199 device_observation_{this};
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()));
212 DoInitialEnumeration();
215 SerialDeviceEnumeratorWin::~SerialDeviceEnumeratorWin() = default;
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())
223 if (!SetupDiOpenDeviceInterface(dev_info.get(), device_path.c_str(), 0,
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))
233 EnumeratePort(dev_info.get(), &dev_info_data, /*check_port_name=*/false);
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())
242 if (!SetupDiOpenDeviceInterface(dev_info.get(), device_path.c_str(), 0,
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))
252 absl::optional<std::string> port_name =
253 GetPortName(dev_info.get(), &dev_info_data);
257 auto it = paths_.find(GetPath(*port_name));
258 if (it == paths_.end())
261 base::UnguessableToken token = it->second;
267 void SerialDeviceEnumeratorWin::DoInitialEnumeration() {
268 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
269 base::BlockingType::MAY_BLOCK);
271 // On Windows 10 and above most COM port drivers register using the COMPORT
272 // device interface class. Try to enumerate these first.
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())
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);
283 EnumeratePort(dev_info.get(), &dev_info_data, /*check_port_name=*/false);
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())
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);
302 EnumeratePort(dev_info.get(), &dev_info_data, /*check_port_name=*/true);
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);
314 if (check_port_name && !base::StartsWith(*port_name, "COM"))
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))
323 absl::optional<std::string> instance_id =
324 GetProperty(dev_info, dev_info_data, DEVPKEY_Device_InstanceId);
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));
333 base::UnguessableToken token = base::UnguessableToken::Create();
334 auto info = mojom::SerialPortInfo::New();
337 info->device_instance_id = *instance_id;
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));
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
354 absl::optional<std::string> friendly_name =
355 GetProperty(dev_info, dev_info_data, DEVPKEY_Device_FriendlyName);
359 info->display_name = GetDisplayName(*friendly_name);
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;
367 info->has_vendor_id = true;
368 info->vendor_id = *vendor_id;
369 vendor_id_str = base::StringPrintf("%04X", *vendor_id);
372 info->has_product_id = true;
373 info->product_id = *product_id;
374 product_id_str = base::StringPrintf("%04X", *product_id);
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)");
382 paths_.insert(std::make_pair(path, token));
383 AddPort(std::move(info));
386 } // namespace device