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