// SetupAPI VID/PID enumeration helper (D-17, LHWD-04). // Pattern: SetupDiGetClassDevs(GUID_DEVINTERFACE_COMPORT) -> SetupDiEnumDeviceInfo // -> SPDRP_HARDWAREID substring match "VID_%04X&PID_%04X" // -> SetupDiOpenDevRegKey(DIREG_DEV) + RegQueryValueExA("PortName"). // [CITED: 15-RESEARCH.md Pattern 4 / Code Examples ยง4; // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-comport; // https://aticleworld.com/get-com-port-of-usb-serial-device/] // // T-15-02 mitigation: fixed 256-byte hwBuf with sizeof passed to // SetupDiGetDeviceRegistryPropertyA โ€” failure path skips the device. // Pitfall 7: never call from DllMain / DLL_PROCESS_DETACH (loader lock). #include "com_port_scan.h" #include #include #include // MUST precede Ntddser.h to instantiate the GUID #include // GUID_DEVINTERFACE_COMPORT #include #include #include #include #include std::vector ScanForUsbComPorts(uint16_t vid, uint16_t pid) { std::vector results; HDEVINFO hDevInfo = SetupDiGetClassDevsA( &GUID_DEVINTERFACE_COMPORT, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (hDevInfo == INVALID_HANDLE_VALUE) { return results; } // Build the substring we're looking for, e.g. "VID_303A&PID_1001" (uppercase). char wantHwId[32]; std::snprintf(wantHwId, sizeof(wantHwId), "VID_%04X&PID_%04X", vid, pid); SP_DEVINFO_DATA devInfo{}; devInfo.cbSize = sizeof(devInfo); // WR-01 (Phase 15 review): MUST call SetupDiDestroyDeviceInfoList on every // exit path from this function. Do NOT add an early `return` inside the // for-loop below โ€” use `continue` and let control fall through to the // single SetupDiDestroyDeviceInfoList call after the loop. If future // refactors need to bail out mid-loop, replace hDevInfo with an RAII // wrapper or add explicit Destroy calls on every return path. for (DWORD i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &devInfo); ++i) { // SPDRP_HARDWAREID returns a multi-string ("USB\VID_303A&PID_1001\xxxx\0"), // case mixed. We do a case-insensitive substring scan. // T-15-02 mitigation: fixed 256-byte buffer with sizeof; on failure // (truncation or missing property), skip the device. char hwBuf[256] = {0}; DWORD hwSz = 0; if (!SetupDiGetDeviceRegistryPropertyA( hDevInfo, &devInfo, SPDRP_HARDWAREID, nullptr, reinterpret_cast(hwBuf), sizeof(hwBuf), &hwSz)) { continue; } // WR-02 (Phase 15 review): SPDRP_HARDWAREID is a REG_MULTI_SZ. hwSz // reports the full byte count including every embedded NUL and the // final double-NUL terminator. Constructing std::string with (hwBuf, // hwSz) yields a buffer with embedded NULs so substring matches can // spill across the first string into trailing ones โ€” a false-positive // risk if a later ID contains the target VID/PID but the first does // not. Match only the first (most-specific) hardware ID by letting // the string ctor stop at the first NUL. std::string upper(hwBuf); // stops at first '\0' naturally std::transform(upper.begin(), upper.end(), upper.begin(), [](unsigned char c) { return static_cast(std::toupper(c)); }); if (upper.find(wantHwId) == std::string::npos) { continue; } // Open per-device registry key and read PortName. HKEY hKey = SetupDiOpenDevRegKey( hDevInfo, &devInfo, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); if (hKey == INVALID_HANDLE_VALUE) { continue; } char portBuf[16] = {0}; DWORD portSz = sizeof(portBuf); DWORD valType = 0; LSTATUS rc = RegQueryValueExA(hKey, "PortName", nullptr, &valType, reinterpret_cast(portBuf), &portSz); RegCloseKey(hKey); if (rc == ERROR_SUCCESS && portBuf[0]) { results.emplace_back(portBuf); // "COM5" } } SetupDiDestroyDeviceInfoList(hDevInfo); // D-10: lowest COM number wins -> sort by trailing integer of "COMn". // IN-02 (Phase 15 review): guard against malformed PortName values shorter // than 4 chars; Windows always writes "COMn" (4+ chars) but a defensive // size check prevents atoi from reading past the string null terminator. std::sort(results.begin(), results.end(), [](const std::string& a, const std::string& b) { int na = (a.size() > 3) ? std::atoi(a.c_str() + 3) : 0; // skip "COM" int nb = (b.size() > 3) ? std::atoi(b.c_str() + 3) : 0; return na < nb; }); return results; }