xref: /freebsd/usr.sbin/bluetooth/bthidd/parser.y (revision 42b388439bd3795e09258c57a74ce9eec3651c7b)
1 %{
2 /*
3  * parser.y
4  */
5 
6 /*-
7  * SPDX-License-Identifier: BSD-2-Clause
8  *
9  * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * $Id: parser.y,v 1.7 2006/09/07 21:06:53 max Exp $
34  */
35 
36 #include <sys/queue.h>
37 #define L2CAP_SOCKET_CHECKED
38 #include <bluetooth.h>
39 #include <dev/usb/usb.h>
40 #include <dev/usb/usbhid.h>
41 #include <errno.h>
42 #include <limits.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 #include <usbhid.h>
48 
49 #ifndef BTHIDCONTROL
50 #include <stdarg.h>
51 #include <syslog.h>
52 #define	SYSLOG		syslog
53 #define	LOGCRIT		LOG_CRIT
54 #define	LOGERR		LOG_ERR
55 #define	LOGWARNING	LOG_WARNING
56 #define	EOL
57 #else
58 #define	SYSLOG		fprintf
59 #define	LOGCRIT		stderr
60 #define	LOGERR		stderr
61 #define	LOGWARNING	stderr
62 #define	EOL	"\n"
63 #endif /* ndef BTHIDCONTROL */
64 
65 #define	NAMELESS_DEVICE	"No Name"
66 
67 #include "bthid_config.h"
68 
69 	int	yylex		(void);
70 	void	yyerror		(char const *);
71 static	int32_t	check_hid_device(hid_device_p hid_device);
72 static	void	free_hid_device	(hid_device_p hid_device);
73 
74 extern	FILE			*yyin;
75 extern	int			 yylineno;
76 	char const		*config_file = BTHIDD_CONFFILE;
77 	char const		*hids_file   = BTHIDD_HIDSFILE;
78 
79 static	char			 buffer[1024];
80 static	int32_t			 hid_descriptor_size;
81 static	hid_device_t		*hid_device = NULL;
82 static	LIST_HEAD(, hid_device)	 hid_devices;
83 
84 %}
85 
86 %union {
87 	bdaddr_t	bdaddr;
88 	int32_t		num;
89 	char		*string;
90 }
91 
92 %token <bdaddr> T_BDADDRSTRING
93 %token <num>	T_HEXBYTE
94 %token <num>	T_HEXWORD
95 %token <string>	T_STRING
96 %token T_NAME
97 %token T_DEVICE T_BDADDR T_VENDOR_ID T_PRODUCT_ID T_VERSION T_CONTROL_PSM
98 %token T_INTERRUPT_PSM T_RECONNECT_INITIATE T_BATTERY_POWER
99 %token T_NORMALLY_CONNECTABLE T_HID_DESCRIPTOR
100 %token T_TRUE T_FALSE T_ERROR
101 
102 %%
103 
104 config:		line
105 		| config line
106 		;
107 
108 line:		T_DEVICE
109 			{
110 			hid_device = (hid_device_t *) calloc(1, sizeof(*hid_device));
111 			if (hid_device == NULL) {
112 				SYSLOG(LOGCRIT, "Could not allocate new " \
113 						"config entry" EOL);
114 				YYABORT;
115 			}
116 
117 			hid_device->new_device = 1;
118 			}
119 		'{' options '}'
120 			{
121 			if (check_hid_device(hid_device))
122 				LIST_INSERT_HEAD(&hid_devices,hid_device,next);
123 			else
124 				free_hid_device(hid_device);
125 
126 			hid_device = NULL;
127 			}
128 		;
129 
130 options:	option ';'
131 		| options option ';'
132 		;
133 
134 option:		bdaddr
135 		| name
136 		| vendor_id
137 		| product_id
138 		| version
139 		| control_psm
140 		| interrupt_psm
141 		| reconnect_initiate
142 		| battery_power
143 		| normally_connectable
144 		| hid_descriptor
145 		| parser_error
146 		;
147 
148 bdaddr:		T_BDADDR T_BDADDRSTRING
149 			{
150 			memcpy(&hid_device->bdaddr, &$2, sizeof(hid_device->bdaddr));
151 			}
152 		;
153 
154 name:		T_NAME T_STRING
155 			{
156 			if (hid_device->name != NULL) {
157                                 free(hid_device->name);
158                                 hid_device->name = NULL;
159 			}
160 
161 			if (strcmp($2, NAMELESS_DEVICE)) {
162 				hid_device->name = strdup($2);
163 				if (hid_device->name == NULL) {
164 					SYSLOG(LOGCRIT, "Could not allocate new " \
165 							"device name" EOL);
166 					YYABORT;
167 				}
168 			}
169 			}
170 		;
171 
172 vendor_id:	T_VENDOR_ID T_HEXWORD
173 			{
174 			hid_device->vendor_id = $2;
175 			}
176 		;
177 
178 product_id:	T_PRODUCT_ID T_HEXWORD
179 			{
180 			hid_device->product_id = $2;
181 			}
182 		;
183 
184 version:	T_VERSION T_HEXWORD
185 			{
186 			hid_device->version = $2;
187 			}
188 		;
189 
190 control_psm:	T_CONTROL_PSM T_HEXBYTE
191 			{
192 			hid_device->control_psm = $2;
193 			}
194 		;
195 
196 interrupt_psm:	T_INTERRUPT_PSM T_HEXBYTE
197 			{
198 			hid_device->interrupt_psm = $2;
199 			}
200 		;
201 
202 reconnect_initiate: T_RECONNECT_INITIATE T_TRUE
203 			{
204 			hid_device->reconnect_initiate = 1;
205 			}
206 		| T_RECONNECT_INITIATE T_FALSE
207 			{
208 			hid_device->reconnect_initiate = 0;
209 			}
210 		;
211 
212 battery_power:	T_BATTERY_POWER T_TRUE
213 			{
214 			hid_device->battery_power = 1;
215 			}
216 		| T_BATTERY_POWER T_FALSE
217 			{
218 			hid_device->battery_power = 0;
219 			}
220 		;
221 
222 normally_connectable: T_NORMALLY_CONNECTABLE T_TRUE
223 			{
224 			hid_device->normally_connectable = 1;
225 			}
226 		| T_NORMALLY_CONNECTABLE T_FALSE
227 			{
228 			hid_device->normally_connectable = 0;
229 			}
230 		;
231 
232 hid_descriptor:	T_HID_DESCRIPTOR
233 			{
234 			hid_descriptor_size = 0;
235 			}
236 		'{' hid_descriptor_bytes '}'
237 			{
238 			if (hid_device->desc != NULL)
239 				hid_dispose_report_desc(hid_device->desc);
240 
241 			hid_device->desc = hid_use_report_desc((unsigned char *) buffer, hid_descriptor_size);
242 			if (hid_device->desc == NULL) {
243 				SYSLOG(LOGCRIT, "Could not use HID descriptor" EOL);
244 				YYABORT;
245 			}
246 			}
247 		;
248 
249 hid_descriptor_bytes: hid_descriptor_byte
250 		| hid_descriptor_bytes hid_descriptor_byte
251 		;
252 
253 hid_descriptor_byte: T_HEXBYTE
254 			{
255 			if (hid_descriptor_size >= (int32_t) sizeof(buffer)) {
256 				SYSLOG(LOGCRIT, "HID descriptor is too big" EOL);
257 				YYABORT;
258 			}
259 
260 			buffer[hid_descriptor_size ++] = $1;
261 			}
262 		;
263 
264 parser_error:	T_ERROR
265 			{
266 				YYABORT;
267 			}
268 
269 %%
270 
271 /* Display parser error message */
272 void
273 yyerror(char const *message)
274 {
275 	SYSLOG(LOGERR, "%s in line %d" EOL, message, yylineno);
276 }
277 
278 /* Re-read config file */
279 int32_t
read_config_file(void)280 read_config_file(void)
281 {
282 	int32_t	e;
283 
284 	if (config_file == NULL) {
285 		SYSLOG(LOGERR, "Unknown config file name!" EOL);
286 		return (-1);
287 	}
288 
289 	if ((yyin = fopen(config_file, "r")) == NULL) {
290 		SYSLOG(LOGERR, "Could not open config file '%s'. %s (%d)" EOL,
291 				config_file, strerror(errno), errno);
292 		return (-1);
293 	}
294 
295 	clean_config();
296 	if (yyparse() < 0) {
297 		SYSLOG(LOGERR, "Could not parse config file '%s'" EOL,
298 				config_file);
299 		e = -1;
300 	} else
301 		e = 0;
302 
303 	fclose(yyin);
304 	yyin = NULL;
305 
306 	return (e);
307 }
308 
309 /* Clean config */
310 void
clean_config(void)311 clean_config(void)
312 {
313 	while (!LIST_EMPTY(&hid_devices)) {
314 		hid_device_p	d = LIST_FIRST(&hid_devices);
315 
316 		LIST_REMOVE(d, next);
317 		free_hid_device(d);
318 	}
319 }
320 
321 /* Lookup config entry */
322 hid_device_p
get_hid_device(bdaddr_p bdaddr)323 get_hid_device(bdaddr_p bdaddr)
324 {
325 	hid_device_p	d;
326 
327 	LIST_FOREACH(d, &hid_devices, next)
328 		if (memcmp(&d->bdaddr, bdaddr, sizeof(bdaddr_t)) == 0)
329 			break;
330 
331 	return (d);
332 }
333 
334 /* Get next config entry */
335 hid_device_p
get_next_hid_device(hid_device_p d)336 get_next_hid_device(hid_device_p d)
337 {
338 	return ((d == NULL)? LIST_FIRST(&hid_devices) : LIST_NEXT(d, next));
339 }
340 
341 /* Print config entry */
342 void
print_hid_device(hid_device_p d,FILE * f)343 print_hid_device(hid_device_p d, FILE *f)
344 {
345 	/* XXX FIXME hack! */
346 	struct report_desc {
347 		unsigned int	size;
348 		unsigned char	data[1];
349 	};
350 	/* XXX FIXME hack! */
351 
352 	struct report_desc	*desc = (struct report_desc *) d->desc;
353 	uint32_t		 i;
354 
355 	fprintf(f,
356 "device {\n"					\
357 "	bdaddr			%s;\n"		\
358 "	name			\"%s\";\n"	\
359 "	vendor_id		0x%04x;\n"	\
360 "	product_id		0x%04x;\n"	\
361 "	version			0x%04x;\n"	\
362 "	control_psm		0x%x;\n"	\
363 "	interrupt_psm		0x%x;\n"	\
364 "	reconnect_initiate	%s;\n"		\
365 "	battery_power		%s;\n"		\
366 "	normally_connectable	%s;\n"		\
367 "	hid_descriptor		{",
368 		bt_ntoa(&d->bdaddr, NULL),
369 		(d->name != NULL)? d->name : NAMELESS_DEVICE,
370 		d->vendor_id, d->product_id, d->version,
371 		d->control_psm, d->interrupt_psm,
372                 d->reconnect_initiate? "true" : "false",
373                 d->battery_power? "true" : "false",
374                 d->normally_connectable? "true" : "false");
375 
376 	for (i = 0; i < desc->size; i ++) {
377 			if ((i % 8) == 0)
378 				fprintf(f, "\n		");
379 
380 			fprintf(f, "0x%2.2x ", desc->data[i]);
381 	}
382 
383 	fprintf(f,
384 "\n"		\
385 "	};\n"	\
386 "}\n");
387 }
388 
389 /* Check config entry */
390 static int32_t
check_hid_device(hid_device_p d)391 check_hid_device(hid_device_p d)
392 {
393 	hid_data_t	hd;
394 	hid_item_t	hi;
395 	int32_t		page, mdepth;
396 
397 	if (get_hid_device(&d->bdaddr) != NULL) {
398 		SYSLOG(LOGERR, "Ignoring duplicated entry for bdaddr %s" EOL,
399 				bt_ntoa(&d->bdaddr, NULL));
400 		return (0);
401 	}
402 
403 	if (d->control_psm == 0) {
404 		SYSLOG(LOGERR, "Ignoring entry with invalid control PSM" EOL);
405 		return (0);
406 	}
407 
408 	if (d->interrupt_psm == 0) {
409 		SYSLOG(LOGERR, "Ignoring entry with invalid interrupt PSM" EOL);
410 		return (0);
411 	}
412 
413 	if (d->desc == NULL) {
414 		SYSLOG(LOGERR, "Ignoring entry without HID descriptor" EOL);
415 		return (0);
416 	}
417 
418 	mdepth = 0;
419 
420 	/* XXX somehow need to make sure descriptor is valid */
421 	for (hd = hid_start_parse(d->desc, ~0, -1); hid_get_item(hd, &hi) > 0; ) {
422 		switch (hi.kind) {
423 		case hid_collection:
424 			if (mdepth != 0)
425 				mdepth++;
426 			else if (hi.collection == 1 &&
427 			     hi.usage ==
428 			      HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE))
429 				mdepth++;
430 			break;
431 		case hid_endcollection:
432 			if (mdepth != 0)
433 				mdepth--;
434 			break;
435 		case hid_output:
436 		case hid_feature:
437 			break;
438 
439 		case hid_input:
440 			/* Check if the device may send keystrokes */
441 			page = HID_PAGE(hi.usage);
442 			if (page == HUP_KEYBOARD)
443 				d->keyboard = 1;
444 			if (page == HUP_CONSUMER &&
445 			    (hi.flags & (HIO_CONST|HIO_RELATIVE)) == 0)
446 				d->has_cons = 1;
447 			/* Check if the device may send relative motion events */
448 			if (mdepth == 0)
449 				break;
450 			if (hi.usage ==
451 			     HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X) &&
452 			    (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE)
453 				d->mouse = 1;
454 			if (hi.usage ==
455 			     HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y) &&
456 			    (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE)
457 				d->mouse = 1;
458 			if (hi.usage ==
459 			     HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_WHEEL) &&
460 			    (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE)
461 				d->has_wheel = 1;
462 			if (hi.usage ==
463 			    HID_USAGE2(HUP_CONSUMER, HUC_AC_PAN) &&
464 			    (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE)
465 				d->has_hwheel = 1;
466 			break;
467 		}
468 	}
469 	hid_end_parse(hd);
470 
471 	return (1);
472 }
473 
474 /* Free config entry */
475 static void
free_hid_device(hid_device_p d)476 free_hid_device(hid_device_p d)
477 {
478 	if (d->desc != NULL)
479 		hid_dispose_report_desc(d->desc);
480 
481 	free(d->name);
482 	memset(d, 0, sizeof(*d));
483 	free(d);
484 }
485 
486 /* Re-read hids file */
487 int32_t
read_hids_file(void)488 read_hids_file(void)
489 {
490 	FILE		*f;
491 	hid_device_t	*d;
492 	char		*line;
493 	bdaddr_t	 bdaddr;
494 	int32_t		 lineno;
495 
496 	if (hids_file == NULL) {
497 		SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
498 		return (-1);
499 	}
500 
501 	if ((f = fopen(hids_file, "r")) == NULL) {
502 		if (errno == ENOENT)
503 			return (0);
504 
505 		SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
506 			hids_file, strerror(errno), errno);
507 		return (-1);
508 	}
509 
510 	for (lineno = 1; fgets(buffer, sizeof(buffer), f) != NULL; lineno ++) {
511 		if ((line = strtok(buffer, "\r\n\t ")) == NULL)
512 			continue; /* ignore empty lines */
513 
514 		if (!bt_aton(line, &bdaddr)) {
515 			SYSLOG(LOGWARNING, "Ignoring unparseable BD_ADDR in " \
516 				"%s:%d" EOL, hids_file, lineno);
517 			continue;
518 		}
519 
520 		if ((d = get_hid_device(&bdaddr)) != NULL)
521 			d->new_device = 0;
522 	}
523 
524 	fclose(f);
525 
526 	return (0);
527 }
528 
529 /* Write hids file */
530 int32_t
write_hids_file(void)531 write_hids_file(void)
532 {
533 	char		 path[PATH_MAX];
534 	FILE		*f;
535 	hid_device_t	*d;
536 
537 	if (hids_file == NULL) {
538 		SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
539 		return (-1);
540 	}
541 
542 	snprintf(path, sizeof(path), "%s.new", hids_file);
543 
544 	if ((f = fopen(path, "w")) == NULL) {
545 		SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
546 			path, strerror(errno), errno);
547 		return (-1);
548 	}
549 
550 	LIST_FOREACH(d, &hid_devices, next)
551 		if (!d->new_device)
552 			fprintf(f, "%s\n", bt_ntoa(&d->bdaddr, NULL));
553 
554 	fclose(f);
555 
556 	if (rename(path, hids_file) < 0) {
557 		SYSLOG(LOGERR, "Could not rename new HIDs file '%s' to '%s'. " \
558 			"%s (%d)" EOL, path, hids_file, strerror(errno), errno);
559 		unlink(path);
560 		return (-1);
561 	}
562 
563 	return (0);
564 }
565 
566