понедельник, 3 октября 2016 г.

How to enumerate serial ports using Setup API (win32)

For me using setupapi is the most strait-forward method to retrieve a list of serial ports on Windows.
I should note, that this code works well on XP and higher.

#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <Setupapi.h>
#include <devguid.h>
#include "conio.h"
#include "tchar.h"
// Get-WMIObject Win32_pnpentity | ? Description -like "*CH*"
// Get-WMIObject Win32_pnpentity | ? Manufacturer -like "*chip*"
/* http://stackoverflow.com/a/3439805 */
#include <cfgmgr32.h> // for MAX_DEVICE_ID_LEN, CM_Get_Parent and CM_Get_Device_ID
#define INITGUID
//#include "c:\WinDDK\7600.16385.1\inc\api\devpkey.h"
// include DEVPKEY_Device_BusReportedDeviceDesc from WinDDK\7600.16385.1\inc\api\devpropdef.h
#ifdef DEFINE_DEVPROPKEY
#undef DEFINE_DEVPROPKEY
#endif
#ifdef INITGUID
#define DEFINE_DEVPROPKEY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, pid) EXTERN_C const DEVPROPKEY DECLSPEC_SELECTANY name = { { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }, pid }
#else
#define DEFINE_DEVPROPKEY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, pid) EXTERN_C const DEVPROPKEY name
#endif // INITGUID
// include DEVPKEY_Device_BusReportedDeviceDesc from WinDDK\7600.16385.1\inc\api\devpkey.h
DEFINE_DEVPROPKEY(DEVPKEY_Device_BusReportedDeviceDesc, 0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2, 4); // DEVPROP_TYPE_STRING
DEFINE_DEVPROPKEY(DEVPKEY_Device_ContainerId, 0x8c7ed206, 0x3f8a, 0x4827, 0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c, 2); // DEVPROP_TYPE_GUID
DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
DEFINE_DEVPROPKEY(DEVPKEY_DeviceDisplay_Category, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 0x5a); // DEVPROP_TYPE_STRING_LIST
DEFINE_DEVPROPKEY(DEVPKEY_Device_LocationInfo, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 15); // DEVPROP_TYPE_STRING
DEFINE_DEVPROPKEY(DEVPKEY_Device_Manufacturer, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 13); // DEVPROP_TYPE_STRING
DEFINE_DEVPROPKEY(DEVPKEY_Device_SecuritySDS, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 26); // DEVPROP_TYPE_SECURITY_DESCRIPTOR_STRING
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
#pragma comment (lib, "setupapi.lib")
typedef BOOL(WINAPI *FN_SetupDiGetDevicePropertyW)(
__in HDEVINFO DeviceInfoSet,
__in PSP_DEVINFO_DATA DeviceInfoData,
__in const DEVPROPKEY *PropertyKey,
__out DEVPROPTYPE *PropertyType,
__out_opt PBYTE PropertyBuffer,
__in DWORD PropertyBufferSize,
__out_opt PDWORD RequiredSize,
__in DWORD Flags
);
wchar_t *subString(wchar_t *someString, int n)
{
wchar_t *new_ = (wchar_t*) malloc(sizeof(wchar_t)*n + 1);
_tcsncpy_s(new_, 5, someString, n);
new_[n] = '\0';
return new_;
}
int main(int argc, char ** argv)
{
static const GUID g1 = { 0x2C7089AA, 0x2E0E, 0x11D1, { 0xB1, 0x14, 0x00, 0xC0, 0x4F, 0xC2, 0xAA, 0xE4 } };
static const GUID g2 = { 0x86E0D1E0, 0x8089, 0x11D0, { 0x9C, 0xE4, 0x08, 0x00, 0x3E, 0x30, 0x1F, 0x73 } };
static const GUID g3 = { 0x4D36E978, 0xE325, 0x11CE, { 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 } };
static const GUID g4 = { 0xCC0EF009, 0xB820, 0x42F4, { 0x95, 0xA9, 0x9B, 0xFA, 0x6A, 0x5A, 0xB7, 0xAB } };
static const GUID g5 = { 0x9341CD95, 0x4371, 0x4A37, { 0xA5, 0xAF, 0xFD, 0xB0, 0xA9, 0xD1, 0x96, 0x31 } };
GUID *guidDev = (GUID*)& GUID_DEVCLASS_PORTS;
HDEVINFO hDevInfo = SetupDiGetClassDevs(guidDev, NULL, NULL, DIGCF_PRESENT | DIGCF_PROFILE); /* DIGCF_DEVICEINTERFACE */
int memberIndex = 0;
DWORD dwSize, dwPropertyRegDataType;
WCHAR szBuffer[400];
DEVPROPTYPE ulPropertyType;
TCHAR *id = NULL;
TCHAR *vid = NULL;
TCHAR *pid = NULL;
TCHAR *name = NULL;
TCHAR *manuf = NULL;
TCHAR *description = NULL;
SP_DEVINFO_DATA deviceInfoData;
FN_SetupDiGetDevicePropertyW fn_SetupDiGetDevicePropertyW = (FN_SetupDiGetDevicePropertyW) GetProcAddress(GetModuleHandle(TEXT("Setupapi.dll")), "SetupDiGetDevicePropertyW");
while (true) {
id = NULL;
vid = NULL;
pid = NULL;
name = NULL;
manuf = NULL;
description = NULL;
ZeroMemory(&deviceInfoData, sizeof(SP_DEVINFO_DATA));
deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
if (SetupDiEnumDeviceInfo(hDevInfo, memberIndex, &deviceInfoData) == FALSE) {
if (GetLastError() == ERROR_NO_MORE_ITEMS) {
break;
}
}
dwSize = sizeof(szBuffer);
SetupDiGetDeviceInstanceId(hDevInfo, &deviceInfoData, szBuffer, dwSize, &dwSize);
szBuffer[dwSize] = '\0';
id = _wcsdup(szBuffer);
vid = wcsstr(szBuffer, _T("VID_"));
if (vid) {
vid += 4;
vid = subString(vid, 4);
}
pid = wcsstr(szBuffer, _T("PID_"));
if (pid) {
pid += 4;
pid = subString(pid, 4);
}
if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &deviceInfoData, SPDRP_DEVICEDESC, &dwPropertyRegDataType, (BYTE*)szBuffer, sizeof(szBuffer), &dwSize)) {
description = _wcsdup(szBuffer);
}
if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &deviceInfoData, SPDRP_MFG, &dwPropertyRegDataType, (BYTE*)szBuffer, sizeof(szBuffer), &dwSize)) {
manuf = _wcsdup(szBuffer);
}
TCHAR szHardwareIDs[4096];
if (SetupDiGetDeviceRegistryProperty(hDevInfo, &deviceInfoData, SPDRP_HARDWAREID,
&dwPropertyRegDataType, (BYTE*)szHardwareIDs,
sizeof(szHardwareIDs), // The size, in bytes
&dwSize)) {
LPCTSTR pszId;
_tprintf(TEXT("Hardware IDs:\n"));
for (pszId = szHardwareIDs;
*pszId != TEXT('\0') && pszId + dwSize / sizeof(TCHAR) <= szHardwareIDs + ARRAYSIZE(szHardwareIDs);
pszId += lstrlen(pszId) + 1) {
_tprintf(TEXT(" -> \"%s\"\n"), pszId);
}
}
HKEY hkey = SetupDiOpenDevRegKey(hDevInfo, &deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
if (hkey != INVALID_HANDLE_VALUE) {
dwSize = sizeof(szBuffer);
if (RegQueryValueEx(hkey, _T("PortName"), NULL, NULL, (LPBYTE)&szBuffer, &dwSize) == ERROR_SUCCESS) {
szBuffer[dwSize] = '\0';
name = _wcsdup(szBuffer);
}
}
if (fn_SetupDiGetDevicePropertyW) { // Vista and higher
if (fn_SetupDiGetDevicePropertyW(hDevInfo, &deviceInfoData, &DEVPKEY_Device_Manufacturer,
&ulPropertyType, (BYTE*)szBuffer, sizeof(szBuffer), &dwSize, 0)) {
manuf = _wcsdup(szBuffer);
}
} else { // XP
}
_tprintf(TEXT("Id: %s \n"), id);
_tprintf(TEXT("Vid: %s \n"), vid);
_tprintf(TEXT("Pid: %s \n"), pid);
_tprintf(TEXT("Device Description: %s\n"), description);
_tprintf(TEXT("Manufacturer: %s \n"), manuf);
_tprintf(_T("PortName %s\n"), name);
free(id);
free(vid);
free(pid);
free(description);
free(manuf);
free(name);
RegCloseKey(hkey);
memberIndex++;
}
if (hDevInfo) {
SetupDiDestroyDeviceInfoList(hDevInfo);
}
_getch();
return 0;
}
view raw EnumPorts.cpp hosted with ❤ by GitHub


