xref: /freebsd/contrib/libfido2/src/hid_linux.c (revision 2ccfa855b2fc331819953e3de1b1c15ce5b95a7e)
10afa8e06SEd Maste /*
2*2ccfa855SEd Maste  * Copyright (c) 2019-2022 Yubico AB. All rights reserved.
30afa8e06SEd Maste  * Use of this source code is governed by a BSD-style
40afa8e06SEd Maste  * license that can be found in the LICENSE file.
5*2ccfa855SEd Maste  * SPDX-License-Identifier: BSD-2-Clause
60afa8e06SEd Maste  */
70afa8e06SEd Maste 
80afa8e06SEd Maste #include <sys/types.h>
90afa8e06SEd Maste #include <sys/file.h>
100afa8e06SEd Maste #include <sys/ioctl.h>
110afa8e06SEd Maste 
120afa8e06SEd Maste #include <linux/hidraw.h>
130afa8e06SEd Maste #include <linux/input.h>
140afa8e06SEd Maste 
150afa8e06SEd Maste #include <errno.h>
160afa8e06SEd Maste #include <libudev.h>
170afa8e06SEd Maste #include <time.h>
180afa8e06SEd Maste #include <unistd.h>
190afa8e06SEd Maste 
200afa8e06SEd Maste #include "fido.h"
210afa8e06SEd Maste 
220afa8e06SEd Maste struct hid_linux {
230afa8e06SEd Maste 	int             fd;
240afa8e06SEd Maste 	size_t          report_in_len;
250afa8e06SEd Maste 	size_t          report_out_len;
260afa8e06SEd Maste 	sigset_t        sigmask;
270afa8e06SEd Maste 	const sigset_t *sigmaskp;
280afa8e06SEd Maste };
290afa8e06SEd Maste 
300afa8e06SEd Maste static int
get_report_descriptor(int fd,struct hidraw_report_descriptor * hrd)310afa8e06SEd Maste get_report_descriptor(int fd, struct hidraw_report_descriptor *hrd)
320afa8e06SEd Maste {
330afa8e06SEd Maste 	int s = -1;
340afa8e06SEd Maste 
350afa8e06SEd Maste 	if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESCSIZE), &s) == -1) {
360afa8e06SEd Maste 		fido_log_error(errno, "%s: ioctl HIDIOCGRDESCSIZE", __func__);
370afa8e06SEd Maste 		return (-1);
380afa8e06SEd Maste 	}
390afa8e06SEd Maste 
400afa8e06SEd Maste 	if (s < 0 || (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) {
410afa8e06SEd Maste 		fido_log_debug("%s: HIDIOCGRDESCSIZE %d", __func__, s);
420afa8e06SEd Maste 		return (-1);
430afa8e06SEd Maste 	}
440afa8e06SEd Maste 
450afa8e06SEd Maste 	hrd->size = (unsigned)s;
460afa8e06SEd Maste 
470afa8e06SEd Maste 	if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESC), hrd) == -1) {
480afa8e06SEd Maste 		fido_log_error(errno, "%s: ioctl HIDIOCGRDESC", __func__);
490afa8e06SEd Maste 		return (-1);
500afa8e06SEd Maste 	}
510afa8e06SEd Maste 
520afa8e06SEd Maste 	return (0);
530afa8e06SEd Maste }
540afa8e06SEd Maste 
550afa8e06SEd Maste static bool
is_fido(const char * path)560afa8e06SEd Maste is_fido(const char *path)
570afa8e06SEd Maste {
58*2ccfa855SEd Maste 	int				 fd = -1;
590afa8e06SEd Maste 	uint32_t			 usage_page = 0;
60*2ccfa855SEd Maste 	struct hidraw_report_descriptor	*hrd = NULL;
610afa8e06SEd Maste 
62*2ccfa855SEd Maste 	if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
63*2ccfa855SEd Maste 	    (fd = fido_hid_unix_open(path)) == -1)
64*2ccfa855SEd Maste 		goto out;
65*2ccfa855SEd Maste 	if (get_report_descriptor(fd, hrd) < 0 ||
66*2ccfa855SEd Maste 	    fido_hid_get_usage(hrd->value, hrd->size, &usage_page) < 0)
670afa8e06SEd Maste 		usage_page = 0;
680afa8e06SEd Maste 
69*2ccfa855SEd Maste out:
70*2ccfa855SEd Maste 	free(hrd);
71*2ccfa855SEd Maste 
72*2ccfa855SEd Maste 	if (fd != -1 && close(fd) == -1)
730afa8e06SEd Maste 		fido_log_error(errno, "%s: close", __func__);
740afa8e06SEd Maste 
750afa8e06SEd Maste 	return (usage_page == 0xf1d0);
760afa8e06SEd Maste }
770afa8e06SEd Maste 
780afa8e06SEd Maste static int
parse_uevent(const char * uevent,int * bus,int16_t * vendor_id,int16_t * product_id)790afa8e06SEd Maste parse_uevent(const char *uevent, int *bus, int16_t *vendor_id,
800afa8e06SEd Maste     int16_t *product_id)
810afa8e06SEd Maste {
820afa8e06SEd Maste 	char			*cp;
830afa8e06SEd Maste 	char			*p;
840afa8e06SEd Maste 	char			*s;
850afa8e06SEd Maste 	int			 ok = -1;
860afa8e06SEd Maste 	short unsigned int	 x;
870afa8e06SEd Maste 	short unsigned int	 y;
880afa8e06SEd Maste 	short unsigned int	 z;
890afa8e06SEd Maste 
900afa8e06SEd Maste 	if ((s = cp = strdup(uevent)) == NULL)
910afa8e06SEd Maste 		return (-1);
920afa8e06SEd Maste 
930afa8e06SEd Maste 	while ((p = strsep(&cp, "\n")) != NULL && *p != '\0') {
940afa8e06SEd Maste 		if (strncmp(p, "HID_ID=", 7) == 0) {
950afa8e06SEd Maste 			if (sscanf(p + 7, "%hx:%hx:%hx", &x, &y, &z) == 3) {
960afa8e06SEd Maste 				*bus = (int)x;
970afa8e06SEd Maste 				*vendor_id = (int16_t)y;
980afa8e06SEd Maste 				*product_id = (int16_t)z;
990afa8e06SEd Maste 				ok = 0;
1000afa8e06SEd Maste 				break;
1010afa8e06SEd Maste 			}
1020afa8e06SEd Maste 		}
1030afa8e06SEd Maste 	}
1040afa8e06SEd Maste 
1050afa8e06SEd Maste 	free(s);
1060afa8e06SEd Maste 
1070afa8e06SEd Maste 	return (ok);
1080afa8e06SEd Maste }
1090afa8e06SEd Maste 
1100afa8e06SEd Maste static char *
get_parent_attr(struct udev_device * dev,const char * subsystem,const char * devtype,const char * attr)1110afa8e06SEd Maste get_parent_attr(struct udev_device *dev, const char *subsystem,
1120afa8e06SEd Maste     const char *devtype, const char *attr)
1130afa8e06SEd Maste {
1140afa8e06SEd Maste 	struct udev_device	*parent;
1150afa8e06SEd Maste 	const char		*value;
1160afa8e06SEd Maste 
1170afa8e06SEd Maste 	if ((parent = udev_device_get_parent_with_subsystem_devtype(dev,
1180afa8e06SEd Maste 	    subsystem, devtype)) == NULL || (value =
1190afa8e06SEd Maste 	    udev_device_get_sysattr_value(parent, attr)) == NULL)
1200afa8e06SEd Maste 		return (NULL);
1210afa8e06SEd Maste 
1220afa8e06SEd Maste 	return (strdup(value));
1230afa8e06SEd Maste }
1240afa8e06SEd Maste 
1250afa8e06SEd Maste static char *
get_usb_attr(struct udev_device * dev,const char * attr)1260afa8e06SEd Maste get_usb_attr(struct udev_device *dev, const char *attr)
1270afa8e06SEd Maste {
1280afa8e06SEd Maste 	return (get_parent_attr(dev, "usb", "usb_device", attr));
1290afa8e06SEd Maste }
1300afa8e06SEd Maste 
1310afa8e06SEd Maste static int
copy_info(fido_dev_info_t * di,struct udev * udev,struct udev_list_entry * udev_entry)1320afa8e06SEd Maste copy_info(fido_dev_info_t *di, struct udev *udev,
1330afa8e06SEd Maste     struct udev_list_entry *udev_entry)
1340afa8e06SEd Maste {
1350afa8e06SEd Maste 	const char		*name;
1360afa8e06SEd Maste 	const char		*path;
1370afa8e06SEd Maste 	char			*uevent = NULL;
1380afa8e06SEd Maste 	struct udev_device	*dev = NULL;
1390afa8e06SEd Maste 	int			 bus = 0;
1400afa8e06SEd Maste 	int			 ok = -1;
1410afa8e06SEd Maste 
1420afa8e06SEd Maste 	memset(di, 0, sizeof(*di));
1430afa8e06SEd Maste 
1440afa8e06SEd Maste 	if ((name = udev_list_entry_get_name(udev_entry)) == NULL ||
1450afa8e06SEd Maste 	    (dev = udev_device_new_from_syspath(udev, name)) == NULL ||
1460afa8e06SEd Maste 	    (path = udev_device_get_devnode(dev)) == NULL ||
1470afa8e06SEd Maste 	    is_fido(path) == 0)
1480afa8e06SEd Maste 		goto fail;
1490afa8e06SEd Maste 
1500afa8e06SEd Maste 	if ((uevent = get_parent_attr(dev, "hid", NULL, "uevent")) == NULL ||
1510afa8e06SEd Maste 	    parse_uevent(uevent, &bus, &di->vendor_id, &di->product_id) < 0) {
1520afa8e06SEd Maste 		fido_log_debug("%s: uevent", __func__);
1530afa8e06SEd Maste 		goto fail;
1540afa8e06SEd Maste 	}
1550afa8e06SEd Maste 
1560afa8e06SEd Maste #ifndef FIDO_HID_ANY
1570afa8e06SEd Maste 	if (bus != BUS_USB) {
1580afa8e06SEd Maste 		fido_log_debug("%s: bus", __func__);
1590afa8e06SEd Maste 		goto fail;
1600afa8e06SEd Maste 	}
1610afa8e06SEd Maste #endif
1620afa8e06SEd Maste 
1630afa8e06SEd Maste 	di->path = strdup(path);
1640afa8e06SEd Maste 	if ((di->manufacturer = get_usb_attr(dev, "manufacturer")) == NULL)
165f540a430SEd Maste 		di->manufacturer = strdup("");
1660afa8e06SEd Maste 	if ((di->product = get_usb_attr(dev, "product")) == NULL)
167f540a430SEd Maste 		di->product = strdup("");
1680afa8e06SEd Maste 	if (di->path == NULL || di->manufacturer == NULL || di->product == NULL)
1690afa8e06SEd Maste 		goto fail;
1700afa8e06SEd Maste 
1710afa8e06SEd Maste 	ok = 0;
1720afa8e06SEd Maste fail:
1730afa8e06SEd Maste 	if (dev != NULL)
1740afa8e06SEd Maste 		udev_device_unref(dev);
1750afa8e06SEd Maste 
1760afa8e06SEd Maste 	free(uevent);
1770afa8e06SEd Maste 
1780afa8e06SEd Maste 	if (ok < 0) {
1790afa8e06SEd Maste 		free(di->path);
1800afa8e06SEd Maste 		free(di->manufacturer);
1810afa8e06SEd Maste 		free(di->product);
1820afa8e06SEd Maste 		explicit_bzero(di, sizeof(*di));
1830afa8e06SEd Maste 	}
1840afa8e06SEd Maste 
1850afa8e06SEd Maste 	return (ok);
1860afa8e06SEd Maste }
1870afa8e06SEd Maste 
1880afa8e06SEd Maste int
fido_hid_manifest(fido_dev_info_t * devlist,size_t ilen,size_t * olen)1890afa8e06SEd Maste fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
1900afa8e06SEd Maste {
1910afa8e06SEd Maste 	struct udev		*udev = NULL;
1920afa8e06SEd Maste 	struct udev_enumerate	*udev_enum = NULL;
1930afa8e06SEd Maste 	struct udev_list_entry	*udev_list;
1940afa8e06SEd Maste 	struct udev_list_entry	*udev_entry;
1950afa8e06SEd Maste 	int			 r = FIDO_ERR_INTERNAL;
1960afa8e06SEd Maste 
1970afa8e06SEd Maste 	*olen = 0;
1980afa8e06SEd Maste 
1990afa8e06SEd Maste 	if (ilen == 0)
2000afa8e06SEd Maste 		return (FIDO_OK); /* nothing to do */
2010afa8e06SEd Maste 
2020afa8e06SEd Maste 	if (devlist == NULL)
2030afa8e06SEd Maste 		return (FIDO_ERR_INVALID_ARGUMENT);
2040afa8e06SEd Maste 
2050afa8e06SEd Maste 	if ((udev = udev_new()) == NULL ||
2060afa8e06SEd Maste 	    (udev_enum = udev_enumerate_new(udev)) == NULL)
2070afa8e06SEd Maste 		goto fail;
2080afa8e06SEd Maste 
2090afa8e06SEd Maste 	if (udev_enumerate_add_match_subsystem(udev_enum, "hidraw") < 0 ||
2100afa8e06SEd Maste 	    udev_enumerate_scan_devices(udev_enum) < 0)
2110afa8e06SEd Maste 		goto fail;
2120afa8e06SEd Maste 
2130afa8e06SEd Maste 	if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) {
2140afa8e06SEd Maste 		r = FIDO_OK; /* zero hidraw devices */
2150afa8e06SEd Maste 		goto fail;
2160afa8e06SEd Maste 	}
2170afa8e06SEd Maste 
2180afa8e06SEd Maste 	udev_list_entry_foreach(udev_entry, udev_list) {
2190afa8e06SEd Maste 		if (copy_info(&devlist[*olen], udev, udev_entry) == 0) {
2200afa8e06SEd Maste 			devlist[*olen].io = (fido_dev_io_t) {
2210afa8e06SEd Maste 				fido_hid_open,
2220afa8e06SEd Maste 				fido_hid_close,
2230afa8e06SEd Maste 				fido_hid_read,
2240afa8e06SEd Maste 				fido_hid_write,
2250afa8e06SEd Maste 			};
2260afa8e06SEd Maste 			if (++(*olen) == ilen)
2270afa8e06SEd Maste 				break;
2280afa8e06SEd Maste 		}
2290afa8e06SEd Maste 	}
2300afa8e06SEd Maste 
2310afa8e06SEd Maste 	r = FIDO_OK;
2320afa8e06SEd Maste fail:
2330afa8e06SEd Maste 	if (udev_enum != NULL)
2340afa8e06SEd Maste 		udev_enumerate_unref(udev_enum);
2350afa8e06SEd Maste 	if (udev != NULL)
2360afa8e06SEd Maste 		udev_unref(udev);
2370afa8e06SEd Maste 
2380afa8e06SEd Maste 	return (r);
2390afa8e06SEd Maste }
2400afa8e06SEd Maste 
2410afa8e06SEd Maste void *
fido_hid_open(const char * path)2420afa8e06SEd Maste fido_hid_open(const char *path)
2430afa8e06SEd Maste {
2440afa8e06SEd Maste 	struct hid_linux *ctx;
245*2ccfa855SEd Maste 	struct hidraw_report_descriptor *hrd;
2460afa8e06SEd Maste 	struct timespec tv_pause;
2470afa8e06SEd Maste 	long interval_ms, retries = 0;
248*2ccfa855SEd Maste 	bool looped;
249*2ccfa855SEd Maste 
250*2ccfa855SEd Maste retry:
251*2ccfa855SEd Maste 	looped = false;
2520afa8e06SEd Maste 
2530afa8e06SEd Maste 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL ||
2540afa8e06SEd Maste 	    (ctx->fd = fido_hid_unix_open(path)) == -1) {
2550afa8e06SEd Maste 		free(ctx);
2560afa8e06SEd Maste 		return (NULL);
2570afa8e06SEd Maste 	}
2580afa8e06SEd Maste 
2590afa8e06SEd Maste 	while (flock(ctx->fd, LOCK_EX|LOCK_NB) == -1) {
2600afa8e06SEd Maste 		if (errno != EWOULDBLOCK) {
2610afa8e06SEd Maste 			fido_log_error(errno, "%s: flock", __func__);
2620afa8e06SEd Maste 			fido_hid_close(ctx);
2630afa8e06SEd Maste 			return (NULL);
2640afa8e06SEd Maste 		}
265*2ccfa855SEd Maste 		looped = true;
266*2ccfa855SEd Maste 		if (retries++ >= 20) {
2670afa8e06SEd Maste 			fido_log_debug("%s: flock timeout", __func__);
2680afa8e06SEd Maste 			fido_hid_close(ctx);
2690afa8e06SEd Maste 			return (NULL);
2700afa8e06SEd Maste 		}
2710afa8e06SEd Maste 		interval_ms = retries * 100000000L;
2720afa8e06SEd Maste 		tv_pause.tv_sec = interval_ms / 1000000000L;
2730afa8e06SEd Maste 		tv_pause.tv_nsec = interval_ms % 1000000000L;
2740afa8e06SEd Maste 		if (nanosleep(&tv_pause, NULL) == -1) {
2750afa8e06SEd Maste 			fido_log_error(errno, "%s: nanosleep", __func__);
2760afa8e06SEd Maste 			fido_hid_close(ctx);
2770afa8e06SEd Maste 			return (NULL);
2780afa8e06SEd Maste 		}
2790afa8e06SEd Maste 	}
2800afa8e06SEd Maste 
281*2ccfa855SEd Maste 	if (looped) {
282*2ccfa855SEd Maste 		fido_log_debug("%s: retrying", __func__);
283*2ccfa855SEd Maste 		fido_hid_close(ctx);
284*2ccfa855SEd Maste 		goto retry;
285*2ccfa855SEd Maste 	}
286*2ccfa855SEd Maste 
287*2ccfa855SEd Maste 	if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
288*2ccfa855SEd Maste 	    get_report_descriptor(ctx->fd, hrd) < 0 ||
289*2ccfa855SEd Maste 	    fido_hid_get_report_len(hrd->value, hrd->size, &ctx->report_in_len,
2900afa8e06SEd Maste 	    &ctx->report_out_len) < 0 || ctx->report_in_len == 0 ||
2910afa8e06SEd Maste 	    ctx->report_out_len == 0) {
2920afa8e06SEd Maste 		fido_log_debug("%s: using default report sizes", __func__);
2930afa8e06SEd Maste 		ctx->report_in_len = CTAP_MAX_REPORT_LEN;
2940afa8e06SEd Maste 		ctx->report_out_len = CTAP_MAX_REPORT_LEN;
2950afa8e06SEd Maste 	}
2960afa8e06SEd Maste 
297*2ccfa855SEd Maste 	free(hrd);
298*2ccfa855SEd Maste 
2990afa8e06SEd Maste 	return (ctx);
3000afa8e06SEd Maste }
3010afa8e06SEd Maste 
3020afa8e06SEd Maste void
fido_hid_close(void * handle)3030afa8e06SEd Maste fido_hid_close(void *handle)
3040afa8e06SEd Maste {
3050afa8e06SEd Maste 	struct hid_linux *ctx = handle;
3060afa8e06SEd Maste 
3070afa8e06SEd Maste 	if (close(ctx->fd) == -1)
3080afa8e06SEd Maste 		fido_log_error(errno, "%s: close", __func__);
3090afa8e06SEd Maste 
3100afa8e06SEd Maste 	free(ctx);
3110afa8e06SEd Maste }
3120afa8e06SEd Maste 
3130afa8e06SEd Maste int
fido_hid_set_sigmask(void * handle,const fido_sigset_t * sigmask)3140afa8e06SEd Maste fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
3150afa8e06SEd Maste {
3160afa8e06SEd Maste 	struct hid_linux *ctx = handle;
3170afa8e06SEd Maste 
3180afa8e06SEd Maste 	ctx->sigmask = *sigmask;
3190afa8e06SEd Maste 	ctx->sigmaskp = &ctx->sigmask;
3200afa8e06SEd Maste 
3210afa8e06SEd Maste 	return (FIDO_OK);
3220afa8e06SEd Maste }
3230afa8e06SEd Maste 
3240afa8e06SEd Maste int
fido_hid_read(void * handle,unsigned char * buf,size_t len,int ms)3250afa8e06SEd Maste fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
3260afa8e06SEd Maste {
3270afa8e06SEd Maste 	struct hid_linux	*ctx = handle;
3280afa8e06SEd Maste 	ssize_t			 r;
3290afa8e06SEd Maste 
3300afa8e06SEd Maste 	if (len != ctx->report_in_len) {
3310afa8e06SEd Maste 		fido_log_debug("%s: len %zu", __func__, len);
3320afa8e06SEd Maste 		return (-1);
3330afa8e06SEd Maste 	}
3340afa8e06SEd Maste 
3350afa8e06SEd Maste 	if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
3360afa8e06SEd Maste 		fido_log_debug("%s: fd not ready", __func__);
3370afa8e06SEd Maste 		return (-1);
3380afa8e06SEd Maste 	}
3390afa8e06SEd Maste 
3400afa8e06SEd Maste 	if ((r = read(ctx->fd, buf, len)) == -1) {
3410afa8e06SEd Maste 		fido_log_error(errno, "%s: read", __func__);
3420afa8e06SEd Maste 		return (-1);
3430afa8e06SEd Maste 	}
3440afa8e06SEd Maste 
3450afa8e06SEd Maste 	if (r < 0 || (size_t)r != len) {
3460afa8e06SEd Maste 		fido_log_debug("%s: %zd != %zu", __func__, r, len);
3470afa8e06SEd Maste 		return (-1);
3480afa8e06SEd Maste 	}
3490afa8e06SEd Maste 
3500afa8e06SEd Maste 	return ((int)r);
3510afa8e06SEd Maste }
3520afa8e06SEd Maste 
3530afa8e06SEd Maste int
fido_hid_write(void * handle,const unsigned char * buf,size_t len)3540afa8e06SEd Maste fido_hid_write(void *handle, const unsigned char *buf, size_t len)
3550afa8e06SEd Maste {
3560afa8e06SEd Maste 	struct hid_linux	*ctx = handle;
3570afa8e06SEd Maste 	ssize_t			 r;
3580afa8e06SEd Maste 
3590afa8e06SEd Maste 	if (len != ctx->report_out_len + 1) {
3600afa8e06SEd Maste 		fido_log_debug("%s: len %zu", __func__, len);
3610afa8e06SEd Maste 		return (-1);
3620afa8e06SEd Maste 	}
3630afa8e06SEd Maste 
3640afa8e06SEd Maste 	if ((r = write(ctx->fd, buf, len)) == -1) {
3650afa8e06SEd Maste 		fido_log_error(errno, "%s: write", __func__);
3660afa8e06SEd Maste 		return (-1);
3670afa8e06SEd Maste 	}
3680afa8e06SEd Maste 
3690afa8e06SEd Maste 	if (r < 0 || (size_t)r != len) {
3700afa8e06SEd Maste 		fido_log_debug("%s: %zd != %zu", __func__, r, len);
3710afa8e06SEd Maste 		return (-1);
3720afa8e06SEd Maste 	}
3730afa8e06SEd Maste 
3740afa8e06SEd Maste 	return ((int)r);
3750afa8e06SEd Maste }
3760afa8e06SEd Maste 
3770afa8e06SEd Maste size_t
fido_hid_report_in_len(void * handle)3780afa8e06SEd Maste fido_hid_report_in_len(void *handle)
3790afa8e06SEd Maste {
3800afa8e06SEd Maste 	struct hid_linux *ctx = handle;
3810afa8e06SEd Maste 
3820afa8e06SEd Maste 	return (ctx->report_in_len);
3830afa8e06SEd Maste }
3840afa8e06SEd Maste 
3850afa8e06SEd Maste size_t
fido_hid_report_out_len(void * handle)3860afa8e06SEd Maste fido_hid_report_out_len(void *handle)
3870afa8e06SEd Maste {
3880afa8e06SEd Maste 	struct hid_linux *ctx = handle;
3890afa8e06SEd Maste 
3900afa8e06SEd Maste 	return (ctx->report_out_len);
3910afa8e06SEd Maste }
392