node-usb / node-usb

Improved USB library for Node.js
https://node-usb.github.io/node-usb/
MIT License
1.55k stars 273 forks source link

getDevices excludes a device for which transferIn request is awaiting #566

Closed nisargjhaveri closed 1 year ago

nisargjhaveri commented 1 year ago

Bug Description:

getDevices call using the WebUsb interface does not include a device which was earlier available and has an active transferIn request.

Steps to Reproduce:

  1. List devices using getDevices.
  2. Filter the relevant device, open and claimInterface
  3. Start a transferIn request, don't await.
  4. Try to call getDevices again.
  5. Notice that the result of the getDevices does not include the device.

Here is a snippet I was able to reproduce this with.

import { WebUSB } from 'usb';

const WebUsbDeviceFilter: USBDeviceFilter = {
    classCode: 0xFF,
    subclassCode: 0x42,
    protocolCode: 1,
};

const usb = new WebUSB({
    "allowAllDevices": true
});

(async () => {
    let devices: USBDevice[];
    devices = await usb.getDevices();

    console.log("Initial device length:", devices.length);

    let device = devices
        .filter(d => {
            return d.configuration?.interfaces.some(iface => {
                return iface.alternate.interfaceClass === WebUsbDeviceFilter.classCode
                    && iface.alternate.interfaceSubclass === WebUsbDeviceFilter.subclassCode
                    && iface.alternate.interfaceProtocol === WebUsbDeviceFilter.protocolCode
            });
        })[0];

    await device.open();

    let inEndpointNumber, outEndpointNumber;
    for (const configuration of device.configurations) {
        for (const interface_ of configuration.interfaces) {
            for (const alternate of interface_.alternates) {
                if (alternate.interfaceClass === WebUsbDeviceFilter.classCode &&
                    alternate.interfaceSubclass === WebUsbDeviceFilter.subclassCode &&
                    alternate.interfaceProtocol === WebUsbDeviceFilter.protocolCode) {
                    if (device.configuration?.configurationValue !== configuration.configurationValue) {
                        await device.selectConfiguration(configuration.configurationValue);
                    }

                    if (!interface_.claimed) {
                        await device.claimInterface(interface_.interfaceNumber);
                    }

                    if (interface_.alternate.alternateSetting !== alternate.alternateSetting) {
                        await device.selectAlternateInterface(interface_.interfaceNumber, alternate.alternateSetting);
                    }

                    for (const endpoint of alternate.endpoints) {
                        switch (endpoint.direction) {
                            case 'in':
                                inEndpointNumber = endpoint.endpointNumber;
                                break;
                            case 'out':
                                outEndpointNumber = endpoint.endpointNumber;
                                break;
                        }
                    }
                }
            }
        }
    }

    // devices = await usb.getDevices();
    // console.log("Before transferIn:", devices.length);

    device.transferIn(inEndpointNumber, 24);

    devices = await usb.getDevices();
    console.log("After transferIn:", devices.length);
})();

Output:

Initial device length: 7
After transferIn: 6

Additional Information

nisargjhaveri commented 1 year ago

Also, calling getDevices just before transferIn call makes the transferIn call fail with the below error.

./node_modules/usb/dist/webusb/webusb-device.js:431
                        throw new Error("transferIn error: " + error_7);
                              ^

Error: transferIn error: TypeError: Cannot read properties of undefined (reading 'transfer')
    at WebUSBDevice.<anonymous> (./node_modules/usb/dist/webusb/webusb-device.js:431:31)
    at step (./node_modules/usb/dist/webusb/webusb-device.js:33:23)
    at Object.next (./node_modules/usb/dist/webusb/webusb-device.js:14:53)
    at ./node_modules/usb/dist/webusb/webusb-device.js:8:71
    at new Promise (<anonymous>)
    at __awaiter (./node_modules/usb/dist/webusb/webusb-device.js:4:12)
    at WebUSBDevice.transferIn (./node_modules/usb/dist/webusb/webusb-device.js:404:16)
    at ./dist/client/trial.js:68:12
    at Generator.next (<anonymous>)
    at fulfilled (./dist/client/trial.js:5:58)
nisargjhaveri commented 1 year ago

Looks like this is likely because of the fact that getDevices call creates new instances of the device and does not return the same instance when called again. Created PR #567 to address this.

nisargjhaveri commented 1 year ago

This should not be fixed in master since #567 was merged.

@thegecko, Curious: What is the release cycle we're following? When should I expect a version that includes the fix for this?

thegecko commented 1 year ago

It's building now

nisargjhaveri commented 1 year ago

That's quick! Thanks a lot! :)

thegecko commented 1 year ago

https://github.com/node-usb/node-usb/releases/tag/v2.7.0 https://www.npmjs.com/package/usb/v/2.7.0