summaryrefslogtreecommitdiff
path: root/src/hid_osx.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/hid_osx.c')
-rw-r--r--src/hid_osx.c323
1 files changed, 230 insertions, 93 deletions
diff --git a/src/hid_osx.c b/src/hid_osx.c
index 5c40747..6be5cd7 100644
--- a/src/hid_osx.c
+++ b/src/hid_osx.c
@@ -19,11 +19,13 @@
19 19
20#include "fido.h" 20#include "fido.h"
21 21
22#define REPORT_LEN 65 22struct hid_osx {
23
24struct dev {
25 IOHIDDeviceRef ref; 23 IOHIDDeviceRef ref;
26 CFStringRef loop_id; 24 CFStringRef loop_id;
25 int report_pipe[2];
26 size_t report_in_len;
27 size_t report_out_len;
28 unsigned char report[CTAP_MAX_REPORT_LEN];
27}; 29};
28 30
29static int 31static int
@@ -64,7 +66,8 @@ get_utf8(IOHIDDeviceRef dev, CFStringRef key, void *buf, size_t len)
64 return (-1); 66 return (-1);
65 } 67 }
66 68
67 if (CFStringGetCString(ref, buf, len, kCFStringEncodingUTF8) == false) { 69 if (CFStringGetCString(ref, buf, (long)len,
70 kCFStringEncodingUTF8) == false) {
68 fido_log_debug("%s: CFStringGetCString", __func__); 71 fido_log_debug("%s: CFStringGetCString", __func__);
69 return (-1); 72 return (-1);
70 } 73 }
@@ -72,30 +75,35 @@ get_utf8(IOHIDDeviceRef dev, CFStringRef key, void *buf, size_t len)
72 return (0); 75 return (0);
73} 76}
74 77
75static bool 78static int
76is_fido(IOHIDDeviceRef dev) 79get_report_len(IOHIDDeviceRef dev, int dir, size_t *report_len)
77{ 80{
78 uint32_t usage_page; 81 CFStringRef key;
79 int32_t report_len; 82 int32_t v;
80 83
81 if (get_int32(dev, CFSTR(kIOHIDPrimaryUsagePageKey), 84 if (dir == 0)
82 (int32_t *)&usage_page) != 0 || usage_page != 0xf1d0) 85 key = CFSTR(kIOHIDMaxInputReportSizeKey);
83 return (false); 86 else
87 key = CFSTR(kIOHIDMaxOutputReportSizeKey);
84 88
85 if (get_int32(dev, CFSTR(kIOHIDMaxInputReportSizeKey), 89 if (get_int32(dev, key, &v) < 0) {
86 &report_len) < 0 || report_len != REPORT_LEN - 1) { 90 fido_log_debug("%s: get_int32/%d", __func__, dir);
87 fido_log_debug("%s: unsupported report len", __func__); 91 return (-1);
88 return (false);
89 } 92 }
90 93
91 return (true); 94 if ((*report_len = (size_t)v) > CTAP_MAX_REPORT_LEN) {
95 fido_log_debug("%s: report_len=%zu", __func__, *report_len);
96 return (-1);
97 }
98
99 return (0);
92} 100}
93 101
94static int 102static int
95get_id(IOHIDDeviceRef dev, int16_t *vendor_id, int16_t *product_id) 103get_id(IOHIDDeviceRef dev, int16_t *vendor_id, int16_t *product_id)
96{ 104{
97 int32_t vendor; 105 int32_t vendor;
98 int32_t product; 106 int32_t product;
99 107
100 if (get_int32(dev, CFSTR(kIOHIDVendorIDKey), &vendor) < 0 || 108 if (get_int32(dev, CFSTR(kIOHIDVendorIDKey), &vendor) < 0 ||
101 vendor > UINT16_MAX) { 109 vendor > UINT16_MAX) {
@@ -175,6 +183,31 @@ get_path(IOHIDDeviceRef dev)
175 return (strdup(path)); 183 return (strdup(path));
176} 184}
177 185
186static bool
187is_fido(IOHIDDeviceRef dev)
188{
189 char buf[32];
190 uint32_t usage_page;
191
192 if (get_int32(dev, CFSTR(kIOHIDPrimaryUsagePageKey),
193 (int32_t *)&usage_page) < 0 || usage_page != 0xf1d0)
194 return (false);
195
196 if (get_utf8(dev, CFSTR(kIOHIDTransportKey), buf, sizeof(buf)) < 0) {
197 fido_log_debug("%s: get_utf8 transport", __func__);
198 return (false);
199 }
200
201#ifndef FIDO_HID_ANY
202 if (strcasecmp(buf, "usb") != 0) {
203 fido_log_debug("%s: transport", __func__);
204 return (false);
205 }
206#endif
207
208 return (true);
209}
210
178static int 211static int
179copy_info(fido_dev_info_t *di, IOHIDDeviceRef dev) 212copy_info(fido_dev_info_t *di, IOHIDDeviceRef dev)
180{ 213{
@@ -199,11 +232,12 @@ copy_info(fido_dev_info_t *di, IOHIDDeviceRef dev)
199int 232int
200fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) 233fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
201{ 234{
202 IOHIDManagerRef manager = NULL; 235 IOHIDManagerRef manager = NULL;
203 CFSetRef devset = NULL; 236 CFSetRef devset = NULL;
204 CFIndex devcnt; 237 size_t devcnt;
205 IOHIDDeviceRef *devs = NULL; 238 CFIndex n;
206 int r = FIDO_ERR_INTERNAL; 239 IOHIDDeviceRef *devs = NULL;
240 int r = FIDO_ERR_INTERNAL;
207 241
208 *olen = 0; 242 *olen = 0;
209 243
@@ -226,11 +260,13 @@ fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
226 goto fail; 260 goto fail;
227 } 261 }
228 262
229 if ((devcnt = CFSetGetCount(devset)) < 0) { 263 if ((n = CFSetGetCount(devset)) < 0) {
230 fido_log_debug("%s: CFSetGetCount", __func__); 264 fido_log_debug("%s: CFSetGetCount", __func__);
231 goto fail; 265 goto fail;
232 } 266 }
233 267
268 devcnt = (size_t)n;
269
234 if ((devs = calloc(devcnt, sizeof(*devs))) == NULL) { 270 if ((devs = calloc(devcnt, sizeof(*devs))) == NULL) {
235 fido_log_debug("%s: calloc", __func__); 271 fido_log_debug("%s: calloc", __func__);
236 goto fail; 272 goto fail;
@@ -238,7 +274,7 @@ fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
238 274
239 CFSetGetValues(devset, (void *)devs); 275 CFSetGetValues(devset, (void *)devs);
240 276
241 for (CFIndex i = 0; i < devcnt; i++) { 277 for (size_t i = 0; i < devcnt; i++) {
242 if (copy_info(&devlist[*olen], devs[i]) == 0) { 278 if (copy_info(&devlist[*olen], devs[i]) == 0) {
243 devlist[*olen].io = (fido_dev_io_t) { 279 devlist[*olen].io = (fido_dev_io_t) {
244 fido_hid_open, 280 fido_hid_open,
@@ -263,157 +299,258 @@ fail:
263 return (r); 299 return (r);
264} 300}
265 301
302static void
303report_callback(void *context, IOReturn result, void *dev, IOHIDReportType type,
304 uint32_t id, uint8_t *ptr, CFIndex len)
305{
306 struct hid_osx *ctx = context;
307 ssize_t r;
308
309 (void)dev;
310
311 if (result != kIOReturnSuccess || type != kIOHIDReportTypeInput ||
312 id != 0 || len < 0 || (size_t)len != ctx->report_in_len) {
313 fido_log_debug("%s: io error", __func__);
314 return;
315 }
316
317 if ((r = write(ctx->report_pipe[1], ptr, (size_t)len)) < 0 ||
318 (size_t)r != (size_t)len) {
319 fido_log_debug("%s: write", __func__);
320 return;
321 }
322}
323
324static void
325removal_callback(void *context, IOReturn result, void *sender)
326{
327 (void)context;
328 (void)result;
329 (void)sender;
330
331 CFRunLoopStop(CFRunLoopGetMain());
332}
333
334static int
335set_nonblock(int fd)
336{
337 int flags;
338
339 if ((flags = fcntl(fd, F_GETFL)) == -1) {
340 fido_log_debug("%s: fcntl F_GETFL", __func__);
341 return (-1);
342 }
343
344 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
345 fido_log_debug("%s: fcntl, F_SETFL", __func__);
346 return (-1);
347 }
348
349 return (0);
350}
351
352static int
353disable_sigpipe(int fd)
354{
355 int disabled = 1;
356
357 if (fcntl(fd, F_SETNOSIGPIPE, &disabled) == -1) {
358 fido_log_debug("%s: fcntl F_SETNOSIGPIPE", __func__);
359 return (-1);
360 }
361
362 return (0);
363}
364
266void * 365void *
267fido_hid_open(const char *path) 366fido_hid_open(const char *path)
268{ 367{
368 struct hid_osx *ctx;
269 io_registry_entry_t entry = MACH_PORT_NULL; 369 io_registry_entry_t entry = MACH_PORT_NULL;
270 struct dev *dev = NULL; 370 char loop_id[32];
271 int ok = -1; 371 int ok = -1;
272 int r; 372 int r;
273 char loop_id[32];
274 373
275 if ((dev = calloc(1, sizeof(*dev))) == NULL) { 374 if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
276 fido_log_debug("%s: calloc", __func__); 375 fido_log_debug("%s: calloc", __func__);
277 goto fail; 376 goto fail;
278 } 377 }
279 378
379 ctx->report_pipe[0] = -1;
380 ctx->report_pipe[1] = -1;
381
382 if (pipe(ctx->report_pipe) == -1) {
383 fido_log_debug("%s: pipe", __func__);
384 goto fail;
385 }
386
387 if (set_nonblock(ctx->report_pipe[0]) < 0 ||
388 set_nonblock(ctx->report_pipe[1]) < 0) {
389 fido_log_debug("%s: set_nonblock", __func__);
390 goto fail;
391 }
392
393 if (disable_sigpipe(ctx->report_pipe[1]) < 0) {
394 fido_log_debug("%s: disable_sigpipe", __func__);
395 goto fail;
396 }
397
280 if ((entry = IORegistryEntryFromPath(kIOMasterPortDefault, 398 if ((entry = IORegistryEntryFromPath(kIOMasterPortDefault,
281 path)) == MACH_PORT_NULL) { 399 path)) == MACH_PORT_NULL) {
282 fido_log_debug("%s: IORegistryEntryFromPath", __func__); 400 fido_log_debug("%s: IORegistryEntryFromPath", __func__);
283 goto fail; 401 goto fail;
284 } 402 }
285 403
286 if ((dev->ref = IOHIDDeviceCreate(kCFAllocatorDefault, 404 if ((ctx->ref = IOHIDDeviceCreate(kCFAllocatorDefault,
287 entry)) == NULL) { 405 entry)) == NULL) {
288 fido_log_debug("%s: IOHIDDeviceCreate", __func__); 406 fido_log_debug("%s: IOHIDDeviceCreate", __func__);
289 goto fail; 407 goto fail;
290 } 408 }
291 409
292 if (IOHIDDeviceOpen(dev->ref, 410 if (get_report_len(ctx->ref, 0, &ctx->report_in_len) < 0 ||
411 get_report_len(ctx->ref, 1, &ctx->report_out_len) < 0) {
412 fido_log_debug("%s: get_report_len", __func__);
413 goto fail;
414 }
415
416 if (ctx->report_in_len > sizeof(ctx->report)) {
417 fido_log_debug("%s: report_in_len=%zu", __func__,
418 ctx->report_in_len);
419 goto fail;
420 }
421
422 if (IOHIDDeviceOpen(ctx->ref,
293 kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) { 423 kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) {
294 fido_log_debug("%s: IOHIDDeviceOpen", __func__); 424 fido_log_debug("%s: IOHIDDeviceOpen", __func__);
295 goto fail; 425 goto fail;
296 } 426 }
297 427
298 if ((r = snprintf(loop_id, sizeof(loop_id), "fido2-%p", 428 if ((r = snprintf(loop_id, sizeof(loop_id), "fido2-%p",
299 (void *)dev->ref)) < 0 || (size_t)r >= sizeof(loop_id)) { 429 (void *)ctx->ref)) < 0 || (size_t)r >= sizeof(loop_id)) {
300 fido_log_debug("%s: snprintf", __func__); 430 fido_log_debug("%s: snprintf", __func__);
301 goto fail; 431 goto fail;
302 } 432 }
303 433
304 if ((dev->loop_id = CFStringCreateWithCString(NULL, loop_id, 434 if ((ctx->loop_id = CFStringCreateWithCString(NULL, loop_id,
305 kCFStringEncodingASCII)) == NULL) { 435 kCFStringEncodingASCII)) == NULL) {
306 fido_log_debug("%s: CFStringCreateWithCString", __func__); 436 fido_log_debug("%s: CFStringCreateWithCString", __func__);
307 goto fail; 437 goto fail;
308 } 438 }
309 439
440 IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report,
441 (long)ctx->report_in_len, &report_callback, ctx);
442 IOHIDDeviceRegisterRemovalCallback(ctx->ref, &removal_callback, ctx);
443 IOHIDDeviceScheduleWithRunLoop(ctx->ref, CFRunLoopGetMain(),
444 ctx->loop_id);
445
310 ok = 0; 446 ok = 0;
311fail: 447fail:
312 if (entry != MACH_PORT_NULL) 448 if (entry != MACH_PORT_NULL)
313 IOObjectRelease(entry); 449 IOObjectRelease(entry);
314 450
315 if (ok < 0 && dev != NULL) { 451 if (ok < 0 && ctx != NULL) {
316 if (dev->ref != NULL) 452 if (ctx->ref != NULL)
317 CFRelease(dev->ref); 453 CFRelease(ctx->ref);
318 if (dev->loop_id != NULL) 454 if (ctx->loop_id != NULL)
319 CFRelease(dev->loop_id); 455 CFRelease(ctx->loop_id);
320 free(dev); 456 if (ctx->report_pipe[0] != -1)
321 dev = NULL; 457 close(ctx->report_pipe[0]);
458 if (ctx->report_pipe[1] != -1)
459 close(ctx->report_pipe[1]);
460 free(ctx);
461 ctx = NULL;
322 } 462 }
323 463
324 return (dev); 464 return (ctx);
325} 465}
326 466
327void 467void
328fido_hid_close(void *handle) 468fido_hid_close(void *handle)
329{ 469{
330 struct dev *dev = handle; 470 struct hid_osx *ctx = handle;
471
472 IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report,
473 (long)ctx->report_in_len, NULL, ctx);
474 IOHIDDeviceRegisterRemovalCallback(ctx->ref, NULL, NULL);
475 IOHIDDeviceUnscheduleFromRunLoop(ctx->ref, CFRunLoopGetMain(),
476 ctx->loop_id);
331 477
332 if (IOHIDDeviceClose(dev->ref, 478 if (IOHIDDeviceClose(ctx->ref,
333 kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) 479 kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess)
334 fido_log_debug("%s: IOHIDDeviceClose", __func__); 480 fido_log_debug("%s: IOHIDDeviceClose", __func__);
335 481
336 CFRelease(dev->ref); 482 CFRelease(ctx->ref);
337 CFRelease(dev->loop_id); 483 CFRelease(ctx->loop_id);
338
339 free(dev);
340}
341 484
342static void 485 explicit_bzero(ctx->report, sizeof(ctx->report));
343read_callback(void *context, IOReturn result, void *dev, IOHIDReportType type, 486 close(ctx->report_pipe[0]);
344 uint32_t report_id, uint8_t *report, CFIndex report_len) 487 close(ctx->report_pipe[1]);
345{
346 (void)context;
347 (void)dev;
348 (void)report;
349 488
350 if (result != kIOReturnSuccess || type != kIOHIDReportTypeInput || 489 free(ctx);
351 report_id != 0 || report_len != REPORT_LEN - 1) {
352 fido_log_debug("%s: io error", __func__);
353 }
354}
355
356static void
357removal_callback(void *context, IOReturn result, void *sender)
358{
359 (void)context;
360 (void)result;
361 (void)sender;
362
363 CFRunLoopStop(CFRunLoopGetCurrent());
364} 490}
365 491
366int 492int
367fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) 493fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
368{ 494{
369 struct dev *dev = handle; 495 struct hid_osx *ctx = handle;
370 CFRunLoopRunResult r; 496 ssize_t r;
371 497
372 (void)ms; /* XXX */ 498 explicit_bzero(buf, len);
499 explicit_bzero(ctx->report, sizeof(ctx->report));
373 500
374 if (len != REPORT_LEN - 1) { 501 if (len != ctx->report_in_len || len > sizeof(ctx->report)) {
375 fido_log_debug("%s: invalid len", __func__); 502 fido_log_debug("%s: len %zu", __func__, len);
376 return (-1); 503 return (-1);
377 } 504 }
378 505
379 explicit_bzero(buf, len); 506 if (ms == -1)
380 507 ms = 5000; /* wait 5 seconds by default */
381 IOHIDDeviceRegisterInputReportCallback(dev->ref, buf, len,
382 &read_callback, NULL);
383 IOHIDDeviceRegisterRemovalCallback(dev->ref, &removal_callback, dev);
384 IOHIDDeviceScheduleWithRunLoop(dev->ref, CFRunLoopGetCurrent(),
385 dev->loop_id);
386 508
387 r = CFRunLoopRunInMode(dev->loop_id, 0.3, true); 509 if (CFRunLoopGetCurrent() != CFRunLoopGetMain())
510 fido_log_debug("%s: CFRunLoopGetCurrent != CFRunLoopGetMain",
511 __func__);
388 512
389 IOHIDDeviceRegisterInputReportCallback(dev->ref, buf, len, NULL, NULL); 513 CFRunLoopRunInMode(ctx->loop_id, (double)ms/1000.0, true);
390 IOHIDDeviceRegisterRemovalCallback(dev->ref, NULL, NULL);
391 IOHIDDeviceUnscheduleFromRunLoop(dev->ref, CFRunLoopGetCurrent(),
392 dev->loop_id);
393 514
394 if (r != kCFRunLoopRunHandledSource) { 515 if ((r = read(ctx->report_pipe[0], buf, len)) < 0 || (size_t)r != len) {
395 fido_log_debug("%s: CFRunLoopRunInMode=%d", __func__, (int)r); 516 fido_log_debug("%s: read", __func__);
396 return (-1); 517 return (-1);
397 } 518 }
398 519
399 return (REPORT_LEN - 1); 520 return ((int)len);
400} 521}
401 522
402int 523int
403fido_hid_write(void *handle, const unsigned char *buf, size_t len) 524fido_hid_write(void *handle, const unsigned char *buf, size_t len)
404{ 525{
405 struct dev *dev = handle; 526 struct hid_osx *ctx = handle;
406 527
407 if (len != REPORT_LEN) { 528 if (len != ctx->report_out_len + 1 || len > LONG_MAX) {
408 fido_log_debug("%s: invalid len", __func__); 529 fido_log_debug("%s: len %zu", __func__, len);
409 return (-1); 530 return (-1);
410 } 531 }
411 532
412 if (IOHIDDeviceSetReport(dev->ref, kIOHIDReportTypeOutput, 0, buf + 1, 533 if (IOHIDDeviceSetReport(ctx->ref, kIOHIDReportTypeOutput, 0, buf + 1,
413 len - 1) != kIOReturnSuccess) { 534 (long)(len - 1)) != kIOReturnSuccess) {
414 fido_log_debug("%s: IOHIDDeviceSetReport", __func__); 535 fido_log_debug("%s: IOHIDDeviceSetReport", __func__);
415 return (-1); 536 return (-1);
416 } 537 }
417 538
418 return (REPORT_LEN); 539 return ((int)len);
540}
541
542size_t
543fido_hid_report_in_len(void *handle)
544{
545 struct hid_osx *ctx = handle;
546
547 return (ctx->report_in_len);
548}
549
550size_t
551fido_hid_report_out_len(void *handle)
552{
553 struct hid_osx *ctx = handle;
554
555 return (ctx->report_out_len);
419} 556}