411 lines
11 KiB
C
411 lines
11 KiB
C
/* ************************************************************************** */
|
|
/* _____ _ */
|
|
/* stream/windows.c |_ _|__ _ _| |__ ___ _ _ */
|
|
/* | Project: libp7 | |/ _ \| | | | '_ \ / _ \ | | | */
|
|
/* | | (_) | |_| | | | | __/ |_| | */
|
|
/* By: thomas <thomas@touhey.fr> |_|\___/ \__,_|_| |_|\___|\__, |.fr */
|
|
/* Last updated: 2017/01/11 18:36:38 |___/ */
|
|
/* */
|
|
/* ************************************************************************** */
|
|
#include <libp7/internals.h>
|
|
|
|
/* ************************************************************************** */
|
|
/* Find devices on Microsoft Windows */
|
|
/* ************************************************************************** */
|
|
#ifdef __WINDOWS__
|
|
# include <windows.h>
|
|
# include <setupapi.h>
|
|
# include <usbiodef.h>
|
|
# include <winerror.h>
|
|
|
|
/**
|
|
* wfind:
|
|
* Find the Microsoft Windows device path.
|
|
*
|
|
* @arg vid the vendor ID (0x0000 to 0xffff)
|
|
* @arg pid the product ID (0x0000 to 0xffff)
|
|
* @return the allocated path of the device if found, NULL otherwise
|
|
*/
|
|
|
|
static char *wfind(unsigned int vid, unsigned int pid)
|
|
{
|
|
char *devpath = NULL;
|
|
DWORD werr;
|
|
|
|
/* make the vid/pid string */
|
|
char vidpid[20];
|
|
sprintf(vidpid, "#vid_%04x&pid_%04x", vid, pid);
|
|
|
|
/* get the device information set (chained list) */
|
|
logr_info("Getting the device info set");
|
|
HDEVINFO DeviceInfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE,
|
|
NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|
if (DeviceInfoSet == INVALID_HANDLE_VALUE) {
|
|
werr = GetLastError();
|
|
logr_fatal("Device info gathering failed! Error 0x%08lX", werr);
|
|
return (NULL);
|
|
}
|
|
|
|
/* local vars */
|
|
SP_DEVICE_INTERFACE_DATA DeviceInterfaceData;
|
|
|
|
/* browse this set, setup */
|
|
DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
|
logr_info("Enumerating interfaces");
|
|
for (int i = 0; SetupDiEnumDeviceInterfaces(DeviceInfoSet, NULL,
|
|
&GUID_DEVINTERFACE_USB_DEVICE, i, &DeviceInterfaceData); i++) {
|
|
/* make the local variables */
|
|
DWORD RequiredSize = 0;
|
|
|
|
/* get the detail size */
|
|
logr_info("Getting interface information detail size");
|
|
if (!SetupDiGetDeviceInterfaceDetail(DeviceInfoSet,
|
|
&DeviceInterfaceData, NULL, 0, &RequiredSize, NULL) &&
|
|
(werr = GetLastError()) != ERROR_INSUFFICIENT_BUFFER) {
|
|
logr_error("Error getting this size: 0x%08lX", werr);
|
|
continue;
|
|
}
|
|
|
|
/* allocate detail space */
|
|
logr_info("Allocating space for interface information detail (%luo)",
|
|
RequiredSize);
|
|
PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData
|
|
= malloc(RequiredSize);
|
|
if (!DeviceInterfaceDetailData) {
|
|
logr_error("Memory allocation failed. Oh well.");
|
|
break ;
|
|
}
|
|
DeviceInterfaceDetailData->cbSize =
|
|
sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
|
|
|
/* get the detail */
|
|
logr_info("Getting interface information detail");
|
|
if (!SetupDiGetDeviceInterfaceDetail(DeviceInfoSet,
|
|
&DeviceInterfaceData, DeviceInterfaceDetailData, RequiredSize,
|
|
NULL, NULL)) {
|
|
werr = GetLastError();
|
|
logr_error("Error getting the interface information detail: 0x%08lX",
|
|
werr);
|
|
continue;
|
|
}
|
|
|
|
/* check if it corresponds */
|
|
const char *p = DeviceInterfaceDetailData->DevicePath;
|
|
logr_info("Stumbled across: %s", p);
|
|
if (strstr(p, vidpid)) {
|
|
devpath = strdup(p);
|
|
if (!devpath) break ;
|
|
}
|
|
|
|
/* free the allocated detail */
|
|
free(DeviceInterfaceDetailData);
|
|
if (devpath) break ;
|
|
}
|
|
|
|
/* destroy the device information set */
|
|
logr_info("Destroying the device information set");
|
|
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
|
|
return (devpath);
|
|
}
|
|
|
|
/* ************************************************************************** */
|
|
/* Structure of a cookie */
|
|
/* ************************************************************************** */
|
|
/* here it is
|
|
* PSP_COOKIE is just there to take the piss out of Microsoft :p */
|
|
# define BUFSIZE 2048
|
|
typedef struct {
|
|
HANDLE _handle;
|
|
|
|
unsigned char _buf[BUFSIZE];
|
|
ssize_t _start, _end;
|
|
} win_cookie_t, *PSP_COOKIE;
|
|
|
|
/* ************************************************************************** */
|
|
/* Callbacks and initialization functions */
|
|
/* ************************************************************************** */
|
|
/**
|
|
* p7_win_read:
|
|
* Read from an MS-Windows stream.
|
|
*
|
|
* @arg vcookie the cookie (uncasted).
|
|
* @return the error code (0 if ok).
|
|
*/
|
|
|
|
static int p7_win_read(void *vcookie, unsigned char *dest, size_t size,
|
|
unsigned int timeout)
|
|
{
|
|
win_cookie_t *cookie = (win_cookie_t*)vcookie;
|
|
|
|
/* transmit what's already in the buffer */
|
|
if (cookie->_start <= cookie->_end) {
|
|
size_t tocopy = cookie->_end - cookie->_start + 1;
|
|
if (tocopy > size) tocopy = size;
|
|
|
|
memcpy(dest, &cookie->_buf[cookie->_start], tocopy);
|
|
cookie->_start += tocopy;
|
|
dest += tocopy;
|
|
size -= tocopy;
|
|
}
|
|
|
|
/* set the timeout */
|
|
SetCommTimeouts(cookie->_handle, (COMMTIMEOUTS[]){{
|
|
.ReadIntervalTimeout = timeout
|
|
}});
|
|
|
|
/* main receiving loop */
|
|
while (size) {
|
|
/* receive */
|
|
DWORD recv;
|
|
DWORD wsuccess = ReadFile(cookie->_handle,
|
|
cookie->_buf, BUFSIZE, &recv, NULL);
|
|
|
|
/* check error */
|
|
DWORD werr;
|
|
if (!wsuccess) switch ((werr = GetLastError())) {
|
|
case ERROR_DEV_NOT_EXIST:
|
|
logr_error("Device has been disconnected!");
|
|
return (p7_error_nocalc);
|
|
|
|
default:
|
|
logr_fatal("Encountered error 0x%08lX", werr);
|
|
return (p7_error_unknown);
|
|
}
|
|
|
|
/* get the current size to copy */
|
|
size_t tocopy = (size_t)recv;
|
|
if (tocopy > size) tocopy = size;
|
|
|
|
/* copy to destination */
|
|
memcpy(dest, cookie->_buf, tocopy);
|
|
dest += tocopy;
|
|
size -= tocopy;
|
|
|
|
/* correct start and end points */
|
|
cookie->_start = tocopy;
|
|
cookie->_end = (size_t)recv - 1;
|
|
}
|
|
|
|
/* no error */
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* p7_win_write:
|
|
* Write to an MS-Windows stream.
|
|
*
|
|
* @arg vcookie the cookie (uncasted).
|
|
* @arg data the source
|
|
* @arg size the source size
|
|
* @return the libp7 error (0 if ok).
|
|
*/
|
|
|
|
static int p7_win_write(void *vcookie,
|
|
const unsigned char *data, size_t size)
|
|
{
|
|
win_cookie_t *cookie = (win_cookie_t*)vcookie;
|
|
|
|
/* make the I/O request */
|
|
BOOL wsuccess = TRUE;
|
|
do {
|
|
/* write */
|
|
DWORD wrt;
|
|
wsuccess = WriteFile(cookie->_handle, data, size, &wrt, NULL);
|
|
if (!wsuccess) break;
|
|
|
|
/* go forward */
|
|
data += wrt;
|
|
size -= wrt;
|
|
} while (size);
|
|
|
|
/* check error */
|
|
DWORD werr;
|
|
if (!wsuccess) switch ((werr = GetLastError())) {
|
|
case ERROR_DEV_NOT_EXIST:
|
|
logr_error("Device has been disconnected!");
|
|
return (p7_error_nocalc);
|
|
|
|
default:
|
|
logr_fatal("Encountered error 0x%08lx", werr);
|
|
return (p7_error_unknown);
|
|
}
|
|
|
|
/* success! */
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* p7_win_setcomm:
|
|
* Set communication status of a Microsoft Windows stream.
|
|
*
|
|
* @arg vcookie the cookie (uncasted).
|
|
* @arg speed the speed.
|
|
* @arg parity the parity.
|
|
* @arg stopbits the stop bits.
|
|
* @return the error (0 if ok)
|
|
*/
|
|
|
|
static int p7_win_setcomm(void *vcookie, int speed, int parity, int stopbits)
|
|
{
|
|
win_cookie_t *cookie = (win_cookie_t*)vcookie;
|
|
|
|
/* get communication properties */
|
|
DCB dcb = {0};
|
|
if (!GetCommState(cookie->_handle, &dcb)) {
|
|
logr_warn("Failed getting communication settings, nevermind.");
|
|
return (0);
|
|
}
|
|
|
|
/* get the speed constant */
|
|
switch (speed) {
|
|
case P7_B9600: default:
|
|
speed = CBR_9600; break;
|
|
case P7_B19200:
|
|
speed = CBR_19200; break;
|
|
}
|
|
|
|
/* set the DCB thingies */
|
|
dcb = (DCB){
|
|
.DCBlength = sizeof(dcb),
|
|
.BaudRate = speed,
|
|
.fBinary = 1,
|
|
.fParity = !!parity,
|
|
.fDtrControl = DTR_CONTROL_ENABLE,
|
|
.fRtsControl = RTS_CONTROL_ENABLE,
|
|
.XonLim = 0x4000,
|
|
.XoffLim = 0x1000,
|
|
.ByteSize = 8,
|
|
.Parity = !parity ? NOPARITY : (parity % 2) ? ODDPARITY : EVENPARITY,
|
|
.StopBits = (stopbits == 1) ? ONESTOPBIT : TWOSTOPBITS,
|
|
.XonChar = 17, /* DC1, Device Control 1 */
|
|
.XoffChar = 19, /* DC3, Device Control 3 */
|
|
};
|
|
|
|
/* save new state */
|
|
DWORD wsuccess = SetCommState(cookie->_handle, &dcb);
|
|
#if LOGLEVEL <= ll_info
|
|
if (!wsuccess)
|
|
logr_warn("Failed setting communication settings, nevermind.");
|
|
#endif
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* p7_win_close:
|
|
* Close a MS-Windows stream.
|
|
*
|
|
* @arg vcookie the cookie (uncasted).
|
|
* @return the error code (0 if ok).
|
|
*/
|
|
|
|
static int p7_win_close(void *vcookie)
|
|
{
|
|
win_cookie_t *cookie = (win_cookie_t*)vcookie;
|
|
CloseHandle(cookie->_handle);
|
|
free(cookie);
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* winit:
|
|
* "Real" MS-Windows initialization function.
|
|
*
|
|
* Distinguished from the `p7_winit` function because of const/non-const and
|
|
* dynamically allocated or not problems.
|
|
*
|
|
* @arg handle the handle to create.
|
|
* @arg flags the flags.
|
|
* @arg path the MS-Windows device path.
|
|
* @return the error code (0 if ok).
|
|
*/
|
|
|
|
static int winit(p7_handle_t **handle, unsigned int flags, const char *path)
|
|
{
|
|
/* open the file handle - my god, this function is too complex. */
|
|
HANDLE fhandle = CreateFile(path, GENERIC_READ | GENERIC_WRITE, 0, NULL,
|
|
OPEN_EXISTING, 0, NULL);
|
|
DWORD werr;
|
|
if (fhandle == INVALID_HANDLE_VALUE) switch ((werr = GetLastError())) {
|
|
case ERROR_FILE_NOT_FOUND:
|
|
case ERROR_DEV_NOT_EXIST:
|
|
return (p7_error_nocalc);
|
|
case ERROR_ACCESS_DENIED:
|
|
return (p7_error_noaccess);
|
|
default:
|
|
logr_fatal("Error 0x%08lx encountered.", werr);
|
|
return (p7_error_unknown);
|
|
}
|
|
|
|
/* make cookie */
|
|
win_cookie_t *cookie = malloc(sizeof(win_cookie_t));
|
|
if (!cookie) {
|
|
CloseHandle(handle);
|
|
return (p7_error_alloc);
|
|
}
|
|
|
|
/* fill cookie */
|
|
*cookie = (win_cookie_t){
|
|
._handle = fhandle,
|
|
._start = 0, ._end = -1
|
|
};
|
|
|
|
/* initialize for real */
|
|
return (p7_sinit(handle, flags, NULL, (p7_stream_t){
|
|
.cookie = cookie,
|
|
.read = p7_win_read,
|
|
.write = p7_win_write,
|
|
.close = p7_win_close,
|
|
.setcomm = p7_win_setcomm
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* p7_winit:
|
|
* Initialize libp7 using the Microsoft Windows API.
|
|
*
|
|
* @arg handle the handle to create.
|
|
* @arg flags the flags.
|
|
* @arg path the MS-Windows device path (NULL if we should find it).
|
|
* @return the error code (0 if you're a boss).
|
|
*/
|
|
|
|
int p7_winit(p7_handle_t **handle, unsigned int flags, const char *path)
|
|
{
|
|
/* if there is a path, init with it */
|
|
if (path) return (winit(handle, flags, path));
|
|
|
|
logr_info("Let's try and find out a path!");
|
|
/* otherwise, find one - TODO: hardcode less */
|
|
char *p = wfind(0x07cf, 0x6101);
|
|
if (!p) return (p7_error_nocalc);
|
|
|
|
/* we found one! call init procedure with it, free it, and go */
|
|
int err = winit(handle, flags, p);
|
|
free(p); return (err);
|
|
}
|
|
|
|
/* ************************************************************************** */
|
|
/* Not on windows: placebo */
|
|
/* ************************************************************************** */
|
|
#else
|
|
|
|
/**
|
|
* p7_winit:
|
|
* Initialize libp7 [not] using the Microsoft Windows API. Placebo.
|
|
*
|
|
* @arg handle the handle to create.
|
|
* @arg flags the flags.
|
|
* @arg path the MS-Windows device path.
|
|
* @return the error code (0 if you're a knoop).
|
|
*/
|
|
|
|
int p7_winit(p7_handle_t **handle, unsigned int flags, const char *path)
|
|
{
|
|
(void)handle;
|
|
(void)flags;
|
|
(void)path;
|
|
return (p7_error_nocalc);
|
|
}
|
|
|
|
#endif
|