WICG / webusb

Connecting hardware to the web.
https://wicg.github.io/webusb/
Other
1.31k stars 131 forks source link

RFE: String descriptor API #106

Closed riggs closed 7 years ago

riggs commented 7 years ago

The browser is already grabbing string descriptors for manufacturerName, productName, and serialNumber. Presumably, then, exposing a function to grab any of the other string descriptors should be relatively straightforward. Reimplementing these calls in user-land is redundant and error prone.

reillyeon commented 7 years ago

Chrome actually attempts to avoid grabbing reading string descriptors directly from the device, preferring to use what has already been cached by the operating system during enumeration. It does have code to do this however because not all operating systems cache all the necessary data.

That said, a getStringDescriptor() method is a short wrapper around controlTransferIn() and can be polyfilled like this:

USBDevice.prototype.getStringDescriptor = async function(language, index) {
  let result = await this.controlTransferIn({
      requestType: 'standard',
      recipient: 'device',
      request: 0x06,
      value: 0x0300 | index,
      index: language}, 255);
  if (result.status !== 'ok')
    return null;
  let decoder = new TextDecoder('utf-16le');
  let length = result.data.getUint8(0);
  if (length <= 2)
    return null;
  let buffer = new Uint8Array(result.data.buffer, 2, length - 2);
  return decoder.decode(buffer);
}
riggs commented 7 years ago

I recognize that it's a relatively simple function, but it would still be convenient not to have to constantly reimplement in user space.

Also, your polyfill doesn't handle index 0 correctly. Which sort of illustrates my point about user-land code (the devil, as usual, is in the details). For future reference, this polyfill does work correctly:

USBDevice.prototype.getStringDescriptor = async function(language, index) {
  let result = await this.controlTransferIn({
      requestType: 'standard',
      recipient: 'device',
      request: 0x06,
      value: 0x0300 | index,
      index: language}, 255);
  if (result.status !== 'ok')
    return null;
  let decoder = new TextDecoder('utf-16le');
  let length = result.data.getUint8(0);
  if (length <= 2)
    return null;
  if (index === 0) {
    return new Uint16Array(result.data.buffer, 2, (length - 2) / 2);
  }
  let buffer = new Uint8Array(result.data.buffer, 2, length - 2);
  return decoder.decode(buffer);
}
riggs commented 7 years ago

And just to further prove my point, I just ran into a webcam that broke the above by returning zero bytes, but with status ok (no idea if this is allowed by some edge-case of USB spec or not).

Updated polyfill:

USBDevice.prototype.getStringDescriptor = async function(language, index) {
  let result = await this.controlTransferIn({
      requestType: 'standard',
      recipient: 'device',
      request: 0x06,
      value: 0x0300 | index,
      index: language}, 255);
  if (result.status !== 'ok')
    return null;
  if (result.data.byteLength < 1)
    return null;
  let length = result.data.getUint8(0);
  if (length <= 2)
    return null;
  if (index === 0) {
    return new Uint16Array(result.data.buffer, 2, (length - 2) / 2);
  }
  let buffer = new Uint8Array(result.data.buffer, 2, length - 2);
  let decoder = new TextDecoder('utf-16le');
  return decoder.decode(buffer);
}