usblib: usb_device_get_string decoding fixes.
usb_device_get_string converts incoming ucs-2 text
to ascii by truncating the upper byte of each
character. This is error prone.
This patch introduces usb_device_get_string_ucs2
to access raw USB string descriptors, as well as
changes usb_device_get_string to replace any non
ascii character with '?'.
Bug: 70163357
Change-Id: If8f98fee4be6ea75dc0f7ce7beba7fd7b6aabf88
Test: Connected & disconnected a NuForceDAC.
diff --git a/libusbhost/usbhost.c b/libusbhost/usbhost.c
index b8c5ca1..07d60e9 100644
--- a/libusbhost/usbhost.c
+++ b/libusbhost/usbhost.c
@@ -475,17 +475,30 @@
return device->desc;
}
-char* usb_device_get_string(struct usb_device *device, int id, int timeout)
-{
- char string[256];
- __u16 buffer[MAX_STRING_DESCRIPTOR_LENGTH / sizeof(__u16)];
+/* Returns a USB descriptor string for the given string ID.
+ * Return value: < 0 on error. 0 on success.
+ * The string is returned in ucs2_out in USB-native UCS-2 encoding.
+ *
+ * parameters:
+ * id - the string descriptor index.
+ * timeout - in milliseconds (see Documentation/driver-api/usb/usb.rst)
+ * ucs2_out - Must point to null on call.
+ * Will be filled in with a buffer on success.
+ * If this is non-null on return, it must be free()d.
+ * response_size - size, in bytes, of ucs-2 string in ucs2_out.
+ * The size isn't guaranteed to include null termination.
+ * Call free() to free the result when you are done with it.
+ */
+int usb_device_get_string_ucs2(struct usb_device* device, int id, int timeout, void** ucs2_out,
+ size_t* response_size) {
__u16 languages[MAX_STRING_DESCRIPTOR_LENGTH / sizeof(__u16)];
- int i, result;
+ char response[MAX_STRING_DESCRIPTOR_LENGTH];
+ int result;
int languageCount = 0;
- if (id == 0) return NULL;
+ if (id == 0) return -1;
+ if (*ucs2_out != NULL) return -1;
- string[0] = 0;
memset(languages, 0, sizeof(languages));
// read list of supported languages
@@ -496,25 +509,54 @@
if (result > 0)
languageCount = (result - 2) / 2;
- for (i = 1; i <= languageCount; i++) {
- memset(buffer, 0, sizeof(buffer));
+ for (int i = 1; i <= languageCount; i++) {
+ memset(response, 0, sizeof(response));
- result = usb_device_control_transfer(device,
- USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE, USB_REQ_GET_DESCRIPTOR,
- (USB_DT_STRING << 8) | id, languages[i], buffer, sizeof(buffer),
- timeout);
- if (result > 0) {
- int i;
- // skip first word, and copy the rest to the string, changing shorts to bytes.
- result /= 2;
- for (i = 1; i < result; i++)
- string[i - 1] = buffer[i];
- string[i - 1] = 0;
- return strdup(string);
+ result = usb_device_control_transfer(
+ device, USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE, USB_REQ_GET_DESCRIPTOR,
+ (USB_DT_STRING << 8) | id, languages[i], response, sizeof(response), timeout);
+ if (result >= 2) { // string contents begin at offset 2.
+ int descriptor_len = result - 2;
+ char* out = malloc(descriptor_len + 3);
+ if (out == NULL) {
+ return -1;
+ }
+ memcpy(out, response + 2, descriptor_len);
+ // trail with three additional NULLs, so that there's guaranteed
+ // to be a UCS-2 NULL character beyond whatever USB returned.
+ // The returned string length is still just what USB returned.
+ memset(out + descriptor_len, '\0', 3);
+ *ucs2_out = (void*)out;
+ *response_size = descriptor_len;
+ return 0;
}
}
+ return -1;
+}
- return NULL;
+/* Warning: previously this blindly returned the lower 8 bits of
+ * every UCS-2 character in a USB descriptor. Now it will replace
+ * values > 127 with ascii '?'.
+ */
+char* usb_device_get_string(struct usb_device* device, int id, int timeout) {
+ char* ascii_string = NULL;
+ size_t raw_string_len = 0;
+ size_t i;
+ if (usb_device_get_string_ucs2(device, id, timeout, (void**)&ascii_string, &raw_string_len) < 0)
+ return NULL;
+ if (ascii_string == NULL) return NULL;
+ for (i = 0; i < raw_string_len / 2; ++i) {
+ // wire format for USB is always little-endian.
+ char lower = ascii_string[2 * i];
+ char upper = ascii_string[2 * i + 1];
+ if (upper || (lower & 0x80)) {
+ ascii_string[i] = '?';
+ } else {
+ ascii_string[i] = lower;
+ }
+ }
+ ascii_string[i] = '\0';
+ return ascii_string;
}
char* usb_device_get_manufacturer_name(struct usb_device *device, int timeout)