1/*
2 * Copyright (c) 2019 Google LLC. All rights reserved.
3 * Use of this source code is governed by a BSD-style
4 * license that can be found in the LICENSE file.
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#ifdef __linux__
9#include <sys/ioctl.h>
10#include <linux/hidraw.h>
11#include <linux/input.h>
12#include <fcntl.h>
13#endif
14
15#include <errno.h>
16#include <hidapi.h>
17#include <wchar.h>
18
19#include "fido.h"
20
21struct hid_hidapi {
22	void *handle;
23	size_t report_in_len;
24	size_t report_out_len;
25};
26
27static size_t
28fido_wcslen(const wchar_t *wcs)
29{
30	size_t l = 0;
31	while (*wcs++ != L'\0')
32		l++;
33	return l;
34}
35
36static char *
37wcs_to_cs(const wchar_t *wcs)
38{
39	char *cs;
40	size_t i;
41
42	if (wcs == NULL || (cs = calloc(fido_wcslen(wcs) + 1, 1)) == NULL)
43		return NULL;
44
45	for (i = 0; i < fido_wcslen(wcs); i++) {
46		if (wcs[i] >= 128) {
47			/* give up on parsing non-ASCII text */
48			free(cs);
49			return strdup("hidapi device");
50		}
51		cs[i] = (char)wcs[i];
52	}
53
54	return cs;
55}
56
57static int
58copy_info(fido_dev_info_t *di, const struct hid_device_info *d)
59{
60	memset(di, 0, sizeof(*di));
61
62	if (d->path != NULL)
63		di->path = strdup(d->path);
64	else
65		di->path = strdup("");
66
67	if (d->manufacturer_string != NULL)
68		di->manufacturer = wcs_to_cs(d->manufacturer_string);
69	else
70		di->manufacturer = strdup("");
71
72	if (d->product_string != NULL)
73		di->product = wcs_to_cs(d->product_string);
74	else
75		di->product = strdup("");
76
77	if (di->path == NULL ||
78	    di->manufacturer == NULL ||
79	    di->product == NULL) {
80		free(di->path);
81		free(di->manufacturer);
82		free(di->product);
83		explicit_bzero(di, sizeof(*di));
84		return -1;
85	}
86
87	di->product_id = (int16_t)d->product_id;
88	di->vendor_id = (int16_t)d->vendor_id;
89	di->io = (fido_dev_io_t) {
90		&fido_hid_open,
91		&fido_hid_close,
92		&fido_hid_read,
93		&fido_hid_write,
94	};
95
96	return 0;
97}
98
99#ifdef __linux__
100static int
101get_report_descriptor(const char *path, struct hidraw_report_descriptor *hrd)
102{
103	int fd;
104	int s = -1;
105	int ok = -1;
106
107	if ((fd = fido_hid_unix_open(path)) == -1) {
108		fido_log_debug("%s: fido_hid_unix_open", __func__);
109		return -1;
110	}
111
112	if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESCSIZE), &s) < 0 || s < 0 ||
113	    (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) {
114		fido_log_error(errno, "%s: ioctl HIDIOCGRDESCSIZE", __func__);
115		goto fail;
116	}
117
118	hrd->size = (unsigned)s;
119
120	if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESC), hrd) < 0) {
121		fido_log_error(errno, "%s: ioctl HIDIOCGRDESC", __func__);
122		goto fail;
123	}
124
125	ok = 0;
126fail:
127	if (fd != -1)
128		close(fd);
129
130	return ok;
131}
132
133static bool
134is_fido(const struct hid_device_info *hdi)
135{
136	uint32_t usage_page = 0;
137	struct hidraw_report_descriptor *hrd;
138
139	if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
140	    get_report_descriptor(hdi->path, hrd) < 0 ||
141	    fido_hid_get_usage(hrd->value, hrd->size, &usage_page) < 0)
142		usage_page = 0;
143
144	free(hrd);
145
146	return usage_page == 0xf1d0;
147}
148#elif defined(_WIN32) || defined(__APPLE__)
149static bool
150is_fido(const struct hid_device_info *hdi)
151{
152	return hdi->usage_page == 0xf1d0;
153}
154#else
155static bool
156is_fido(const struct hid_device_info *hdi)
157{
158	(void)hdi;
159	fido_log_debug("%s: assuming FIDO HID", __func__);
160	return true;
161}
162#endif
163
164void *
165fido_hid_open(const char *path)
166{
167	struct hid_hidapi *ctx;
168
169	if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
170		return (NULL);
171	}
172
173	if ((ctx->handle = hid_open_path(path)) == NULL) {
174		free(ctx);
175		return (NULL);
176	}
177
178	ctx->report_in_len = ctx->report_out_len = CTAP_MAX_REPORT_LEN;
179
180	return ctx;
181}
182
183void
184fido_hid_close(void *handle)
185{
186	struct hid_hidapi *ctx = handle;
187
188	hid_close(ctx->handle);
189	free(ctx);
190}
191
192int
193fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
194{
195	(void)handle;
196	(void)sigmask;
197
198	return (FIDO_ERR_INTERNAL);
199}
200
201int
202fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
203{
204	struct hid_hidapi *ctx = handle;
205
206	if (len != ctx->report_in_len) {
207		fido_log_debug("%s: len %zu", __func__, len);
208		return -1;
209	}
210
211	return hid_read_timeout(ctx->handle, buf, len, ms);
212}
213
214int
215fido_hid_write(void *handle, const unsigned char *buf, size_t len)
216{
217	struct hid_hidapi *ctx = handle;
218
219	if (len != ctx->report_out_len + 1) {
220		fido_log_debug("%s: len %zu", __func__, len);
221		return -1;
222	}
223
224	return hid_write(ctx->handle, buf, len);
225}
226
227int
228fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
229{
230	struct hid_device_info *hdi;
231
232	*olen = 0;
233
234	if (ilen == 0)
235		return FIDO_OK; /* nothing to do */
236	if (devlist == NULL)
237		return FIDO_ERR_INVALID_ARGUMENT;
238	if ((hdi = hid_enumerate(0, 0)) == NULL)
239		return FIDO_OK; /* nothing to do */
240
241	for (struct hid_device_info *d = hdi; d != NULL; d = d->next) {
242		if (is_fido(d) == false)
243			continue;
244		if (copy_info(&devlist[*olen], d) == 0) {
245			if (++(*olen) == ilen)
246				break;
247		}
248	}
249
250	hid_free_enumeration(hdi);
251
252	return FIDO_OK;
253}
254
255size_t
256fido_hid_report_in_len(void *handle)
257{
258	struct hid_hidapi *ctx = handle;
259
260	return (ctx->report_in_len);
261}
262
263size_t
264fido_hid_report_out_len(void *handle)
265{
266	struct hid_hidapi *ctx = handle;
267
268	return (ctx->report_out_len);
269}
270