diff --git a/libusb/hid.c b/libusb/hid.c index a48ea9bf..77d7d680 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -85,6 +85,18 @@ struct input_report { }; +typedef struct hidapi_error_ctx_ { + enum libusb_error error_code; + /* designed to hold string literals only - do not free */ + const char *error_context; + + enum libusb_error last_error_code_cache; + const char *last_error_context_cache; + /* dynamically allocated */ + wchar_t *last_error_str; +} hidapi_error_ctx; + + struct hid_device_ { /* Handle to the actual device. */ libusb_device_handle *device_handle; @@ -123,6 +135,8 @@ struct hid_device_ { #ifdef DETACH_KERNEL_DRIVER int is_driver_detached; #endif + + hidapi_error_ctx error; }; static struct hid_api_version api_version = { @@ -146,25 +160,23 @@ static hid_device *new_hid_device(void) return dev; } +static void free_hidapi_error(hidapi_error_ctx *err) +{ + free(err->last_error_str); +} + static void free_hid_device(hid_device *dev) { /* Clean up the thread objects */ hidapi_thread_state_destroy(&dev->thread_state); hid_free_enumeration(dev->device_info); + free_hidapi_error(&dev->error); /* Free the device itself */ free(dev); } -#if 0 -/*TODO: Implement this function on hidapi/libusb.. */ -static void register_error(hid_device *dev, const char *op) -{ - -} -#endif - /* Get bytes from a HID Report Descriptor. Only call with a num_bytes of 0, 1, 2, or 4. */ static uint32_t get_bytes(uint8_t *rpt, size_t len, size_t num_bytes, size_t cur) @@ -295,6 +307,156 @@ static inline int libusb_get_string_descriptor(libusb_device_handle *dev, #endif +static wchar_t *ctowcdup(const char *s, size_t slen) +{ + size_t i; + + size_t wchar_len = slen; + wchar_t *wbuf = (wchar_t*) malloc((wchar_len + 1) * sizeof(wchar_t)); + + if (!wbuf) + return NULL; + + for (i = 0u; i < wchar_len; i++) { + wbuf[i] = s[i]; + } + + wbuf[wchar_len] = L'\0'; + + return wbuf; +} + + +static void register_libusb_error(hidapi_error_ctx *err, enum libusb_error error, const char *error_context) +{ + err->error_code = error; + err->error_context = error_context; +} + + +static void register_string_error(hidapi_error_ctx *err, const char *error) +{ + free(err->last_error_str); + + err->last_error_str = ctowcdup(error, strlen(error)); + err->error_code = err->last_error_code_cache = 1; + err->error_context = err->last_error_context_cache = NULL; +} + + +/* we don't use iconv on Android, or when it is explicitly disabled */ +#if !defined(__ANDROID__) && !defined(NO_ICONV) + +/* iconv implementations */ + +static wchar_t *utf_to_wchar(char *utf, size_t utfbytes, const char *fromcode, size_t max_expected_wchar) +{ + iconv_t ic; + size_t inbytes; + size_t outbytes; + size_t res; + ICONV_CONST char *inptr; + char *outptr; + wchar_t *wbuf = NULL; + size_t wbuf_size; + + /* Initialize iconv. */ + ic = iconv_open("WCHAR_T", fromcode); + if (ic == (iconv_t)-1) { + LOG("iconv_open() failed\n"); + return NULL; + } + + wbuf_size = (max_expected_wchar + 1) * sizeof(wchar_t); + wbuf = malloc(wbuf_size); + if (!wbuf) { + goto end; + } + + inptr = utf; + inbytes = utfbytes; + outptr = (char*) wbuf; + outbytes = wbuf_size; + res = iconv(ic, &inptr, &inbytes, &outptr, &outbytes); + if (res == (size_t)-1) { + LOG("iconv() failed\n"); + goto err; + } + + /* Ensure the terminating NULL. */ + wbuf[max_expected_wchar] = L'\0'; + if (outbytes >= sizeof(wbuf[0])) + *((wchar_t*)outptr) = L'\0'; + + goto end; + +err: + free(wbuf); + wbuf = NULL; + +end: + iconv_close(ic); + + return wbuf; +} + + +static wchar_t *utf16le_to_wchar(char *utf16, size_t utf16bytes) +{ + return utf_to_wchar(utf16, utf16bytes, "UTF-16LE", utf16bytes / 2); +} + + +static wchar_t *utf8_to_wchar(char *utf8, size_t utf8bytes) +{ + return utf_to_wchar(utf8, utf8bytes, "UTF-8", utf8bytes); +} + + +#else + +/* simple implementations */ + + +static wchar_t *utf16le_to_wchar(char *utf16, size_t utf16bytes) +{ + /* The following code will only work for + code points that can be represented as a single UTF-16 character, + and will incorrectly convert any code points which require more + than one UTF-16 character. */ + + size_t i; + + size_t wchar_len = utf16bytes / 2; + wchar_t *wbuf = (wchar_t*) malloc((wchar_len + 1) * sizeof(wchar_t)); + + if (!wbuf) + return NULL; + + for (i = 0u; i < wchar_len; i++) { + wbuf[i] = utf16[i * 2] | (utf16[i * 2 + 1] << 8); + } + + wbuf[wchar_len] = L'\0'; + + return wbuf; +} + + +static wchar_t *utf8_to_wchar(char *utf8, size_t utf8bytes) +{ + /* The will only work for + code points that can be represented as a single UTF-8 character, + and will incorrectly convert any code points which require more + than one UTF-8 character. */ + + return ctowcdup(utf8, utf8bytes); +} + + +#endif + + /* Get the first language the device says it reports. This comes from USB string #0. */ static uint16_t get_first_language(libusb_device_handle *dev) @@ -344,22 +506,10 @@ static int is_language_supported(libusb_device_handle *dev, uint16_t lang) /* This function returns a newly allocated wide string containing the USB device string numbered by the index. The returned string must be freed by using free(). */ -static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx) +static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx, int *res) { char buf[512]; int len; - wchar_t *str = NULL; - -#if !defined(__ANDROID__) && !defined(NO_ICONV) /* we don't use iconv on Android, or when it is explicitly disabled */ - wchar_t wbuf[256]; - /* iconv variables */ - iconv_t ic; - size_t inbytes; - size_t outbytes; - size_t res; - ICONV_CONST char *inptr; - char *outptr; -#endif /* Determine which language to use. */ uint16_t lang; @@ -373,64 +523,12 @@ static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx) lang, (unsigned char*)buf, sizeof(buf)); - if (len < 2) /* we always skip first 2 bytes */ - return NULL; - -#if defined(__ANDROID__) || defined(NO_ICONV) - - /* Bionic does not have iconv support nor wcsdup() function, so it - has to be done manually. The following code will only work for - code points that can be represented as a single UTF-16 character, - and will incorrectly convert any code points which require more - than one UTF-16 character. - - Skip over the first character (2-bytes). */ - len -= 2; - str = (wchar_t*) malloc((len / 2 + 1) * sizeof(wchar_t)); - int i; - for (i = 0; i < len / 2; i++) { - str[i] = buf[i * 2 + 2] | (buf[i * 2 + 3] << 8); - } - str[len / 2] = 0x00000000; + *res = len; -#else - - /* buf does not need to be explicitly NULL-terminated because - it is only passed into iconv() which does not need it. */ - - /* Initialize iconv. */ - ic = iconv_open("WCHAR_T", "UTF-16LE"); - if (ic == (iconv_t)-1) { - LOG("iconv_open() failed\n"); + if (len < 2) /* we always skip first 2 bytes */ return NULL; - } - /* Convert to native wchar_t (UTF-32 on glibc/BSD systems). - Skip the first character (2-bytes). */ - inptr = buf+2; - inbytes = len-2; - outptr = (char*) wbuf; - outbytes = sizeof(wbuf); - res = iconv(ic, &inptr, &inbytes, &outptr, &outbytes); - if (res == (size_t)-1) { - LOG("iconv() failed\n"); - goto err; - } - - /* Write the terminating NULL. */ - wbuf[sizeof(wbuf)/sizeof(wbuf[0])-1] = 0x00000000; - if (outbytes >= sizeof(wbuf[0])) - *((wchar_t*)outptr) = 0x00000000; - - /* Allocate and copy the string. */ - str = wcsdup(wbuf); - -err: - iconv_close(ic); - -#endif - - return str; + return utf16le_to_wchar(buf + 2, (size_t)(len - 2)); } /** @@ -521,7 +619,7 @@ static int hid_get_report_descriptor_libusb(libusb_device_handle *handle, int in int res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8), interface_num, tmp, expected_report_descriptor_size, 5000); if (res < 0) { LOG("libusb_control_transfer() for getting the HID Report descriptor failed with %d: %s\n", res, libusb_error_name(res)); - return -1; + return res; } if (res > (int)buf_size) @@ -597,6 +695,7 @@ static void invasive_fill_device_info_usage(struct hid_device_info *cur_dev, lib */ static struct hid_device_info * create_device_info_for_device(libusb_device *device, libusb_device_handle *handle, struct libusb_device_descriptor *desc, int config_number, int interface_num) { + int res = 0; struct hid_device_info *cur_dev = calloc(1, sizeof(struct hid_device_info)); if (cur_dev == NULL) { return NULL; @@ -619,13 +718,13 @@ static struct hid_device_info * create_device_info_for_device(libusb_device *dev } if (desc->iSerialNumber > 0) - cur_dev->serial_number = get_usb_string(handle, desc->iSerialNumber); + cur_dev->serial_number = get_usb_string(handle, desc->iSerialNumber, &res); /* Manufacturer and Product strings */ if (desc->iManufacturer > 0) - cur_dev->manufacturer_string = get_usb_string(handle, desc->iManufacturer); + cur_dev->manufacturer_string = get_usb_string(handle, desc->iManufacturer, &res); if (desc->iProduct > 0) - cur_dev->product_string = get_usb_string(handle, desc->iProduct); + cur_dev->product_string = get_usb_string(handle, desc->iProduct, &res); return cur_dev; } @@ -1414,10 +1513,13 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t return hid_send_output_report(dev, data, length); } - if (!data || (length ==0)) { + if (!data || !length) { + register_string_error(&dev->error, "Zero buffer/length"); return -1; } + register_libusb_error(&dev->error, LIBUSB_SUCCESS, NULL); + report_number = data[0]; if (report_number == 0x0) { @@ -1434,8 +1536,10 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t (int)length, &actual_length, 1000); - if (res < 0) + if (res < 0) { + register_libusb_error(&dev->error, res, "hid_write"); return -1; + } if (skipped_report_id) actual_length++; @@ -1478,6 +1582,13 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t /* error: variable ‘bytes_read’ might be clobbered by ‘longjmp’ or ‘vfork’ [-Werror=clobbered] */ int bytes_read; /* = -1; */ + if (!data || !length) { + register_string_error(&dev->error, "Zero buffer/length"); + return -1; + } + + register_libusb_error(&dev->error, LIBUSB_SUCCESS, NULL); + hidapi_thread_mutex_lock(&dev->thread_state); hidapi_thread_cleanup_push(cleanup_mutex, dev); @@ -1494,6 +1605,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t /* This means the device has been disconnected. An error code of -1 should be returned. */ bytes_read = -1; + register_string_error(&dev->error, "hid_read(_timeout): device is closing"); goto ret; } @@ -1533,6 +1645,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t else { /* Error. */ bytes_read = -1; + register_string_error(&dev->error, "hid_read(_timeout): error waiting for data"); break; } } @@ -1568,6 +1681,13 @@ int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char int skipped_report_id = 0; int report_number = data[0]; + if (!data || !length) { + register_string_error(&dev->error, "Zero buffer/length"); + return -1; + } + + register_libusb_error(&dev->error, LIBUSB_SUCCESS, NULL); + if (report_number == 0x0) { data++; length--; @@ -1582,8 +1702,10 @@ int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char (unsigned char *)data, length, 1000/*timeout millis*/); - if (res < 0) + if (res < 0) { + register_libusb_error(&dev->error, res, "hid_send_feature_report"); return -1; + } /* Account for the report ID */ if (skipped_report_id) @@ -1598,6 +1720,13 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, int skipped_report_id = 0; int report_number = data[0]; + if (!data || !length) { + register_string_error(&dev->error, "Zero buffer/length"); + return -1; + } + + register_libusb_error(&dev->error, LIBUSB_SUCCESS, NULL); + if (report_number == 0x0) { /* Offset the return buffer by 1, so that the report ID will remain in byte 0. */ @@ -1613,8 +1742,10 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, (unsigned char *)data, length, 1000/*timeout millis*/); - if (res < 0) + if (res < 0) { + register_libusb_error(&dev->error, res, "hid_get_feature_report"); return -1; + } if (skipped_report_id) res++; @@ -1628,6 +1759,13 @@ int HID_API_EXPORT hid_send_output_report(hid_device *dev, const unsigned char * int skipped_report_id = 0; int report_number = data[0]; + if (!data || !length) { + register_string_error(&dev->error, "Zero buffer/length"); + return -1; + } + + register_libusb_error(&dev->error, LIBUSB_SUCCESS, NULL); + if (report_number == 0x0) { data++; length--; @@ -1642,8 +1780,10 @@ int HID_API_EXPORT hid_send_output_report(hid_device *dev, const unsigned char * (unsigned char *)data, length, 1000/*timeout millis*/); - if (res < 0) + if (res < 0) { + register_libusb_error(&dev->error, res, "hid_send_output_report"); return -1; + } /* Account for the report ID */ if (skipped_report_id) @@ -1658,6 +1798,13 @@ int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned c int skipped_report_id = 0; int report_number = data[0]; + if (!data || !length) { + register_string_error(&dev->error, "Zero buffer/length"); + return -1; + } + + register_libusb_error(&dev->error, LIBUSB_SUCCESS, NULL); + if (report_number == 0x0) { /* Offset the return buffer by 1, so that the report ID will remain in byte 0. */ @@ -1673,8 +1820,10 @@ int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned c (unsigned char *)data, length, 1000/*timeout millis*/); - if (res < 0) + if (res < 0) { + register_libusb_error(&dev->error, res, "hid_get_input_report"); return -1; + } if (skipped_report_id) res++; @@ -1741,17 +1890,21 @@ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *s } HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { + register_libusb_error(&dev->error, LIBUSB_SUCCESS, NULL); + if (!dev->device_info) { struct libusb_device_descriptor desc; libusb_device *usb_device = libusb_get_device(dev->device_handle); libusb_get_device_descriptor(usb_device, &desc); dev->device_info = create_device_info_for_device(usb_device, dev->device_handle, &desc, dev->config_number, dev->interface); - // device error already set by create_device_info_for_device, if any if (dev->device_info) { fill_device_info_usage(dev->device_info, dev->device_handle, dev->interface, dev->report_descriptor_size); } + else { + register_string_error(&dev->error, "hid_get_device_info: failed to allocate device info"); + } } return dev->device_info; @@ -1760,29 +1913,135 @@ HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_devi int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) { wchar_t *str; + int res = 0; + + if (!string || !maxlen) { + register_string_error(&dev->error, "Zero buffer/length"); + return -1; + } + + register_libusb_error(&dev->error, LIBUSB_SUCCESS, NULL); - str = get_usb_string(dev->device_handle, string_index); + str = get_usb_string(dev->device_handle, string_index, &res); if (str) { wcsncpy(string, str, maxlen); string[maxlen-1] = L'\0'; free(str); return 0; } - else + else { + if (res < 0) { + register_libusb_error(&dev->error, res, "hid_get_indexed_string"); + } + else { + register_string_error(&dev->error, "hid_get_indexed_string: failed to allocate result string"); + } return -1; + } } int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size) { - return hid_get_report_descriptor_libusb(dev->device_handle, dev->interface, dev->report_descriptor_size, buf, buf_size); + int res = 0; + + if (!buf || !buf_size) { + register_string_error(&dev->error, "Zero buffer/length"); + return -1; + } + + register_libusb_error(&dev->error, LIBUSB_SUCCESS, NULL); + + res = hid_get_report_descriptor_libusb(dev->device_handle, dev->interface, dev->report_descriptor_size, buf, buf_size); + + if (res < 0) { + register_libusb_error(&dev->error, res, "hid_get_report_descriptor"); + return -1; + } + + return res; +} + +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + const char *name, *description, *context; + char *buffer; + size_t len; + hidapi_error_ctx *err; + + if (!dev) { + /* TODO: have a global error handling */ + return L"Global hid_error is not implemented yet"; + } + + err = &dev->error; + + if (err->error_code == LIBUSB_SUCCESS) { + return L"Success"; + } + + if (err->error_code == err->last_error_code_cache + && err->error_context == err->last_error_context_cache) { + if (err->last_error_str) + return err->last_error_str; + else + return L"Error string memory allocation error"; + } + else { + free(err->last_error_str); + err->last_error_str = NULL; + err->last_error_code_cache = LIBUSB_SUCCESS; + err->last_error_context_cache = NULL; + } + + name = libusb_error_name(err->error_code); + description = libusb_strerror(err->error_code); + context = err->error_context; + + len = snprintf(NULL, 0, "%s: (%s) %s", context, name, description); + + if (len <= 0) { + return L"Error string format error"; + } + + buffer = malloc(len + 1); /* +1 for terminating NULL */ + if (!buffer) { + return L"Error string memory allocation error"; + } + + len = snprintf(buffer, len + 1, "%s: (%s) %s", context, name, description); + + if (len <= 0) { + free(buffer); + return L"Error string format error"; + } + + buffer[len] = '\0'; + + + err->last_error_str = utf8_to_wchar(buffer, (size_t)len); + + free(buffer); + + if (err->last_error_str != NULL) { + err->last_error_code_cache = err->error_code; + err->last_error_context_cache = err->error_context; + return err->last_error_str; + } + else { + return L"Error string memory allocation error"; + } } -HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +HID_API_EXPORT int HID_API_CALL hid_libusb_error(hid_device *dev) { - (void)dev; - return L"hid_error is not implemented yet"; + if (!dev) { + /* TODO: to be implemented as part of global error implementation */ + return LIBUSB_SUCCESS; + } + + return dev->error.error_code; } diff --git a/libusb/hidapi_libusb.h b/libusb/hidapi_libusb.h index 29207687..6e0ded4f 100644 --- a/libusb/hidapi_libusb.h +++ b/libusb/hidapi_libusb.h @@ -39,7 +39,7 @@ extern "C" { for details on libusb_wrap_sys_device. @ingroup API - @param sys_dev Platform-specific file descriptor that can be recognised by libusb. + @param sys_dev Platform-specific file descriptor that can be recognized by libusb. @param interface_num USB interface number of the device to be used as HID interface. Pass -1 to select first HID interface of the device. @@ -49,6 +49,23 @@ extern "C" { */ HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys_dev, int interface_num); + /** @brief Similar to @ref hid_error but gives a libusb error code. + + Since version 0.15.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0) + + If the error occurred is not immediately caused by a libusb function call, + the returned value is 1. @ref hid_error would still contain a valid and meaningful error message. + + @ingroup API + @param dev A device handle returned from hid_open(), + or NULL to get the last non-device-specific error + (e.g. for errors in hid_open() or hid_enumerate()). + + @returns + enum libusb_error value representing last error code. + */ + HID_API_EXPORT int HID_API_CALL hid_libusb_error(hid_device *dev); + #ifdef __cplusplus } #endif