From e940c46e8e7b40367a4c151656550693476ea870 Mon Sep 17 00:00:00 2001 From: charliea Date: Mon, 28 Sep 2015 14:21:48 -0700 Subject: [PATCH] Extracts more information from serial devices on Windows At rockot@'s suggestion, I tested this with a small Chrome app that I wrote (https://github.com/zeptonaut/serial-device-enumerator-app) that opens a window showing all information that Chrome knows about serial devices. We chose this route over unit tests because we didn't think it was wise to write unit tests that assumed specific devices were plugged into our test servers. As far as devices, I used an FTDI Chipset High Speed USB 2.0 to Serial RS-232 DB-9 Converter. This was useful because it's one of not-so-many devices that has both serial properties (COM port) and USB properties (vendor ID, device ID, and display name). BUG=522217 Review URL: https://codereview.chromium.org/1357993002 Cr-Commit-Position: refs/heads/master@{#351163} --- device/serial/BUILD.gn | 1 + device/serial/DEPS | 1 + device/serial/serial.gyp | 1 + device/serial/serial_device_enumerator_win.cc | 116 ++++++++++++++++-- 4 files changed, 109 insertions(+), 10 deletions(-) diff --git a/device/serial/BUILD.gn b/device/serial/BUILD.gn index d3079102383431..fab490e29bc3b1 100644 --- a/device/serial/BUILD.gn +++ b/device/serial/BUILD.gn @@ -48,6 +48,7 @@ static_library("serial") { ] deps = [ "//third_party/mojo/src/mojo/public/cpp/system", + "//third_party/re2", ] if (use_udev) { diff --git a/device/serial/DEPS b/device/serial/DEPS index fc30a890dace87..4bebd41c4aa01b 100644 --- a/device/serial/DEPS +++ b/device/serial/DEPS @@ -1,4 +1,5 @@ include_rules = [ "+dbus", "+net/base", + "+third_party/re2", ] diff --git a/device/serial/serial.gyp b/device/serial/serial.gyp index 650c8c61c0b902..5d43bd1e9286a4 100644 --- a/device/serial/serial.gyp +++ b/device/serial/serial.gyp @@ -44,6 +44,7 @@ 'device_serial_mojo', '../../net/net.gyp:net', '../../third_party/mojo/mojo_public.gyp:mojo_cpp_bindings', + '../../third_party/re2/re2.gyp:re2', ], 'export_dependent_settings': [ 'device_serial_mojo', diff --git a/device/serial/serial_device_enumerator_win.cc b/device/serial/serial_device_enumerator_win.cc index 124c6b72046690..0d1e8f1831f161 100644 --- a/device/serial/serial_device_enumerator_win.cc +++ b/device/serial/serial_device_enumerator_win.cc @@ -6,14 +6,75 @@ #include +#include +#include + #include "base/memory/scoped_ptr.h" -#include "base/strings/string_util.h" -#include "base/strings/stringprintf.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" -#include "base/win/registry.h" +#include "third_party/re2/re2/re2.h" namespace device { +namespace { + +// Searches the specified device info for a property with the specified key, +// assigns the result to value, and returns whether the operation was +// successful. +bool GetProperty(HDEVINFO dev_info, + SP_DEVINFO_DATA dev_info_data, + const int key, + std::string* value) { + // We don't know how much space the property's value will take up, so we call + // the property retrieval function once to fetch the size of the required + // value buffer, then again once we've allocated a sufficiently large buffer. + DWORD buffer_size = 0; + SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data, key, nullptr, + nullptr, buffer_size, &buffer_size); + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return false; + + scoped_ptr buffer(new wchar_t[buffer_size]); + if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data, key, nullptr, + reinterpret_cast(buffer.get()), + buffer_size, nullptr)) + return false; + + *value = base::WideToUTF8(buffer.get()); + return true; +} + +// Searches for the COM port in the device's friendly name, assigns its value to +// com_port, and returns whether the operation was successful. +bool GetCOMPort(const std::string friendly_name, std::string* com_port) { + return RE2::PartialMatch(friendly_name, ".* \\((COM[0-9]+)\\)", com_port); +} + +// Searches for the display name in the device's friendly name, assigns its +// value to display_name, and returns whether the operation was successful. +bool GetDisplayName(const std::string friendly_name, + std::string* display_name) { + return RE2::PartialMatch(friendly_name, "(.*) \\(COM[0-9]+\\)", display_name); +} + +// Searches for the vendor ID in the device's hardware ID, assigns its value to +// vendor_id, and returns whether the operation was successful. +bool GetVendorID(const std::string hardware_id, uint32_t* vendor_id) { + std::string vendor_id_str; + return RE2::PartialMatch(hardware_id, "VID_([0-9]+)", &vendor_id_str) && + base::HexStringToUInt(vendor_id_str, vendor_id); +} + +// Searches for the product ID in the device's product ID, assigns its value to +// product_id, and returns whether the operation was successful. +bool GetProductID(const std::string hardware_id, uint32_t* product_id) { + std::string product_id_str; + return RE2::PartialMatch(hardware_id, "PID_([0-9]+)", &product_id_str) && + base::HexStringToUInt(product_id_str, product_id); +} + +} // namespace + // static scoped_ptr SerialDeviceEnumerator::Create() { return scoped_ptr(new SerialDeviceEnumeratorWin()); @@ -23,18 +84,53 @@ SerialDeviceEnumeratorWin::SerialDeviceEnumeratorWin() {} SerialDeviceEnumeratorWin::~SerialDeviceEnumeratorWin() {} -// TODO(rockot): Query the system for more information than just device paths. -// This may or may not require using a different strategy than scanning the -// registry location below. mojo::Array SerialDeviceEnumeratorWin::GetDevices() { - base::win::RegistryValueIterator iter_key( - HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM\\"); mojo::Array devices(0); - for (; iter_key.Valid(); ++iter_key) { + + // Make a device interface query to find all serial devices. + HDEVINFO dev_info = + SetupDiGetClassDevs(&GUID_DEVINTERFACE_SERENUM_BUS_ENUMERATOR, 0, 0, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (dev_info == INVALID_HANDLE_VALUE) + return devices.Pass(); + + SP_DEVINFO_DATA dev_info_data; + dev_info_data.cbSize = sizeof(SP_DEVINFO_DATA); + for (DWORD i = 0; SetupDiEnumDeviceInfo(dev_info, i, &dev_info_data); i++) { + std::string friendly_name, com_port; + // SPDRP_FRIENDLYNAME looks like "USB_SERIAL_PORT (COM3)". + if (!GetProperty(dev_info, dev_info_data, SPDRP_FRIENDLYNAME, + &friendly_name) || + !GetCOMPort(friendly_name, &com_port)) + // In Windows, the COM port is the path used to uniquely identify the + // serial device. If the COM can't be found, ignore the device. + continue; + serial::DeviceInfoPtr info(serial::DeviceInfo::New()); - info->path = base::UTF16ToASCII(iter_key.Value()); + info->path = com_port; + + std::string display_name; + if (GetDisplayName(friendly_name, &display_name)) + info->display_name = display_name; + + std::string hardware_id; + // SPDRP_HARDWAREID looks like "FTDIBUS\COMPORT&VID_0403&PID_6001". + if (GetProperty(dev_info, dev_info_data, SPDRP_HARDWAREID, &hardware_id)) { + uint32_t vendor_id, product_id; + if (GetVendorID(hardware_id, &vendor_id)) { + info->has_vendor_id = true; + info->vendor_id = vendor_id; + } + if (GetProductID(hardware_id, &product_id)) { + info->has_product_id = true; + info->product_id = product_id; + } + } + devices.push_back(info.Pass()); } + + SetupDiDestroyDeviceInfoList(dev_info); return devices.Pass(); }