Example of output:

Hardware IDs:
-> "USB\VID_10C4&PID_EA60&REV_0100"
-> "USB\VID_10C4&PID_EA60"
Id: USB\VID_10C4&PID_EA60\0001
Vid: 10C4
Pid: EA60
Device Description: Silicon Labs CP210x USB to UART Bridge
Manufacturer: Silicon Laboratories
PortName COM18
Hardware IDs:
-> "MF\PCI9710_COM"
Id: MF\PCI#VEN_9710&DEV_9835&SUBSYS_00121000&REV_01\6&20BEF77D&0&0800E5#CHILD0000
Vid: (null)
Pid: (null)
Device Description: PCI Serial Port
Manufacturer: MosChip Semiconductor Technology Ltd
PortName COM6
I'm going to offer this code to guys of node-serialport, because current method doesn't work well on some setups with antivir.


Update: The patch is ready https://github.com/Zensey/node-serialport/commit/b4c8e830d248c4f7e3be0fd0d2cd56a18159f2d9


References:
1. https://social.msdn.microsoft.com/Forums/vstudio/en-US/2555943e-0c69-4357-a85b-d6540bcaaf84/using-setupapi-to-return-all-guiddevclassdisplay?forum=vcgeneral
2. http://stackoverflow.com/a/3439805
3. https://gist.github.com/Zensey/7bd7781a28d6be306be5cdd70539dc65

