xref: /linux/tools/testing/selftests/hid/hidraw.c (revision 54ba6d9b1393a0061600c0e49c8ebef65d60a8b2)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2022-2024 Red Hat */
3 
4 #include "hid_common.h"
5 #include <linux/input.h>
6 #include <string.h>
7 #include <sys/ioctl.h>
8 
9 /* for older kernels */
10 #ifndef HIDIOCREVOKE
11 #define HIDIOCREVOKE	      _IOW('H', 0x0D, int) /* Revoke device access */
12 #endif /* HIDIOCREVOKE */
13 
14 FIXTURE(hidraw) {
15 	struct uhid_device hid;
16 	int hidraw_fd;
17 };
18 static void close_hidraw(FIXTURE_DATA(hidraw) * self)
19 {
20 	if (self->hidraw_fd)
21 		close(self->hidraw_fd);
22 	self->hidraw_fd = 0;
23 }
24 
25 FIXTURE_TEARDOWN(hidraw) {
26 	void *uhid_err;
27 
28 	uhid_destroy(_metadata, &self->hid);
29 
30 	close_hidraw(self);
31 	pthread_join(self->hid.tid, &uhid_err);
32 }
33 #define TEARDOWN_LOG(fmt, ...) do { \
34 	TH_LOG(fmt, ##__VA_ARGS__); \
35 	hidraw_teardown(_metadata, self, variant); \
36 } while (0)
37 
38 FIXTURE_SETUP(hidraw)
39 {
40 	int err;
41 
42 	err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a37, rdesc, sizeof(rdesc));
43 	ASSERT_OK(err);
44 
45 	self->hidraw_fd = open_hidraw(&self->hid);
46 	ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
47 }
48 
49 /*
50  * A simple test to see if the fixture is working fine.
51  * If this fails, none of the other tests will pass.
52  */
53 TEST_F(hidraw, test_create_uhid)
54 {
55 }
56 
57 /*
58  * Inject one event in the uhid device,
59  * check that we get the same data through hidraw
60  */
61 TEST_F(hidraw, raw_event)
62 {
63 	__u8 buf[10] = {0};
64 	int err;
65 
66 	/* inject one event */
67 	buf[0] = 1;
68 	buf[1] = 42;
69 	uhid_send_event(_metadata, &self->hid, buf, 6);
70 
71 	/* read the data from hidraw */
72 	memset(buf, 0, sizeof(buf));
73 	err = read(self->hidraw_fd, buf, sizeof(buf));
74 	ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
75 	ASSERT_EQ(buf[0], 1);
76 	ASSERT_EQ(buf[1], 42);
77 }
78 
79 /*
80  * After initial opening/checks of hidraw, revoke the hidraw
81  * node and check that we can not read any more data.
82  */
83 TEST_F(hidraw, raw_event_revoked)
84 {
85 	__u8 buf[10] = {0};
86 	int err;
87 
88 	/* inject one event */
89 	buf[0] = 1;
90 	buf[1] = 42;
91 	uhid_send_event(_metadata, &self->hid, buf, 6);
92 
93 	/* read the data from hidraw */
94 	memset(buf, 0, sizeof(buf));
95 	err = read(self->hidraw_fd, buf, sizeof(buf));
96 	ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
97 	ASSERT_EQ(buf[0], 1);
98 	ASSERT_EQ(buf[1], 42);
99 
100 	/* call the revoke ioctl */
101 	err = ioctl(self->hidraw_fd, HIDIOCREVOKE, NULL);
102 	ASSERT_OK(err) TH_LOG("couldn't revoke the hidraw fd");
103 
104 	/* inject one other event */
105 	buf[0] = 1;
106 	buf[1] = 43;
107 	uhid_send_event(_metadata, &self->hid, buf, 6);
108 
109 	/* read the data from hidraw */
110 	memset(buf, 0, sizeof(buf));
111 	err = read(self->hidraw_fd, buf, sizeof(buf));
112 	ASSERT_EQ(err, -1) TH_LOG("read_hidraw");
113 	ASSERT_EQ(errno, ENODEV) TH_LOG("unexpected error code while reading the hidraw node: %d",
114 					errno);
115 }
116 
117 /*
118  * Revoke the hidraw node and check that we can not do any ioctl.
119  */
120 TEST_F(hidraw, ioctl_revoked)
121 {
122 	int err, desc_size = 0;
123 
124 	/* call the revoke ioctl */
125 	err = ioctl(self->hidraw_fd, HIDIOCREVOKE, NULL);
126 	ASSERT_OK(err) TH_LOG("couldn't revoke the hidraw fd");
127 
128 	/* do an ioctl */
129 	err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size);
130 	ASSERT_EQ(err, -1) TH_LOG("ioctl_hidraw");
131 	ASSERT_EQ(errno, ENODEV) TH_LOG("unexpected error code while doing an ioctl: %d",
132 					errno);
133 }
134 
135 /*
136  * Setup polling of the fd, and check that revoke works properly.
137  */
138 TEST_F(hidraw, poll_revoked)
139 {
140 	struct pollfd pfds[1];
141 	__u8 buf[10] = {0};
142 	int err, ready;
143 
144 	/* setup polling */
145 	pfds[0].fd = self->hidraw_fd;
146 	pfds[0].events = POLLIN;
147 
148 	/* inject one event */
149 	buf[0] = 1;
150 	buf[1] = 42;
151 	uhid_send_event(_metadata, &self->hid, buf, 6);
152 
153 	while (true) {
154 		ready = poll(pfds, 1, 5000);
155 		ASSERT_EQ(ready, 1) TH_LOG("poll return value");
156 
157 		if (pfds[0].revents & POLLIN) {
158 			memset(buf, 0, sizeof(buf));
159 			err = read(self->hidraw_fd, buf, sizeof(buf));
160 			ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
161 			ASSERT_EQ(buf[0], 1);
162 			ASSERT_EQ(buf[1], 42);
163 
164 			/* call the revoke ioctl */
165 			err = ioctl(self->hidraw_fd, HIDIOCREVOKE, NULL);
166 			ASSERT_OK(err) TH_LOG("couldn't revoke the hidraw fd");
167 		} else {
168 			break;
169 		}
170 	}
171 
172 	ASSERT_TRUE(pfds[0].revents & POLLHUP);
173 }
174 
175 /*
176  * After initial opening/checks of hidraw, revoke the hidraw
177  * node and check that we can not read any more data.
178  */
179 TEST_F(hidraw, write_event_revoked)
180 {
181 	struct timespec time_to_wait;
182 	__u8 buf[10] = {0};
183 	int err;
184 
185 	/* inject one event from hidraw */
186 	buf[0] = 1; /* report ID */
187 	buf[1] = 2;
188 	buf[2] = 42;
189 
190 	pthread_mutex_lock(&uhid_output_mtx);
191 
192 	memset(output_report, 0, sizeof(output_report));
193 	clock_gettime(CLOCK_REALTIME, &time_to_wait);
194 	time_to_wait.tv_sec += 2;
195 
196 	err = write(self->hidraw_fd, buf, 3);
197 	ASSERT_EQ(err, 3) TH_LOG("unexpected error while writing to hidraw node: %d", err);
198 
199 	err = pthread_cond_timedwait(&uhid_output_cond, &uhid_output_mtx, &time_to_wait);
200 	ASSERT_OK(err) TH_LOG("error while calling waiting for the condition");
201 
202 	ASSERT_EQ(output_report[0], 1);
203 	ASSERT_EQ(output_report[1], 2);
204 	ASSERT_EQ(output_report[2], 42);
205 
206 	/* call the revoke ioctl */
207 	err = ioctl(self->hidraw_fd, HIDIOCREVOKE, NULL);
208 	ASSERT_OK(err) TH_LOG("couldn't revoke the hidraw fd");
209 
210 	/* inject one other event */
211 	buf[0] = 1;
212 	buf[1] = 43;
213 	err = write(self->hidraw_fd, buf, 3);
214 	ASSERT_LT(err, 0) TH_LOG("unexpected success while writing to hidraw node: %d", err);
215 	ASSERT_EQ(errno, ENODEV) TH_LOG("unexpected error code while writing to hidraw node: %d",
216 					errno);
217 
218 	pthread_mutex_unlock(&uhid_output_mtx);
219 }
220 
221 /*
222  * Test HIDIOCGRDESCSIZE ioctl to get report descriptor size
223  */
224 TEST_F(hidraw, ioctl_rdescsize)
225 {
226 	int desc_size = 0;
227 	int err;
228 
229 	/* call HIDIOCGRDESCSIZE ioctl */
230 	err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size);
231 	ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESCSIZE ioctl failed");
232 
233 	/* verify the size matches our test report descriptor */
234 	ASSERT_EQ(desc_size, sizeof(rdesc))
235 		TH_LOG("expected size %zu, got %d", sizeof(rdesc), desc_size);
236 }
237 
238 /*
239  * Test HIDIOCGRDESC ioctl to get report descriptor data
240  */
241 TEST_F(hidraw, ioctl_rdesc)
242 {
243 	struct hidraw_report_descriptor desc;
244 	int err;
245 
246 	/* get the full report descriptor */
247 	desc.size = sizeof(rdesc);
248 	err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc);
249 	ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed");
250 
251 	/* verify the descriptor data matches our test descriptor */
252 	ASSERT_EQ(memcmp(desc.value, rdesc, sizeof(rdesc)), 0)
253 		TH_LOG("report descriptor data mismatch");
254 }
255 
256 /*
257  * Test HIDIOCGRDESC ioctl with smaller buffer size
258  */
259 TEST_F(hidraw, ioctl_rdesc_small_buffer)
260 {
261 	struct hidraw_report_descriptor desc;
262 	int err;
263 	size_t small_size = sizeof(rdesc) / 2; /* request half the descriptor size */
264 
265 	/* get partial report descriptor */
266 	desc.size = small_size;
267 	err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc);
268 	ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed with small buffer");
269 
270 	/* verify we got the first part of the descriptor */
271 	ASSERT_EQ(memcmp(desc.value, rdesc, small_size), 0)
272 		TH_LOG("partial report descriptor data mismatch");
273 }
274 
275 /*
276  * Test HIDIOCGRAWINFO ioctl to get device information
277  */
278 TEST_F(hidraw, ioctl_rawinfo)
279 {
280 	struct hidraw_devinfo devinfo;
281 	int err;
282 
283 	/* get device info */
284 	err = ioctl(self->hidraw_fd, HIDIOCGRAWINFO, &devinfo);
285 	ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRAWINFO ioctl failed");
286 
287 	/* verify device info matches our test setup */
288 	ASSERT_EQ(devinfo.bustype, BUS_USB)
289 		TH_LOG("expected bustype 0x03, got 0x%x", devinfo.bustype);
290 	ASSERT_EQ(devinfo.vendor, 0x0001)
291 		TH_LOG("expected vendor 0x0001, got 0x%x", devinfo.vendor);
292 	ASSERT_EQ(devinfo.product, 0x0a37)
293 		TH_LOG("expected product 0x0a37, got 0x%x", devinfo.product);
294 }
295 
296 /*
297  * Test HIDIOCGFEATURE ioctl to get feature report
298  */
299 TEST_F(hidraw, ioctl_gfeature)
300 {
301 	__u8 buf[10] = {0};
302 	int err;
303 
304 	/* set report ID 1 in first byte */
305 	buf[0] = 1;
306 
307 	/* get feature report */
308 	err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
309 	ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGFEATURE ioctl failed, got %d", err);
310 
311 	/* verify we got the expected feature data */
312 	ASSERT_EQ(buf[0], feature_data[0])
313 		TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
314 	ASSERT_EQ(buf[1], feature_data[1])
315 		TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
316 }
317 
318 /*
319  * Test HIDIOCGFEATURE ioctl with invalid report ID
320  */
321 TEST_F(hidraw, ioctl_gfeature_invalid)
322 {
323 	__u8 buf[10] = {0};
324 	int err;
325 
326 	/* set invalid report ID (not 1) */
327 	buf[0] = 2;
328 
329 	/* try to get feature report */
330 	err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
331 	ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE should have failed with invalid report ID");
332 	ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
333 }
334 
335 /*
336  * Test ioctl with incorrect nr bits
337  */
338 TEST_F(hidraw, ioctl_invalid_nr)
339 {
340 	char buf[256] = {0};
341 	int err;
342 	unsigned int bad_cmd;
343 
344 	/*
345 	 * craft an ioctl command with wrong _IOC_NR bits
346 	 */
347 	bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x00, sizeof(buf)); /* 0 is not valid */
348 
349 	/* test the ioctl */
350 	err = ioctl(self->hidraw_fd, bad_cmd, buf);
351 	ASSERT_LT(err, 0) TH_LOG("ioctl read-write with wrong _IOC_NR (0) should have failed");
352 	ASSERT_EQ(errno, ENOTTY)
353 		TH_LOG("expected ENOTTY for wrong read-write _IOC_NR (0), got errno %d", errno);
354 
355 	/*
356 	 * craft an ioctl command with wrong _IOC_NR bits
357 	 */
358 	bad_cmd = _IOC(_IOC_READ, 'H', 0x00, sizeof(buf)); /* 0 is not valid */
359 
360 	/* test the ioctl */
361 	err = ioctl(self->hidraw_fd, bad_cmd, buf);
362 	ASSERT_LT(err, 0) TH_LOG("ioctl read-only with wrong _IOC_NR (0) should have failed");
363 	ASSERT_EQ(errno, ENOTTY)
364 		TH_LOG("expected ENOTTY for wrong read-only _IOC_NR (0), got errno %d", errno);
365 
366 	/* also test with bigger number */
367 	bad_cmd = _IOC(_IOC_READ, 'H', 0x42, sizeof(buf)); /* 0x42 is not valid as well */
368 
369 	err = ioctl(self->hidraw_fd, bad_cmd, buf);
370 	ASSERT_LT(err, 0) TH_LOG("ioctl read-only with wrong _IOC_NR (0x42) should have failed");
371 	ASSERT_EQ(errno, ENOTTY)
372 		TH_LOG("expected ENOTTY for wrong read-only _IOC_NR (0x42), got errno %d", errno);
373 
374 	/* also test with bigger number: 0x42 is not valid as well */
375 	bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x42, sizeof(buf));
376 
377 	err = ioctl(self->hidraw_fd, bad_cmd, buf);
378 	ASSERT_LT(err, 0) TH_LOG("ioctl read-write with wrong _IOC_NR (0x42) should have failed");
379 	ASSERT_EQ(errno, ENOTTY)
380 		TH_LOG("expected ENOTTY for wrong read-write _IOC_NR (0x42), got errno %d", errno);
381 }
382 
383 /*
384  * Test ioctl with incorrect type bits
385  */
386 TEST_F(hidraw, ioctl_invalid_type)
387 {
388 	char buf[256] = {0};
389 	int err;
390 	unsigned int bad_cmd;
391 
392 	/*
393 	 * craft an ioctl command with wrong _IOC_TYPE bits
394 	 */
395 	bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'I', 0x01, sizeof(buf)); /* 'I' should be 'H' */
396 
397 	/* test the ioctl */
398 	err = ioctl(self->hidraw_fd, bad_cmd, buf);
399 	ASSERT_LT(err, 0) TH_LOG("ioctl with wrong _IOC_TYPE (I) should have failed");
400 	ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_NR, got errno %d", errno);
401 }
402 
403 /*
404  * Test HIDIOCGFEATURE ioctl with incorrect _IOC_DIR bits
405  */
406 TEST_F(hidraw, ioctl_gfeature_invalid_dir)
407 {
408 	__u8 buf[10] = {0};
409 	int err;
410 	unsigned int bad_cmd;
411 
412 	/* set report ID 1 in first byte */
413 	buf[0] = 1;
414 
415 	/*
416 	 * craft an ioctl command with wrong _IOC_DIR bits
417 	 * HIDIOCGFEATURE should have _IOC_WRITE|_IOC_READ, let's use only _IOC_WRITE
418 	 */
419 	bad_cmd = _IOC(_IOC_WRITE, 'H', 0x07, sizeof(buf)); /* should be _IOC_WRITE|_IOC_READ */
420 
421 	/* try to get feature report with wrong direction bits */
422 	err = ioctl(self->hidraw_fd, bad_cmd, buf);
423 	ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have failed");
424 	ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
425 
426 	/* also test with only _IOC_READ */
427 	bad_cmd = _IOC(_IOC_READ, 'H', 0x07, sizeof(buf)); /* should be _IOC_WRITE|_IOC_READ */
428 
429 	err = ioctl(self->hidraw_fd, bad_cmd, buf);
430 	ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have failed");
431 	ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
432 }
433 
434 /*
435  * Test read-only ioctl with incorrect _IOC_DIR bits
436  */
437 TEST_F(hidraw, ioctl_readonly_invalid_dir)
438 {
439 	char buf[256] = {0};
440 	int err;
441 	unsigned int bad_cmd;
442 
443 	/*
444 	 * craft an ioctl command with wrong _IOC_DIR bits
445 	 * HIDIOCGRAWNAME should have _IOC_READ, let's use _IOC_WRITE
446 	 */
447 	bad_cmd = _IOC(_IOC_WRITE, 'H', 0x04, sizeof(buf)); /* should be _IOC_READ */
448 
449 	/* try to get device name with wrong direction bits */
450 	err = ioctl(self->hidraw_fd, bad_cmd, buf);
451 	ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have failed");
452 	ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
453 
454 	/* also test with _IOC_WRITE|_IOC_READ */
455 	bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x04, sizeof(buf)); /* should be only _IOC_READ */
456 
457 	err = ioctl(self->hidraw_fd, bad_cmd, buf);
458 	ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have failed");
459 	ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
460 }
461 
462 /*
463  * Test HIDIOCSFEATURE ioctl to set feature report
464  */
465 TEST_F(hidraw, ioctl_sfeature)
466 {
467 	__u8 buf[10] = {0};
468 	int err;
469 
470 	/* prepare feature report data */
471 	buf[0] = 1; /* report ID */
472 	buf[1] = 0x42;
473 	buf[2] = 0x24;
474 
475 	/* set feature report */
476 	err = ioctl(self->hidraw_fd, HIDIOCSFEATURE(3), buf);
477 	ASSERT_EQ(err, 3) TH_LOG("HIDIOCSFEATURE ioctl failed, got %d", err);
478 
479 	/*
480 	 * Note: The uhid mock doesn't validate the set report data,
481 	 * so we just verify the ioctl succeeds
482 	 */
483 }
484 
485 /*
486  * Test HIDIOCGINPUT ioctl to get input report
487  */
488 TEST_F(hidraw, ioctl_ginput)
489 {
490 	__u8 buf[10] = {0};
491 	int err;
492 
493 	/* set report ID 1 in first byte */
494 	buf[0] = 1;
495 
496 	/* get input report */
497 	err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf);
498 	ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGINPUT ioctl failed, got %d", err);
499 
500 	/* verify we got the expected input data */
501 	ASSERT_EQ(buf[0], feature_data[0])
502 		TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
503 	ASSERT_EQ(buf[1], feature_data[1])
504 		TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
505 }
506 
507 /*
508  * Test HIDIOCGINPUT ioctl with invalid report ID
509  */
510 TEST_F(hidraw, ioctl_ginput_invalid)
511 {
512 	__u8 buf[10] = {0};
513 	int err;
514 
515 	/* set invalid report ID (not 1) */
516 	buf[0] = 2;
517 
518 	/* try to get input report */
519 	err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf);
520 	ASSERT_LT(err, 0) TH_LOG("HIDIOCGINPUT should have failed with invalid report ID");
521 	ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
522 }
523 
524 /*
525  * Test HIDIOCSINPUT ioctl to set input report
526  */
527 TEST_F(hidraw, ioctl_sinput)
528 {
529 	__u8 buf[10] = {0};
530 	int err;
531 
532 	/* prepare input report data */
533 	buf[0] = 1; /* report ID */
534 	buf[1] = 0x55;
535 	buf[2] = 0xAA;
536 
537 	/* set input report */
538 	err = ioctl(self->hidraw_fd, HIDIOCSINPUT(3), buf);
539 	ASSERT_EQ(err, 3) TH_LOG("HIDIOCSINPUT ioctl failed, got %d", err);
540 
541 	/*
542 	 * Note: The uhid mock doesn't validate the set report data,
543 	 * so we just verify the ioctl succeeds
544 	 */
545 }
546 
547 /*
548  * Test HIDIOCGOUTPUT ioctl to get output report
549  */
550 TEST_F(hidraw, ioctl_goutput)
551 {
552 	__u8 buf[10] = {0};
553 	int err;
554 
555 	/* set report ID 1 in first byte */
556 	buf[0] = 1;
557 
558 	/* get output report */
559 	err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf);
560 	ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGOUTPUT ioctl failed, got %d", err);
561 
562 	/* verify we got the expected output data */
563 	ASSERT_EQ(buf[0], feature_data[0])
564 		TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
565 	ASSERT_EQ(buf[1], feature_data[1])
566 		TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
567 }
568 
569 /*
570  * Test HIDIOCGOUTPUT ioctl with invalid report ID
571  */
572 TEST_F(hidraw, ioctl_goutput_invalid)
573 {
574 	__u8 buf[10] = {0};
575 	int err;
576 
577 	/* set invalid report ID (not 1) */
578 	buf[0] = 2;
579 
580 	/* try to get output report */
581 	err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf);
582 	ASSERT_LT(err, 0) TH_LOG("HIDIOCGOUTPUT should have failed with invalid report ID");
583 	ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
584 }
585 
586 /*
587  * Test HIDIOCSOUTPUT ioctl to set output report
588  */
589 TEST_F(hidraw, ioctl_soutput)
590 {
591 	__u8 buf[10] = {0};
592 	int err;
593 
594 	/* prepare output report data */
595 	buf[0] = 1; /* report ID */
596 	buf[1] = 0x33;
597 	buf[2] = 0xCC;
598 
599 	/* set output report */
600 	err = ioctl(self->hidraw_fd, HIDIOCSOUTPUT(3), buf);
601 	ASSERT_EQ(err, 3) TH_LOG("HIDIOCSOUTPUT ioctl failed, got %d", err);
602 
603 	/*
604 	 * Note: The uhid mock doesn't validate the set report data,
605 	 * so we just verify the ioctl succeeds
606 	 */
607 }
608 
609 /*
610  * Test HIDIOCGRAWNAME ioctl to get device name string
611  */
612 TEST_F(hidraw, ioctl_rawname)
613 {
614 	char name[256] = {0};
615 	char expected_name[64];
616 	int err;
617 
618 	/* get device name */
619 	err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(name)), name);
620 	ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWNAME ioctl failed, got %d", err);
621 
622 	/* construct expected name based on device id */
623 	snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id);
624 
625 	/* verify the name matches expected pattern */
626 	ASSERT_EQ(strcmp(name, expected_name), 0)
627 		TH_LOG("expected name '%s', got '%s'", expected_name, name);
628 }
629 
630 /*
631  * Test HIDIOCGRAWPHYS ioctl to get device physical address string
632  */
633 TEST_F(hidraw, ioctl_rawphys)
634 {
635 	char phys[256] = {0};
636 	char expected_phys[64];
637 	int err;
638 
639 	/* get device physical address */
640 	err = ioctl(self->hidraw_fd, HIDIOCGRAWPHYS(sizeof(phys)), phys);
641 	ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWPHYS ioctl failed, got %d", err);
642 
643 	/* construct expected phys based on device id */
644 	snprintf(expected_phys, sizeof(expected_phys), "%d", self->hid.dev_id);
645 
646 	/* verify the phys matches expected value */
647 	ASSERT_EQ(strcmp(phys, expected_phys), 0)
648 		TH_LOG("expected phys '%s', got '%s'", expected_phys, phys);
649 }
650 
651 /*
652  * Test HIDIOCGRAWUNIQ ioctl to get device unique identifier string
653  */
654 TEST_F(hidraw, ioctl_rawuniq)
655 {
656 	char uniq[256] = {0};
657 	int err;
658 
659 	/* get device unique identifier */
660 	err = ioctl(self->hidraw_fd, HIDIOCGRAWUNIQ(sizeof(uniq)), uniq);
661 	ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWUNIQ ioctl failed, got %d", err);
662 
663 	/* uniq is typically empty in our test setup */
664 	ASSERT_EQ(strlen(uniq), 0) TH_LOG("expected empty uniq, got '%s'", uniq);
665 }
666 
667 /*
668  * Test device string ioctls with small buffer sizes
669  */
670 TEST_F(hidraw, ioctl_strings_small_buffer)
671 {
672 	char small_buf[8] = {0};
673 	char expected_name[64];
674 	int err;
675 
676 	/* test HIDIOCGRAWNAME with small buffer */
677 	err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(small_buf)), small_buf);
678 	ASSERT_EQ(err, sizeof(small_buf))
679 		TH_LOG("HIDIOCGRAWNAME with small buffer failed, got %d", err);
680 
681 	/* construct expected truncated name */
682 	snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id);
683 
684 	/* verify we got truncated name (first 8 chars, no null terminator guaranteed) */
685 	ASSERT_EQ(strncmp(small_buf, expected_name, sizeof(small_buf)), 0)
686 		TH_LOG("expected truncated name to match first %zu chars", sizeof(small_buf));
687 
688 	/* Note: hidraw driver doesn't guarantee null termination when buffer is too small */
689 }
690 
691 int main(int argc, char **argv)
692 {
693 	return test_harness_run(argc, argv);
694 }
695