1919#include <wchar.h>
2020#include <string.h>
2121#include <stdlib.h>
22+ #include <ctype.h> // for "tolower()"
2223
2324#include <hidapi.h>
2425
25- // Headers needed for sleeping.
26+ // Headers needed for sleeping and console management (wait for a keypress)
2627#ifdef _WIN32
2728 #include <windows.h>
29+ #include <conio.h>
2830#else
31+ #include <fcntl.h>
32+ #include <termios.h>
2933 #include <unistd.h>
3034#endif
3135
5054#if defined(USING_HIDAPI_LIBUSB ) && HID_API_VERSION >= HID_API_MAKE_VERSION (0 , 12 , 0 )
5155#include <hidapi_libusb.h>
5256#endif
57+
5358//
59+ // A function that waits for a key to be pressed and reports it's code
60+ // Used for immediate response in interactive subroutines
61+ // Taken from:
62+ // https://cboard.cprogramming.com/c-programming/63166-kbhit-linux.html
63+ int waitkey (void )
64+ {
65+ #ifdef _WIN32
66+ return _getch ();
67+ #else
68+ struct termios oldt , newt ;
69+ int ch ;
70+ int oldf ;
71+
72+ tcgetattr (STDIN_FILENO , & oldt );
73+ newt = oldt ;
74+ newt .c_lflag &= ~(ICANON | ECHO );
75+ tcsetattr (STDIN_FILENO , TCSANOW , & newt );
76+ oldf = fcntl (STDIN_FILENO , F_GETFL , 0 );
77+ fcntl (STDIN_FILENO , F_SETFL , oldf | O_NONBLOCK );
78+
79+ do
80+ {
81+ usleep (1 );
82+ ch = getchar ();
83+ }
84+ while (EOF == ch );
85+
86+ tcsetattr (STDIN_FILENO , TCSANOW , & oldt );
87+ fcntl (STDIN_FILENO , F_SETFL , oldf );
5488
89+ return ch ;
90+ #endif
91+ }
92+
93+ //
94+
95+ //
96+ // Report Device info
5597const char * hid_bus_name (hid_bus_type bus_type ) {
5698 static const char * const HidBusTypeName [] = {
5799 "Unknown" ,
@@ -126,80 +168,28 @@ void print_devices_with_descriptor(struct hid_device_info *cur_dev) {
126168 }
127169}
128170
129- int device_callback (
130- hid_hotplug_callback_handle callback_handle ,
131- struct hid_device_info * device ,
132- hid_hotplug_event event ,
133- void * user_data )
171+ //
172+ // Default static testing
173+ void test_static (void )
134174{
135- (void )user_data ;
136-
137- if (event & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED )
138- printf ("Handle %d: New device is connected: %s.\n" , callback_handle , device -> path );
139- else
140- printf ("Handle %d: Device was disconnected: %s.\n" , callback_handle , device -> path );
141-
142- printf ("type: %04hx %04hx\n serial_number: %ls" , device -> vendor_id , device -> product_id , device -> serial_number );
143- printf ("\n" );
144- printf (" Manufacturer: %ls\n" , device -> manufacturer_string );
145- printf (" Product: %ls\n" , device -> product_string );
146- printf (" Release: %hx\n" , device -> release_number );
147- printf (" Interface: %d\n" , device -> interface_number );
148- printf (" Usage (page): 0x%hx (0x%hx)\n" , device -> usage , device -> usage_page );
149- printf ("\n" );
150-
151- /* Printed data might not show on the screen - force it out */
152- fflush (stdout );
175+ struct hid_device_info * devs ;
153176
154- return 0 ;
177+ devs = hid_enumerate (0x0 , 0x0 );
178+ print_devices_with_descriptor (devs );
179+ hid_free_enumeration (devs );
155180}
156181
157- int main (int argc , char * argv [])
158- {
159- (void )argc ;
160- (void )argv ;
161182
183+ //
184+ // Fixed device testing
185+ void test_device (void )
186+ {
162187 int res ;
163188 unsigned char buf [256 ];
164- #define MAX_STR 255
189+ #define MAX_STR 255
165190 wchar_t wstr [MAX_STR ];
166191 hid_device * handle ;
167192 int i ;
168- hid_hotplug_callback_handle token1 , token2 ;
169-
170- struct hid_device_info * devs ;
171-
172- printf ("hidapi test/example tool. Compiled with hidapi version %s, runtime version %s.\n" , HID_API_VERSION_STR , hid_version_str ());
173- if (HID_API_VERSION == HID_API_MAKE_VERSION (hid_version ()-> major , hid_version ()-> minor , hid_version ()-> patch )) {
174- printf ("Compile-time version matches runtime version of hidapi.\n\n" );
175- }
176- else {
177- printf ("Compile-time version is different than runtime version of hidapi.\n]n" );
178- }
179-
180- if (hid_init ())
181- return -1 ;
182-
183- #if defined(__APPLE__ ) && HID_API_VERSION >= HID_API_MAKE_VERSION (0 , 12 , 0 )
184- // To work properly needs to be called before hid_open/hid_open_path after hid_init.
185- // Best/recommended option - call it right after hid_init.
186- hid_darwin_set_open_exclusive (0 );
187- #endif
188-
189- devs = hid_enumerate (0x0 , 0x0 );
190- print_devices_with_descriptor (devs );
191- hid_free_enumeration (devs );
192-
193- hid_hotplug_register_callback (0 , 0 , HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT , HID_API_HOTPLUG_ENUMERATE , device_callback , NULL , & token1 );
194- hid_hotplug_register_callback (0x054c , 0x0ce6 , HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT , HID_API_HOTPLUG_ENUMERATE , device_callback , NULL , & token2 );
195-
196- while (1 )
197- {
198-
199- }
200-
201- hid_hotplug_deregister_callback (token2 );
202- hid_hotplug_deregister_callback (token1 );
203193
204194 // Set up the command buffer.
205195 memset (buf ,0x00 ,sizeof (buf ));
@@ -213,8 +203,7 @@ int main(int argc, char* argv[])
213203 handle = hid_open (0x4d8 , 0x3f , NULL );
214204 if (!handle ) {
215205 printf ("unable to open device\n" );
216- hid_exit ();
217- return 1 ;
206+ return ;
218207 }
219208
220209 // Read the Manufacturer String
@@ -344,13 +333,221 @@ int main(int argc, char* argv[])
344333 }
345334
346335 hid_close (handle );
336+ }
347337
348- /* Free static HIDAPI objects. */
349- hid_exit ();
338+ //
339+ // Normal hotplug testing
340+ int device_callback (
341+ hid_hotplug_callback_handle callback_handle ,
342+ struct hid_device_info * device ,
343+ hid_hotplug_event event ,
344+ void * user_data )
345+ {
346+ (void )user_data ;
350347
351- #ifdef _WIN32
352- system ("pause" );
348+ if (event & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED )
349+ printf ("Handle %d: New device is connected: %s.\n" , callback_handle , device -> path );
350+ else
351+ printf ("Handle %d: Device was disconnected: %s.\n" , callback_handle , device -> path );
352+
353+ printf ("type: %04hx %04hx\n serial_number: %ls" , device -> vendor_id , device -> product_id , device -> serial_number );
354+ printf ("\n" );
355+ printf (" Manufacturer: %ls\n" , device -> manufacturer_string );
356+ printf (" Product: %ls\n" , device -> product_string );
357+ printf (" Release: %hx\n" , device -> release_number );
358+ printf (" Interface: %d\n" , device -> interface_number );
359+ printf (" Usage (page): 0x%hx (0x%hx)\n" , device -> usage , device -> usage_page );
360+ printf ("(Press Q to exit the test)\n" );
361+ printf ("\n" );
362+
363+ return 0 ;
364+ }
365+
366+
367+ void test_hotplug (void )
368+ {
369+ printf ("Starting the Hotplug test\n" );
370+ printf ("(Press Q to exit the test)\n" );
371+
372+ hid_hotplug_callback_handle token1 , token2 ;
373+
374+ hid_hotplug_register_callback (0 , 0 , HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT , HID_API_HOTPLUG_ENUMERATE , device_callback , NULL , & token1 );
375+ hid_hotplug_register_callback (0x054c , 0x0ce6 , HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT , HID_API_HOTPLUG_ENUMERATE , device_callback , NULL , & token2 );
376+
377+ while (1 )
378+ {
379+ int command = tolower (waitkey ());
380+ if ('q' == command )
381+ {
382+ break ;
383+ }
384+ }
385+
386+ hid_hotplug_deregister_callback (token2 );
387+ hid_hotplug_deregister_callback (token1 );
388+
389+ printf ("\n\nHotplug test stopped\n" );
390+ }
391+
392+ //
393+ // Stress-testing weird edge cases in hotplugs
394+ int cb1_handle ;
395+ int cb2_handle ;
396+ int cb_test1_triggered ;
397+
398+ int cb2_func (hid_hotplug_callback_handle callback_handle ,
399+ struct hid_device_info * device ,
400+ hid_hotplug_event event ,
401+ void * user_data )
402+ {
403+ (void ) callback_handle ;
404+ (void ) device ;
405+ (void ) event ;
406+ (void ) user_data ;
407+ // TIP: only perform the test once
408+ if (cb_test1_triggered )
409+ {
410+ return 1 ;
411+ }
412+
413+ printf ("Callback 2 fired\n" );
414+
415+ // Deregister the first callback
416+ // It should be placed in the list at an index prior to the current one, which will make the pointer to the current one invalid on some implementations
417+ hid_hotplug_deregister_callback (cb1_handle );
418+
419+ cb_test1_triggered = 1 ;
420+
421+ // As long as we are inside this callback, nothing goes wrong; however, returning from here will cause a use-after-free error on flawed implementations
422+ // as to retrieve the next element (or to check for it's presence) it will look those dereference a pointer located in an already freed area
423+ // Undefined behavior
424+ return 1 ;
425+ }
426+
427+ int cb1_func (hid_hotplug_callback_handle callback_handle ,
428+ struct hid_device_info * device ,
429+ hid_hotplug_event event ,
430+ void * user_data )
431+ {
432+ (void ) callback_handle ;
433+ (void ) device ;
434+ (void ) event ;
435+ (void ) user_data ;
436+
437+ // TIP: only perform the test once
438+ if (cb_test1_triggered )
439+ {
440+ return 1 ;
441+ }
442+
443+ printf ("Callback 1 fired\n" );
444+
445+ // Register the second callback and make it be called immediately by enumeration attempt
446+ // Will cause a deadlock on Linux immediately
447+ hid_hotplug_register_callback (0 , 0 , HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT , HID_API_HOTPLUG_ENUMERATE , cb2_func , NULL , & cb2_handle );
448+ return 1 ;
449+ }
450+
451+ void test_hotplug_deadlocks (void )
452+ {
453+ cb_test1_triggered = 0 ;
454+ printf ("Starting the Hotplug callbacks deadlocks test\n" );
455+ printf ("TIP: if you don't see a message that it succeeded, it means the test failed and the system is now deadlocked\n" );
456+ // Register the first callback and make it be called immediately by enumeration attempt (if at least 1 device is present)
457+ hid_hotplug_register_callback (0 , 0 , HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT , HID_API_HOTPLUG_ENUMERATE , cb1_func , NULL , & cb1_handle );
458+
459+ printf ("Test finished successfully (at least no deadlocks were found)\n" );
460+
461+ // Intentionally leave a callback registered to test how hid_exit handles it
462+ //hid_hotplug_deregister_callback(cb2_handle);
463+ }
464+
465+
466+ //
467+ // CLI
468+
469+ void print_version_check (void )
470+ {
471+ printf ("hidapi test/example tool. Compiled with hidapi version %s, runtime version %s.\n" , HID_API_VERSION_STR , hid_version_str ());
472+ if (HID_API_VERSION == HID_API_MAKE_VERSION (hid_version ()-> major , hid_version ()-> minor , hid_version ()-> patch )) {
473+ printf ("Compile-time version matches runtime version of hidapi.\n\n" );
474+ }
475+ else {
476+ printf ("Compile-time version is different than runtime version of hidapi.\n]n" );
477+ }
478+ }
479+
480+ void interactive_loop (void )
481+ {
482+ int command = 0 ;
483+
484+ print_version_check ();
485+
486+ do {
487+ printf ("Interactive HIDAPI testing utility\n" );
488+ printf (" 1: List connected devices\n" );
489+ printf (" 2: Dynamic hotplug test\n" );
490+ printf (" 3: Test specific device [04d8:003f]\n" );
491+ printf (" 4: Test hotplug callback management deadlocking scenario\n" );
492+ printf (" Q: Quit\n" );
493+ printf ("Please enter command:" );
494+
495+ /* Printed data might not show on the screen when the command is done - force it out */
496+ fflush (stdout );
497+
498+ command = toupper (waitkey ());
499+
500+ printf ("%c\n\n========================================\n\n" , command );
501+
502+ // GET COMMAND
503+ switch (command ) {
504+ case '1' :
505+ test_static ();
506+ break ;
507+ case '2' :
508+ test_hotplug ();
509+ break ;
510+ case '3' :
511+ test_device ();
512+ break ;
513+ case '4' :
514+ test_hotplug_deadlocks ();
515+ break ;
516+ case 'Q' :
517+ printf ("Quitting.\n" );
518+ return ;
519+ default :
520+ printf ("Command not recognized\n" );
521+ break ;
522+ }
523+
524+ /* Printed data might not show on the screen when the command is done - force it out */
525+ fflush (stdout );
526+
527+ printf ("\n\n========================================\n\n" );
528+ } while (command != 'Q' );
529+ }
530+
531+ //
532+ // Main
533+ int main (int argc , char * argv [])
534+ {
535+ (void )argc ;
536+ (void )argv ;
537+
538+ if (hid_init ())
539+ return -1 ;
540+
541+ #if defined(__APPLE__ ) && HID_API_VERSION >= HID_API_MAKE_VERSION (0 , 12 , 0 )
542+ // To work properly needs to be called before hid_open/hid_open_path after hid_init.
543+ // Best/recommended option - call it right after hid_init.
544+ hid_darwin_set_open_exclusive (0 );
353545#endif
354546
547+ interactive_loop ();
548+
549+ /* Free static HIDAPI objects. */
550+ hid_exit ();
551+
355552 return 0 ;
356553}
0 commit comments