SSH over TLS/SSL

This method is appliable to situation when you are at restricted network where connection to external ssh is not allowed. In addition to interactive ssh connect to server you can setup persistent reverse tunnel to server in order to login from server to client.

On server:

Install stunnel:

$ sudo apt-get install stunnel4

$ openssl genrsa 1024 > stunnel.key
$ openssl req -new -key stunnel.key -x509 -days 1000 -out stunnel.crt
$ cat stunnel.crt stunnel.key > stunnel.pem

$ sudo mv stunnel.pem /etc/stunnel/


Edit stunnel config file: /etc/stunnel/stunnel.conf
pid = /var/run/stunnel.pid
cert = /etc/stunnel/stunnel.pem
[ssh]
accept = 1.1.1.1:443
connect = 127.0.0.1:22
view raw stunnel.conf hosted with ❤ by GitHub

^where 1.1.1.1 -- is your server external ip.

Also edit /etc/default/stunnel4. Set param Enabled=1
Then start stunnel4 service.

On client:

Install socat:
$ sudo apt-get install socat

Add to file .ssh/config
Host 1.1.1.1
ProxyCommand socat - openssl:1.1.1.1:443,verify=0
view raw tun_ssh_config1 hosted with ❤ by GitHub


Connect to your server:
$ ssh user@1.1.1.1

Reverse tunnel with AutoSSH:

First of all add user without shell:

$ sudo useradd -m -s /bin/false autossh

Now login, make a new key and copy it to the server:

$ sudo su -s /bin/bash autossh
autossh@pc:...$ cd ~
autossh@pc:~$ ssh-keygen
autossh@pc:~$ ssh-copy-id -i .ssh/id_rsa.pub remote@1.1.1.1

Also add this to .ssh/config of user autossh:
Host 1.1.1.1
ProxyCommand socat - openssl:1.1.1.1:443,verify=0
ServerAliveInterval 60
ServerAliveCountMax 3
view raw tun_ssh_config2 hosted with ❤ by GitHub


To start reverse tunnel execute:
autossh -M 0 -N -f -R 5001:localhost:22 remote@1.1.1.1
view raw autossh hosted with ❤ by GitHub
To auto-start reverse tunnel during system start add to /etc/rc.local:
su - autossh -s /bin/sh -c "/usr/bin/autossh -M 0 -N -f -R 5001:localhost:22 remote@1.1.1.1"
view raw rc.local hosted with ❤ by GitHub

Now you can test your reverse tunnel from server:
$ ssh -p 5001 user@localhost