summaryrefslogtreecommitdiff
path: root/src/hid_win.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/hid_win.c')
-rw-r--r--src/hid_win.c398
1 files changed, 299 insertions, 99 deletions
diff --git a/src/hid_win.c b/src/hid_win.c
index f970589..018b4d9 100644
--- a/src/hid_win.c
+++ b/src/hid_win.c
@@ -14,19 +14,39 @@
14#include <windows.h> 14#include <windows.h>
15#include <setupapi.h> 15#include <setupapi.h>
16#include <initguid.h> 16#include <initguid.h>
17#include <devpkey.h>
18#include <devpropdef.h>
17#include <hidclass.h> 19#include <hidclass.h>
18#include <hidsdi.h> 20#include <hidsdi.h>
19 21
20#include "fido.h" 22#include "fido.h"
21 23
22#define REPORT_LEN 65 24#if defined(__MINGW32__) && __MINGW64_VERSION_MAJOR < 6
25WINSETUPAPI WINBOOL WINAPI SetupDiGetDevicePropertyW(HDEVINFO,
26 PSP_DEVINFO_DATA, const DEVPROPKEY *, DEVPROPTYPE *, PBYTE,
27 DWORD, PDWORD, DWORD);
28#endif
29
30#if defined(__MINGW32__)
31DEFINE_DEVPROPKEY(DEVPKEY_Device_Parent, 0x4340a6c5, 0x93fa, 0x4706, 0x97,
32 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7, 8);
33#endif
34
35struct hid_win {
36 HANDLE dev;
37 OVERLAPPED overlap;
38 int report_pending;
39 size_t report_in_len;
40 size_t report_out_len;
41 unsigned char report[1 + CTAP_MAX_REPORT_LEN];
42};
23 43
24static bool 44static bool
25is_fido(HANDLE dev) 45is_fido(HANDLE dev)
26{ 46{
27 PHIDP_PREPARSED_DATA data = NULL; 47 PHIDP_PREPARSED_DATA data = NULL;
28 HIDP_CAPS caps; 48 HIDP_CAPS caps;
29 uint16_t usage_page = 0; 49 int fido = 0;
30 50
31 if (HidD_GetPreparsedData(dev, &data) == false) { 51 if (HidD_GetPreparsedData(dev, &data) == false) {
32 fido_log_debug("%s: HidD_GetPreparsedData", __func__); 52 fido_log_debug("%s: HidD_GetPreparsedData", __func__);
@@ -38,18 +58,48 @@ is_fido(HANDLE dev)
38 goto fail; 58 goto fail;
39 } 59 }
40 60
41 if (caps.OutputReportByteLength != REPORT_LEN || 61 fido = (uint16_t)caps.UsagePage == 0xf1d0;
42 caps.InputReportByteLength != REPORT_LEN) { 62fail:
43 fido_log_debug("%s: unsupported report len", __func__); 63 if (data != NULL)
64 HidD_FreePreparsedData(data);
65
66 return (fido);
67}
68
69static int
70get_report_len(HANDLE dev, int dir, size_t *report_len)
71{
72 PHIDP_PREPARSED_DATA data = NULL;
73 HIDP_CAPS caps;
74 USHORT v;
75 int ok = -1;
76
77 if (HidD_GetPreparsedData(dev, &data) == false) {
78 fido_log_debug("%s: HidD_GetPreparsedData/%d", __func__, dir);
44 goto fail; 79 goto fail;
45 } 80 }
46 81
47 usage_page = caps.UsagePage; 82 if (HidP_GetCaps(data, &caps) != HIDP_STATUS_SUCCESS) {
83 fido_log_debug("%s: HidP_GetCaps/%d", __func__, dir);
84 goto fail;
85 }
86
87 if (dir == 0)
88 v = caps.InputReportByteLength;
89 else
90 v = caps.OutputReportByteLength;
91
92 if ((*report_len = (size_t)v) == 0) {
93 fido_log_debug("%s: report_len == 0", __func__);
94 goto fail;
95 }
96
97 ok = 0;
48fail: 98fail:
49 if (data != NULL) 99 if (data != NULL)
50 HidD_FreePreparsedData(data); 100 HidD_FreePreparsedData(data);
51 101
52 return (usage_page == 0xf1d0); 102 return (ok);
53} 103}
54 104
55static int 105static int
@@ -59,13 +109,14 @@ get_int(HANDLE dev, int16_t *vendor_id, int16_t *product_id)
59 109
60 attr.Size = sizeof(attr); 110 attr.Size = sizeof(attr);
61 111
62 if (HidD_GetAttributes(dev, &attr) == false) { 112 if (HidD_GetAttributes(dev, &attr) == false ||
113 attr.VendorID > INT16_MAX || attr.ProductID > INT16_MAX) {
63 fido_log_debug("%s: HidD_GetAttributes", __func__); 114 fido_log_debug("%s: HidD_GetAttributes", __func__);
64 return (-1); 115 return (-1);
65 } 116 }
66 117
67 *vendor_id = attr.VendorID; 118 *vendor_id = (int16_t)attr.VendorID;
68 *product_id = attr.ProductID; 119 *product_id = (int16_t)attr.ProductID;
69 120
70 return (0); 121 return (0);
71} 122}
@@ -91,7 +142,7 @@ get_str(HANDLE dev, char **manufacturer, char **product)
91 goto fail; 142 goto fail;
92 } 143 }
93 144
94 if ((*manufacturer = malloc(utf8_len)) == NULL) { 145 if ((*manufacturer = malloc((size_t)utf8_len)) == NULL) {
95 fido_log_debug("%s: malloc", __func__); 146 fido_log_debug("%s: malloc", __func__);
96 goto fail; 147 goto fail;
97 } 148 }
@@ -113,7 +164,7 @@ get_str(HANDLE dev, char **manufacturer, char **product)
113 goto fail; 164 goto fail;
114 } 165 }
115 166
116 if ((*product = malloc(utf8_len)) == NULL) { 167 if ((*product = malloc((size_t)utf8_len)) == NULL) {
117 fido_log_debug("%s: malloc", __func__); 168 fido_log_debug("%s: malloc", __func__);
118 goto fail; 169 goto fail;
119 } 170 }
@@ -136,25 +187,138 @@ fail:
136 return (ok); 187 return (ok);
137} 188}
138 189
190static char *
191get_path(HDEVINFO devinfo, SP_DEVICE_INTERFACE_DATA *ifdata)
192{
193 SP_DEVICE_INTERFACE_DETAIL_DATA_A *ifdetail = NULL;
194 char *path = NULL;
195 DWORD len = 0;
196
197 /*
198 * "Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail
199 * with a NULL DeviceInterfaceDetailData pointer, a
200 * DeviceInterfaceDetailDataSize of zero, and a valid RequiredSize
201 * variable. In response to such a call, this function returns the
202 * required buffer size at RequiredSize and fails with GetLastError
203 * returning ERROR_INSUFFICIENT_BUFFER."
204 */
205 if (SetupDiGetDeviceInterfaceDetailA(devinfo, ifdata, NULL, 0, &len,
206 NULL) != false || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
207 fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 1",
208 __func__);
209 goto fail;
210 }
211
212 if ((ifdetail = malloc(len)) == NULL) {
213 fido_log_debug("%s: malloc", __func__);
214 goto fail;
215 }
216
217 ifdetail->cbSize = sizeof(*ifdetail);
218
219 if (SetupDiGetDeviceInterfaceDetailA(devinfo, ifdata, ifdetail, len,
220 NULL, NULL) == false) {
221 fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 2",
222 __func__);
223 goto fail;
224 }
225
226 if ((path = strdup(ifdetail->DevicePath)) == NULL) {
227 fido_log_debug("%s: strdup", __func__);
228 goto fail;
229 }
230
231fail:
232 free(ifdetail);
233
234 return (path);
235}
236
237#ifndef FIDO_HID_ANY
238static bool
239hid_ok(HDEVINFO devinfo, DWORD idx)
240{
241 SP_DEVINFO_DATA devinfo_data;
242 wchar_t *parent = NULL;
243 DWORD parent_type = DEVPROP_TYPE_STRING;
244 DWORD len = 0;
245 bool ok = false;
246
247 memset(&devinfo_data, 0, sizeof(devinfo_data));
248 devinfo_data.cbSize = sizeof(devinfo_data);
249
250 if (SetupDiEnumDeviceInfo(devinfo, idx, &devinfo_data) == false) {
251 fido_log_debug("%s: SetupDiEnumDeviceInfo", __func__);
252 goto fail;
253 }
254
255 if (SetupDiGetDevicePropertyW(devinfo, &devinfo_data,
256 &DEVPKEY_Device_Parent, &parent_type, NULL, 0, &len, 0) != false ||
257 GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
258 fido_log_debug("%s: SetupDiGetDevicePropertyW 1", __func__);
259 goto fail;
260 }
261
262 if ((parent = malloc(len)) == NULL) {
263 fido_log_debug("%s: malloc", __func__);
264 goto fail;
265 }
266
267 if (SetupDiGetDevicePropertyW(devinfo, &devinfo_data,
268 &DEVPKEY_Device_Parent, &parent_type, (PBYTE)parent, len, NULL,
269 0) == false) {
270 fido_log_debug("%s: SetupDiGetDevicePropertyW 2", __func__);
271 goto fail;
272 }
273
274 ok = wcsncmp(parent, L"USB\\", 4) == 0;
275fail:
276 free(parent);
277
278 return (ok);
279}
280#endif
281
139static int 282static int
140copy_info(fido_dev_info_t *di, const char *path) 283copy_info(fido_dev_info_t *di, HDEVINFO devinfo, DWORD idx,
284 SP_DEVICE_INTERFACE_DATA *ifdata)
141{ 285{
142 HANDLE dev = INVALID_HANDLE_VALUE; 286 HANDLE dev = INVALID_HANDLE_VALUE;
143 int ok = -1; 287 int ok = -1;
144 288
145 memset(di, 0, sizeof(*di)); 289 memset(di, 0, sizeof(*di));
146 290
147 dev = CreateFileA(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 291 if ((di->path = get_path(devinfo, ifdata)) == NULL) {
148 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 292 fido_log_debug("%s: get_path", __func__);
149 if (dev == INVALID_HANDLE_VALUE || is_fido(dev) == 0)
150 goto fail; 293 goto fail;
294 }
151 295
152 if (get_int(dev, &di->vendor_id, &di->product_id) < 0 || 296 fido_log_debug("%s: path=%s", __func__, di->path);
153 get_str(dev, &di->manufacturer, &di->product) < 0) 297
298#ifndef FIDO_HID_ANY
299 if (hid_ok(devinfo, idx) == false) {
300 fido_log_debug("%s: hid_ok", __func__);
154 goto fail; 301 goto fail;
302 }
303#endif
155 304
156 if ((di->path = strdup(path)) == NULL) 305 dev = CreateFileA(di->path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
306 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
307 if (dev == INVALID_HANDLE_VALUE) {
308 fido_log_debug("%s: CreateFileA", __func__);
157 goto fail; 309 goto fail;
310 }
311
312 if (is_fido(dev) == false) {
313 fido_log_debug("%s: is_fido", __func__);
314 goto fail;
315 }
316
317 if (get_int(dev, &di->vendor_id, &di->product_id) < 0 ||
318 get_str(dev, &di->manufacturer, &di->product) < 0) {
319 fido_log_debug("%s: get_int/get_str", __func__);
320 goto fail;
321 }
158 322
159 ok = 0; 323 ok = 0;
160fail: 324fail:
@@ -174,66 +338,30 @@ fail:
174int 338int
175fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) 339fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
176{ 340{
177 GUID hid_guid = GUID_DEVINTERFACE_HID; 341 GUID hid_guid = GUID_DEVINTERFACE_HID;
178 HDEVINFO devinfo = INVALID_HANDLE_VALUE; 342 HDEVINFO devinfo = INVALID_HANDLE_VALUE;
179 SP_DEVICE_INTERFACE_DATA ifdata; 343 SP_DEVICE_INTERFACE_DATA ifdata;
180 SP_DEVICE_INTERFACE_DETAIL_DATA_A *ifdetail = NULL; 344 DWORD idx;
181 DWORD len = 0; 345 int r = FIDO_ERR_INTERNAL;
182 DWORD idx = 0;
183 int r = FIDO_ERR_INTERNAL;
184 346
185 *olen = 0; 347 *olen = 0;
186 348
187 if (ilen == 0) 349 if (ilen == 0)
188 return (FIDO_OK); /* nothing to do */ 350 return (FIDO_OK); /* nothing to do */
189
190 if (devlist == NULL) 351 if (devlist == NULL)
191 return (FIDO_ERR_INVALID_ARGUMENT); 352 return (FIDO_ERR_INVALID_ARGUMENT);
192 353
193 devinfo = SetupDiGetClassDevsA(&hid_guid, NULL, NULL, 354 if ((devinfo = SetupDiGetClassDevsA(&hid_guid, NULL, NULL,
194 DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); 355 DIGCF_DEVICEINTERFACE | DIGCF_PRESENT)) == INVALID_HANDLE_VALUE) {
195 if (devinfo == INVALID_HANDLE_VALUE) {
196 fido_log_debug("%s: SetupDiGetClassDevsA", __func__); 356 fido_log_debug("%s: SetupDiGetClassDevsA", __func__);
197 goto fail; 357 goto fail;
198 } 358 }
199 359
200 ifdata.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); 360 ifdata.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
201 361
202 while (SetupDiEnumDeviceInterfaces(devinfo, NULL, &hid_guid, idx++, 362 for (idx = 0; SetupDiEnumDeviceInterfaces(devinfo, NULL, &hid_guid,
203 &ifdata) == true) { 363 idx, &ifdata) == true; idx++) {
204 /* 364 if (copy_info(&devlist[*olen], devinfo, idx, &ifdata) == 0) {
205 * "Get the required buffer size. Call
206 * SetupDiGetDeviceInterfaceDetail with a NULL
207 * DeviceInterfaceDetailData pointer, a
208 * DeviceInterfaceDetailDataSize of zero, and a valid
209 * RequiredSize variable. In response to such a call, this
210 * function returns the required buffer size at RequiredSize
211 * and fails with GetLastError returning
212 * ERROR_INSUFFICIENT_BUFFER."
213 */
214 if (SetupDiGetDeviceInterfaceDetailA(devinfo, &ifdata, NULL, 0,
215 &len, NULL) != false ||
216 GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
217 fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 1",
218 __func__);
219 goto fail;
220 }
221
222 if ((ifdetail = malloc(len)) == NULL) {
223 fido_log_debug("%s: malloc", __func__);
224 goto fail;
225 }
226
227 ifdetail->cbSize = sizeof(*ifdetail);
228
229 if (SetupDiGetDeviceInterfaceDetailA(devinfo, &ifdata, ifdetail,
230 len, NULL, NULL) == false) {
231 fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 2",
232 __func__);
233 goto fail;
234 }
235
236 if (copy_info(&devlist[*olen], ifdetail->DevicePath) == 0) {
237 devlist[*olen].io = (fido_dev_io_t) { 365 devlist[*olen].io = (fido_dev_io_t) {
238 fido_hid_open, 366 fido_hid_open,
239 fido_hid_close, 367 fido_hid_close,
@@ -243,9 +371,6 @@ fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
243 if (++(*olen) == ilen) 371 if (++(*olen) == ilen)
244 break; 372 break;
245 } 373 }
246
247 free(ifdetail);
248 ifdetail = NULL;
249 } 374 }
250 375
251 r = FIDO_OK; 376 r = FIDO_OK;
@@ -253,78 +378,153 @@ fail:
253 if (devinfo != INVALID_HANDLE_VALUE) 378 if (devinfo != INVALID_HANDLE_VALUE)
254 SetupDiDestroyDeviceInfoList(devinfo); 379 SetupDiDestroyDeviceInfoList(devinfo);
255 380
256 free(ifdetail);
257
258 return (r); 381 return (r);
259} 382}
260 383
261void * 384void *
262fido_hid_open(const char *path) 385fido_hid_open(const char *path)
263{ 386{
264 HANDLE dev; 387 struct hid_win *ctx;
388
389 if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
390 return (NULL);
265 391
266 dev = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, 392 ctx->dev = CreateFileA(path, GENERIC_READ | GENERIC_WRITE,
267 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 393 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
268 FILE_ATTRIBUTE_NORMAL, NULL); 394 FILE_FLAG_OVERLAPPED, NULL);
395
396 if (ctx->dev == INVALID_HANDLE_VALUE) {
397 free(ctx);
398 return (NULL);
399 }
269 400
270 if (dev == INVALID_HANDLE_VALUE) 401 if ((ctx->overlap.hEvent = CreateEventA(NULL, FALSE, FALSE,
402 NULL)) == NULL) {
403 fido_log_debug("%s: CreateEventA", __func__);
404 fido_hid_close(ctx);
271 return (NULL); 405 return (NULL);
406 }
272 407
273 return (dev); 408 if (get_report_len(ctx->dev, 0, &ctx->report_in_len) < 0 ||
409 get_report_len(ctx->dev, 1, &ctx->report_out_len) < 0) {
410 fido_log_debug("%s: get_report_len", __func__);
411 fido_hid_close(ctx);
412 return (NULL);
413 }
414
415 return (ctx);
274} 416}
275 417
276void 418void
277fido_hid_close(void *handle) 419fido_hid_close(void *handle)
278{ 420{
279 CloseHandle(handle); 421 struct hid_win *ctx = handle;
422
423 if (ctx->overlap.hEvent != NULL) {
424 if (ctx->report_pending) {
425 fido_log_debug("%s: report_pending", __func__);
426 CancelIo(ctx->dev);
427 }
428 CloseHandle(ctx->overlap.hEvent);
429 }
430
431 explicit_bzero(ctx->report, sizeof(ctx->report));
432 CloseHandle(ctx->dev);
433 free(ctx);
280} 434}
281 435
282int 436int
283fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) 437fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
284{ 438{
285 DWORD n; 439 struct hid_win *ctx = handle;
286 int r = -1; 440 DWORD n;
287 uint8_t report[REPORT_LEN];
288 441
289 (void)ms; /* XXX */ 442 if (len != ctx->report_in_len - 1 || len > sizeof(ctx->report) - 1) {
443 fido_log_debug("%s: len %zu", __func__, len);
444 return (-1);
445 }
446
447 if (ctx->report_pending == 0) {
448 memset(&ctx->report, 0, sizeof(ctx->report));
449 ResetEvent(ctx->overlap.hEvent);
450 if (ReadFile(ctx->dev, ctx->report, (DWORD)(len + 1), &n,
451 &ctx->overlap) == 0 && GetLastError() != ERROR_IO_PENDING) {
452 CancelIo(ctx->dev);
453 fido_log_debug("%s: ReadFile", __func__);
454 return (-1);
455 }
456 ctx->report_pending = 1;
457 }
290 458
291 memset(report, 0, sizeof(report)); 459 if (ms > -1 && WaitForSingleObject(ctx->overlap.hEvent,
460 (DWORD)ms) != WAIT_OBJECT_0)
461 return (0);
292 462
293 if (len != sizeof(report) - 1) { 463 ctx->report_pending = 0;
294 fido_log_debug("%s: invalid len", __func__); 464
465 if (GetOverlappedResult(ctx->dev, &ctx->overlap, &n, TRUE) == 0) {
466 fido_log_debug("%s: GetOverlappedResult", __func__);
295 return (-1); 467 return (-1);
296 } 468 }
297 469
298 if (ReadFile(handle, report, sizeof(report), &n, NULL) == false || 470 if (n != len + 1) {
299 n != sizeof(report)) { 471 fido_log_debug("%s: expected %zu, got %zu", __func__,
300 fido_log_debug("%s: ReadFile", __func__); 472 len + 1, (size_t)n);
301 goto fail; 473 return (-1);
302 } 474 }
303 475
304 r = sizeof(report) - 1; 476 memcpy(buf, ctx->report + 1, len);
305 memcpy(buf, report + 1, len); 477 explicit_bzero(ctx->report, sizeof(ctx->report));
306
307fail:
308 explicit_bzero(report, sizeof(report));
309 478
310 return (r); 479 return ((int)len);
311} 480}
312 481
313int 482int
314fido_hid_write(void *handle, const unsigned char *buf, size_t len) 483fido_hid_write(void *handle, const unsigned char *buf, size_t len)
315{ 484{
316 DWORD n; 485 struct hid_win *ctx = handle;
486 OVERLAPPED overlap;
487 DWORD n;
488
489 memset(&overlap, 0, sizeof(overlap));
317 490
318 if (len != REPORT_LEN) { 491 if (len != ctx->report_out_len) {
319 fido_log_debug("%s: invalid len", __func__); 492 fido_log_debug("%s: len %zu", __func__, len);
320 return (-1); 493 return (-1);
321 } 494 }
322 495
323 if (WriteFile(handle, buf, (DWORD)len, &n, NULL) == false || 496 if (WriteFile(ctx->dev, buf, (DWORD)len, NULL, &overlap) == 0 &&
324 n != REPORT_LEN) { 497 GetLastError() != ERROR_IO_PENDING) {
325 fido_log_debug("%s: WriteFile", __func__); 498 fido_log_debug("%s: WriteFile", __func__);
326 return (-1); 499 return (-1);
327 } 500 }
328 501
329 return (REPORT_LEN); 502 if (GetOverlappedResult(ctx->dev, &overlap, &n, TRUE) == 0) {
503 fido_log_debug("%s: GetOverlappedResult", __func__);
504 return (-1);
505 }
506
507 if (n != len) {
508 fido_log_debug("%s: expected %zu, got %zu", __func__, len,
509 (size_t)n);
510 return (-1);
511 }
512
513 return ((int)len);
514}
515
516size_t
517fido_hid_report_in_len(void *handle)
518{
519 struct hid_win *ctx = handle;
520
521 return (ctx->report_in_len - 1);
522}
523
524size_t
525fido_hid_report_out_len(void *handle)
526{
527 struct hid_win *ctx = handle;
528
529 return (ctx->report_out_len - 1);
330} 530}