| // META: timeout=long |
| // META: script=/resources/test-only-api.js |
| // META: script=/webusb/resources/fake-devices.js |
| // META: script=/webusb/resources/usb-helpers.js |
| 'use strict'; |
| |
| function assertRejectsWithNotFoundError(promise) { |
| return assertRejectsWithError(promise, 'NotFoundError'); |
| } |
| |
| function assertRejectsWithTypeError(promise) { |
| return assertRejectsWithError(promise, 'TypeError'); |
| } |
| |
| function assertRejectsWithNotOpenError(promise) { |
| return assertRejectsWithError( |
| promise, 'InvalidStateError', 'The device must be opened first.'); |
| } |
| |
| function assertRejectsWithNotConfiguredError(promise) { |
| return assertRejectsWithError( |
| promise, 'InvalidStateError', |
| 'The device must have a configuration selected.'); |
| } |
| |
| function assertRejectsWithNotClaimedError(promise) { |
| return assertRejectsWithError( |
| promise, 'InvalidStateError', |
| 'The specified interface has not been claimed.'); |
| } |
| |
| function assertRejectsWithEndpointNotFoundError(promise) { |
| return assertRejectsWithError( |
| promise, 'NotFoundError', |
| 'The specified endpoint is not part of a claimed and selected ' + |
| 'alternate interface.'); |
| } |
| |
| function assertRejectsWithDeviceStateChangeInProgressError(promise) { |
| return assertRejectsWithError( |
| promise, 'InvalidStateError', |
| 'An operation that changes the device state is in progress.'); |
| } |
| |
| function assertRejectsWithInterfaceStateChangeInProgressError(promise) { |
| return assertRejectsWithError( |
| promise, 'InvalidStateError', |
| 'An operation that changes interface state is in progress.'); |
| } |
| |
| function detachBuffer(buffer) { |
| if (self.GLOBAL.isWindow()) |
| window.postMessage('', '*', [buffer]); |
| else |
| self.postMessage('', [buffer]); |
| } |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return waitForDisconnect(fakeDevice) |
| .then(() => assertRejectsWithNotFoundError(device.open())); |
| }); |
| }, 'open rejects when called on a disconnected device'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return device.open() |
| .then(() => waitForDisconnect(fakeDevice)) |
| .then(() => { |
| assert_false(device.opened); |
| }); |
| }); |
| }, 'disconnection closes the device'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| assert_false(device.opened); |
| return device.open().then(() => { |
| assert_true(device.opened); |
| return device.close().then(() => { |
| assert_false(device.opened); |
| }); |
| }); |
| }); |
| }, 'a device can be opened and closed'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| return device.open() |
| .then(() => device.open()) |
| .then(() => device.open()) |
| .then(() => device.open()) |
| .then(() => device.close()) |
| .then(() => device.close()) |
| .then(() => device.close()) |
| .then(() => device.close()); |
| }); |
| }, 'open and close can be called multiple times'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| await Promise.all([ |
| device.open(), |
| assertRejectsWithDeviceStateChangeInProgressError(device.open()), |
| assertRejectsWithDeviceStateChangeInProgressError(device.close()), |
| ]); |
| await Promise.all([ |
| device.close(), |
| assertRejectsWithDeviceStateChangeInProgressError(device.open()), |
| assertRejectsWithDeviceStateChangeInProgressError(device.close()), |
| ]); |
| }, 'open and close cannot be called again while open or close are in progress'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| await device.open(); |
| return Promise.all([ |
| device.selectConfiguration(1), |
| assertRejectsWithDeviceStateChangeInProgressError( |
| device.claimInterface(0)), |
| assertRejectsWithDeviceStateChangeInProgressError( |
| device.releaseInterface(0)), |
| assertRejectsWithDeviceStateChangeInProgressError(device.open()), |
| assertRejectsWithDeviceStateChangeInProgressError( |
| device.selectConfiguration(1)), |
| assertRejectsWithDeviceStateChangeInProgressError(device.reset()), |
| assertRejectsWithDeviceStateChangeInProgressError( |
| device.selectAlternateInterface(0, 0)), |
| assertRejectsWithDeviceStateChangeInProgressError( |
| device.controlTransferOut({ |
| requestType: 'standard', |
| recipient: 'interface', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x0000, |
| })), |
| assertRejectsWithDeviceStateChangeInProgressError( |
| device.controlTransferOut({ |
| requestType: 'standard', |
| recipient: 'interface', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x0000, |
| }, new Uint8Array([1, 2, 3]))), |
| assertRejectsWithDeviceStateChangeInProgressError( |
| device.controlTransferIn({ |
| requestType: 'standard', |
| recipient: 'interface', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x0000 |
| }, 0)), |
| assertRejectsWithDeviceStateChangeInProgressError(device.close()), |
| ]); |
| }, 'device operations reject if an device state change is in progress'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return device.open() |
| .then(() => waitForDisconnect(fakeDevice)) |
| .then(() => assertRejectsWithNotFoundError(device.close())); |
| }); |
| }, 'close rejects when called on a disconnected device'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return device.open() |
| .then(() => waitForDisconnect(fakeDevice)) |
| .then(() => assertRejectsWithNotFoundError(device.selectConfiguration(1))); |
| }); |
| }, 'selectConfiguration rejects when called on a disconnected device'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => Promise.all([ |
| assertRejectsWithNotOpenError(device.selectConfiguration(1)), |
| assertRejectsWithNotOpenError(device.claimInterface(0)), |
| assertRejectsWithNotOpenError(device.releaseInterface(0)), |
| assertRejectsWithNotOpenError(device.selectAlternateInterface(0, 1)), |
| assertRejectsWithNotOpenError(device.controlTransferIn({ |
| requestType: 'vendor', |
| recipient: 'device', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x5678 |
| }, 7)), |
| assertRejectsWithNotOpenError(device.controlTransferOut({ |
| requestType: 'vendor', |
| recipient: 'device', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x5678 |
| }, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]))), |
| assertRejectsWithNotOpenError(device.clearHalt('in', 1)), |
| assertRejectsWithNotOpenError(device.transferIn(1, 8)), |
| assertRejectsWithNotOpenError( |
| device.transferOut(1, new ArrayBuffer(8))), |
| assertRejectsWithNotOpenError(device.isochronousTransferIn(1, [8])), |
| assertRejectsWithNotOpenError( |
| device.isochronousTransferOut(1, new ArrayBuffer(8), [8])), |
| assertRejectsWithNotOpenError(device.reset()) |
| ])); |
| }, 'methods requiring it reject when the device is not open'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| assert_equals(device.configuration, null); |
| return device.open() |
| .then(() => { |
| assert_equals(device.configuration, null); |
| return device.selectConfiguration(1); |
| }) |
| .then(() => { |
| assertDeviceInfoEquals( |
| device.configuration, fakeDeviceInit.configurations[0]); |
| }) |
| .then(() => device.close()); |
| }); |
| }, 'device configuration can be set and queried'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| assert_equals(device.configuration, null); |
| await device.open(); |
| assert_equals(device.configuration, null); |
| await device.selectConfiguration(1); |
| await device.selectConfiguration(1); |
| assertDeviceInfoEquals( |
| device.configuration, fakeDeviceInit.configurations[0]); |
| await device.selectConfiguration(2); |
| assertDeviceInfoEquals( |
| device.configuration, fakeDeviceInit.configurations[1]); |
| await device.close(); |
| }, 'a device configuration value can be set again'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| assert_equals(device.configuration, null); |
| return device.open() |
| .then(() => assertRejectsWithError( |
| device.selectConfiguration(3), 'NotFoundError', |
| 'The configuration value provided is not supported by the device.')) |
| .then(() => device.close()); |
| }); |
| }, 'selectConfiguration rejects on invalid configurations'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| assert_equals(device.configuration, null); |
| return device.open().then(() => Promise.all([ |
| assertRejectsWithNotConfiguredError(device.claimInterface(0)), |
| assertRejectsWithNotConfiguredError(device.releaseInterface(0)), |
| assertRejectsWithNotConfiguredError(device.selectAlternateInterface(0, 1)), |
| assertRejectsWithNotConfiguredError(device.clearHalt('in', 1)), |
| assertRejectsWithNotConfiguredError(device.transferIn(1, 8)), |
| assertRejectsWithNotConfiguredError( |
| device.transferOut(1, new ArrayBuffer(8))), |
| assertRejectsWithNotConfiguredError( |
| device.isochronousTransferIn(1, [8])), |
| assertRejectsWithNotConfiguredError( |
| device.isochronousTransferOut(1, new ArrayBuffer(8), [8])), |
| ])).then(() => device.close()); |
| }); |
| }, 'methods requiring it reject when the device is unconfigured'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| await device.open(); |
| await device.selectConfiguration(1); |
| assert_false(device.configuration.interfaces[0].claimed); |
| assert_false(device.configuration.interfaces[1].claimed); |
| |
| await device.claimInterface(0); |
| assert_true(device.configuration.interfaces[0].claimed); |
| assert_false(device.configuration.interfaces[1].claimed); |
| |
| await device.claimInterface(1); |
| assert_true(device.configuration.interfaces[0].claimed); |
| assert_true(device.configuration.interfaces[1].claimed); |
| |
| await device.releaseInterface(0); |
| assert_false(device.configuration.interfaces[0].claimed); |
| assert_true(device.configuration.interfaces[1].claimed); |
| |
| await device.releaseInterface(1); |
| assert_false(device.configuration.interfaces[0].claimed); |
| assert_false(device.configuration.interfaces[1].claimed); |
| |
| await device.close(); |
| }, 'interfaces can be claimed and released'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| await device.open(); |
| await device.selectConfiguration(1); |
| assert_false(device.configuration.interfaces[0].claimed); |
| assert_false(device.configuration.interfaces[1].claimed); |
| |
| await Promise.all([device.claimInterface(0), |
| device.claimInterface(1)]); |
| assert_true(device.configuration.interfaces[0].claimed); |
| assert_true(device.configuration.interfaces[1].claimed); |
| |
| await Promise.all([device.releaseInterface(0), |
| device.releaseInterface(1)]); |
| assert_false(device.configuration.interfaces[0].claimed); |
| assert_false(device.configuration.interfaces[1].claimed); |
| |
| await device.close(); |
| }, 'interfaces can be claimed and released in parallel'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice() |
| await device.open(); |
| await device.selectConfiguration(1); |
| await device.claimInterface(0); |
| assert_true(device.configuration.interfaces[0].claimed); |
| await device.claimInterface(0); |
| assert_true(device.configuration.interfaces[0].claimed); |
| await device.close(); |
| }, 'an interface can be claimed multiple times'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| await device.open(); |
| await device.selectConfiguration(1); |
| await device.claimInterface(0); |
| assert_true(device.configuration.interfaces[0].claimed); |
| await device.releaseInterface(0); |
| assert_false(device.configuration.interfaces[0].claimed); |
| await device.releaseInterface(0); |
| assert_false(device.configuration.interfaces[0].claimed); |
| await device.close(); |
| }, 'an interface can be released multiple times'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| await device.open(); |
| await device.selectConfiguration(1); |
| return Promise.all([ |
| device.claimInterface(0), |
| assertRejectsWithInterfaceStateChangeInProgressError( |
| device.claimInterface(0)), |
| assertRejectsWithInterfaceStateChangeInProgressError( |
| device.releaseInterface(0)), |
| assertRejectsWithInterfaceStateChangeInProgressError(device.open()), |
| assertRejectsWithInterfaceStateChangeInProgressError( |
| device.selectConfiguration(1)), |
| assertRejectsWithInterfaceStateChangeInProgressError(device.reset()), |
| assertRejectsWithInterfaceStateChangeInProgressError( |
| device.selectAlternateInterface(0, 0)), |
| assertRejectsWithInterfaceStateChangeInProgressError( |
| device.controlTransferOut({ |
| requestType: 'standard', |
| recipient: 'interface', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x0000, |
| })), |
| assertRejectsWithInterfaceStateChangeInProgressError( |
| device.controlTransferOut({ |
| requestType: 'standard', |
| recipient: 'interface', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x0000, |
| }, new Uint8Array([1, 2, 3]))), |
| assertRejectsWithInterfaceStateChangeInProgressError( |
| device.controlTransferIn({ |
| requestType: 'standard', |
| recipient: 'interface', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x0000 |
| }, 0)), |
| assertRejectsWithInterfaceStateChangeInProgressError(device.close()), |
| ]); |
| }, 'device operations reject if an interface state change is in progress'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| await device.open(); |
| await device.selectConfiguration(1); |
| await device.claimInterface(0); |
| assert_true(device.configuration.interfaces[0].claimed); |
| await device.close(0); |
| assert_false(device.configuration.interfaces[0].claimed); |
| }, 'interfaces are released on close'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| const message = 'The interface number provided is not supported by the ' + |
| 'device in its current configuration.'; |
| return device.open() |
| .then(() => device.selectConfiguration(1)) |
| .then(() => Promise.all([ |
| assertRejectsWithError( |
| device.claimInterface(2), 'NotFoundError', message), |
| assertRejectsWithError( |
| device.releaseInterface(2), 'NotFoundError', message), |
| ])) |
| .then(() => device.close()); |
| }); |
| }, 'a non-existent interface cannot be claimed or released'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(1)) |
| .then(() => waitForDisconnect(fakeDevice)) |
| .then(() => assertRejectsWithNotFoundError(device.claimInterface(0))); |
| }); |
| }, 'claimInterface rejects when called on a disconnected device'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(1)) |
| .then(() => device.claimInterface(0)) |
| .then(() => waitForDisconnect(fakeDevice)) |
| .then(() => assertRejectsWithNotFoundError(device.releaseInterface(0))); |
| }); |
| }, 'releaseInterface rejects when called on a disconnected device'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(2)) |
| .then(() => device.claimInterface(0)) |
| .then(() => device.selectAlternateInterface(0, 1)) |
| .then(() => device.close()); |
| }); |
| }, 'can select an alternate interface'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(2)) |
| .then(() => device.claimInterface(0)) |
| .then(() => assertRejectsWithError( |
| device.selectAlternateInterface(0, 2), 'NotFoundError', |
| 'The alternate setting provided is not supported by the device in ' + |
| 'its current configuration.')) |
| .then(() => device.close()); |
| }); |
| }, 'cannot select a non-existent alternate interface'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(2)) |
| .then(() => device.claimInterface(0)) |
| .then(() => waitForDisconnect(fakeDevice)) |
| .then(() => assertRejectsWithNotFoundError(device.selectAlternateInterface(0, 1))); |
| }); |
| }, 'selectAlternateInterface rejects when called on a disconnected device'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| let usbRequestTypes = ['standard', 'class', 'vendor']; |
| let usbRecipients = ['device', 'interface', 'endpoint', 'other']; |
| await device.open(); |
| await device.selectConfiguration(1); |
| await device.claimInterface(0); |
| await device.selectAlternateInterface(0, 0); |
| for (const requestType of usbRequestTypes) { |
| for (const recipient of usbRecipients) { |
| let index = recipient === 'interface' ? 0x5600 : 0x5681; |
| let result = await device.controlTransferIn({ |
| requestType: requestType, |
| recipient: recipient, |
| request: 0x42, |
| value: 0x1234, |
| index: index |
| }, 7); |
| assert_true(result instanceof USBInTransferResult); |
| assert_equals(result.status, 'ok'); |
| assert_equals(result.data.byteLength, 7); |
| assert_equals(result.data.getUint16(0), 0x07); |
| assert_equals(result.data.getUint8(2), 0x42); |
| assert_equals(result.data.getUint16(3), 0x1234); |
| assert_equals(result.data.getUint16(5), index); |
| } |
| } |
| await device.close(); |
| }, 'can issue all types of IN control transfers'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| let usbRequestTypes = ['standard', 'class', 'vendor']; |
| let usbRecipients = ['device', 'other']; |
| await device.open(); |
| await Promise.all(usbRequestTypes.flatMap(requestType => { |
| return usbRecipients.map(async recipient => { |
| let result = await device.controlTransferIn({ |
| requestType: requestType, |
| recipient: recipient, |
| request: 0x42, |
| value: 0x1234, |
| index: 0x5678 |
| }, 7); |
| assert_true(result instanceof USBInTransferResult); |
| assert_equals(result.status, 'ok'); |
| assert_equals(result.data.byteLength, 7); |
| assert_equals(result.data.getUint16(0), 0x07); |
| assert_equals(result.data.getUint8(2), 0x42); |
| assert_equals(result.data.getUint16(3), 0x1234); |
| assert_equals(result.data.getUint16(5), 0x5678); |
| }); |
| })); |
| await device.close(); |
| }, 'device-scope IN control transfers don\'t require configuration'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| let usbRequestTypes = ['standard', 'class', 'vendor']; |
| let usbRecipients = ['interface', 'endpoint']; |
| await device.open(); |
| await Promise.all(usbRequestTypes.flatMap(requestType => { |
| return usbRecipients.map(recipient => { |
| let index = recipient === 'interface' ? 0x5600 : 0x5681; |
| return assertRejectsWithNotConfiguredError(device.controlTransferIn({ |
| requestType: requestType, |
| recipient: recipient, |
| request: 0x42, |
| value: 0x1234, |
| index: index |
| }, 7)); |
| }); |
| })); |
| await device.close(); |
| }, 'interface-scope IN control transfers require configuration'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| let usbRequestTypes = ['standard', 'class', 'vendor']; |
| let usbRecipients = ['interface', 'endpoint']; |
| await device.open(); |
| await device.selectConfiguration(1); |
| await Promise.all(usbRequestTypes.flatMap(requestType => { |
| return [ |
| assertRejectsWithNotClaimedError(device.controlTransferIn({ |
| requestType: requestType, |
| recipient: 'interface', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x5600 |
| }, 7)), |
| assertRejectsWithNotFoundError(device.controlTransferIn({ |
| requestType: requestType, |
| recipient: 'endpoint', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x5681 |
| }, 7)) |
| ]; |
| })); |
| await device.close(); |
| }, 'interface-scope IN control transfers require claiming the interface'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(1)) |
| .then(() => waitForDisconnect(fakeDevice)) |
| .then(() => assertRejectsWithNotFoundError(device.controlTransferIn({ |
| requestType: 'vendor', |
| recipient: 'device', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x5678 |
| }, 7))); |
| }); |
| }, 'controlTransferIn rejects when called on a disconnected device'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| let usbRequestTypes = ['standard', 'class', 'vendor']; |
| let usbRecipients = ['device', 'interface', 'endpoint', 'other']; |
| let dataArray = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); |
| let dataTypes = [dataArray, dataArray.buffer]; |
| await device.open(); |
| await device.selectConfiguration(1); |
| await device.claimInterface(0); |
| await device.selectAlternateInterface(0, 0); |
| for (const requestType of usbRequestTypes) { |
| for (const recipient of usbRecipients) { |
| let index = recipient === 'interface' ? 0x5600 : 0x5681; |
| let transferParams = { |
| requestType: requestType, |
| recipient: recipient, |
| request: 0x42, |
| value: 0x1234, |
| index: index |
| }; |
| for (const data of dataTypes) { |
| let result = await device.controlTransferOut(transferParams, data); |
| assert_true(result instanceof USBOutTransferResult); |
| assert_equals(result.status, 'ok'); |
| assert_equals(result.bytesWritten, 8); |
| } |
| let result = await device.controlTransferOut(transferParams); |
| assert_true(result instanceof USBOutTransferResult); |
| assert_equals(result.status, 'ok'); |
| } |
| } |
| await device.close(); |
| }, 'can issue all types of OUT control transfers'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| let usbRequestTypes = ['standard', 'class', 'vendor']; |
| let usbRecipients = ['device', 'other']; |
| let dataArray = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); |
| let dataTypes = [dataArray, dataArray.buffer]; |
| await device.open(); |
| await Promise.all(usbRequestTypes.flatMap(requestType => { |
| return usbRecipients.flatMap(recipient => { |
| let transferParams = { |
| requestType: requestType, |
| recipient: recipient, |
| request: 0x42, |
| value: 0x1234, |
| index: 0x5678 |
| }; |
| return dataTypes.map(async data => { |
| let result = await device.controlTransferOut(transferParams, data); |
| assert_true(result instanceof USBOutTransferResult); |
| assert_equals(result.status, 'ok'); |
| assert_equals(result.bytesWritten, 8); |
| }).push((async () => { |
| let result = await device.controlTransferOut(transferParams); |
| assert_true(result instanceof USBOutTransferResult); |
| assert_equals(result.status, 'ok'); |
| })()); |
| }); |
| })); |
| await device.close(); |
| }, 'device-scope OUT control transfers don\'t require configuration'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| let usbRequestTypes = ['standard', 'class', 'vendor']; |
| let usbRecipients = ['interface', 'endpoint']; |
| let dataArray = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); |
| let dataTypes = [dataArray, dataArray.buffer]; |
| await device.open(); |
| await Promise.all(usbRequestTypes.flatMap(requestType => { |
| return usbRecipients.flatMap(recipient => { |
| let index = recipient === 'interface' ? 0x5600 : 0x5681; |
| let transferParams = { |
| requestType: requestType, |
| recipient: recipient, |
| request: 0x42, |
| value: 0x1234, |
| index: index |
| }; |
| return dataTypes.map(data => { |
| return assertRejectsWithNotConfiguredError( |
| device.controlTransferOut(transferParams, data)); |
| }).push(assertRejectsWithNotConfiguredError( |
| device.controlTransferOut(transferParams))); |
| }); |
| })); |
| await device.close(); |
| }, 'interface-scope OUT control transfers require configuration'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| let usbRequestTypes = ['standard', 'class', 'vendor']; |
| let usbRecipients = ['interface', 'endpoint']; |
| let dataArray = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); |
| let dataTypes = [dataArray, dataArray.buffer]; |
| await device.open(); |
| await device.selectConfiguration(1); |
| await Promise.all(usbRequestTypes.flatMap(requestType => { |
| return usbRecipients.flatMap(recipient => { |
| let index = recipient === 'interface' ? 0x5600 : 0x5681; |
| let assertion = recipient === 'interface' |
| ? assertRejectsWithNotClaimedError |
| : assertRejectsWithEndpointNotFoundError; |
| let transferParams = { |
| requestType: requestType, |
| recipient: recipient, |
| request: 0x42, |
| value: 0x1234, |
| index: index |
| }; |
| return dataTypes.map(data => { |
| return assertion(device.controlTransferOut(transferParams, data)); |
| }).push(assertion(device.controlTransferOut(transferParams))); |
| }); |
| })); |
| await device.close(); |
| }, 'interface-scope OUT control transfers an interface claim'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(1)) |
| .then(() => waitForDisconnect(fakeDevice)) |
| .then(() => assertRejectsWithNotFoundError(device.controlTransferOut({ |
| requestType: 'vendor', |
| recipient: 'device', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x5678 |
| }, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])))); |
| }); |
| }, 'controlTransferOut rejects when called on a disconnected device'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| await device.open(); |
| await device.selectConfiguration(1); |
| await device.claimInterface(0); |
| assertRejectsWithTypeError(device.controlTransferOut({ |
| requestType: 'invalid', |
| recipient: 'device', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x5678 |
| }, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]))); |
| assertRejectsWithTypeError(device.controlTransferIn({ |
| requestType: 'invalid', |
| recipient: 'device', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x5678 |
| }, 0)); |
| await device.close(); |
| }, 'control transfers with a invalid request type reject'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| await device.open(); |
| await device.selectConfiguration(1); |
| await device.claimInterface(0); |
| assertRejectsWithTypeError(device.controlTransferOut({ |
| requestType: 'vendor', |
| recipient: 'invalid', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x5678 |
| }, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]))); |
| assertRejectsWithTypeError(device.controlTransferIn({ |
| requestType: 'vendor', |
| recipient: 'invalid', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x5678 |
| }, 0)); |
| }, 'control transfers with a invalid recipient type reject'); |
| |
| usb_test(async () => { |
| let { device } = await getFakeDevice(); |
| await device.open(); |
| await device.selectConfiguration(1); |
| await device.claimInterface(0); |
| assertRejectsWithNotFoundError(device.controlTransferOut({ |
| requestType: 'vendor', |
| recipient: 'interface', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x0002 // Last byte of index is interface number. |
| }, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]))); |
| assertRejectsWithNotFoundError(device.controlTransferIn({ |
| requestType: 'vendor', |
| recipient: 'interface', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x0002 // Last byte of index is interface number. |
| }, 0)); |
| }, 'control transfers to a non-existant interface reject'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| let interfaceRequest = { |
| requestType: 'vendor', |
| recipient: 'interface', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x5600 // Last byte of index is interface number. |
| }; |
| let endpointRequest = { |
| requestType: 'vendor', |
| recipient: 'endpoint', |
| request: 0x42, |
| value: 0x1234, |
| index: 0x5681 // Last byte of index is endpoint address. |
| }; |
| let data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); |
| return device.open() |
| .then(() => device.selectConfiguration(1)) |
| .then(() => Promise.all([ |
| assertRejectsWithError( |
| device.controlTransferIn(interfaceRequest, 7), |
| 'InvalidStateError'), |
| assertRejectsWithError( |
| device.controlTransferIn(endpointRequest, 7), |
| 'NotFoundError'), |
| assertRejectsWithError( |
| device.controlTransferOut(interfaceRequest, data), |
| 'InvalidStateError'), |
| assertRejectsWithError( |
| device.controlTransferOut(endpointRequest, data), |
| 'NotFoundError'), |
| ])) |
| .then(() => device.claimInterface(0)) |
| .then(() => Promise.all([ |
| device.controlTransferIn(interfaceRequest, 7).then(result => { |
| assert_true(result instanceof USBInTransferResult); |
| assert_equals(result.status, 'ok'); |
| assert_equals(result.data.byteLength, 7); |
| assert_equals(result.data.getUint16(0), 0x07); |
| assert_equals(result.data.getUint8(2), 0x42); |
| assert_equals(result.data.getUint16(3), 0x1234); |
| assert_equals(result.data.getUint16(5), 0x5600); |
| }), |
| device.controlTransferIn(endpointRequest, 7).then(result => { |
| assert_true(result instanceof USBInTransferResult); |
| assert_equals(result.status, 'ok'); |
| assert_equals(result.data.byteLength, 7); |
| assert_equals(result.data.getUint16(0), 0x07); |
| assert_equals(result.data.getUint8(2), 0x42); |
| assert_equals(result.data.getUint16(3), 0x1234); |
| assert_equals(result.data.getUint16(5), 0x5681); |
| }), |
| device.controlTransferOut(interfaceRequest, data), |
| device.controlTransferOut(endpointRequest, data), |
| ])) |
| .then(() => device.close()); |
| }); |
| }, 'requests to interfaces and endpoint require an interface claim'); |
| |
| usb_test(async () => { |
| const { device } = await getFakeDevice(); |
| await device.open(); |
| await device.selectConfiguration(1); |
| await device.claimInterface(0); |
| |
| const transfer_params = { |
| requestType: 'vendor', |
| recipient: 'device', |
| request: 0, |
| value: 0, |
| index: 0 |
| }; |
| |
| try { |
| const array_buffer = new ArrayBuffer(64 * 8); |
| const result = |
| await device.controlTransferOut(transfer_params, array_buffer); |
| assert_equals(result.status, 'ok'); |
| |
| detachBuffer(array_buffer); |
| await device.controlTransferOut(transfer_params, array_buffer); |
| assert_unreached(); |
| } catch (e) { |
| assert_equals(e.code, DOMException.INVALID_STATE_ERR); |
| } |
| |
| try { |
| const typed_array = new Uint8Array(64 * 8); |
| const result = |
| await device.controlTransferOut(transfer_params, typed_array); |
| assert_equals(result.status, 'ok'); |
| |
| detachBuffer(typed_array.buffer); |
| await device.controlTransferOut(transfer_params, typed_array); |
| assert_unreached(); |
| } catch (e) { |
| assert_equals(e.code, DOMException.INVALID_STATE_ERR); |
| } |
| }, 'controlTransferOut rejects if called with a detached buffer'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(1)) |
| .then(() => device.claimInterface(0)) |
| .then(() => device.clearHalt('in', 1)) |
| .then(() => device.close()); |
| }); |
| }, 'can clear a halt condition'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(1)) |
| .then(() => device.claimInterface(0)) |
| .then(() => waitForDisconnect(fakeDevice)) |
| .then(() => assertRejectsWithNotFoundError(device.clearHalt('in', 1))); |
| }); |
| }, 'clearHalt rejects when called on a disconnected device'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| let data = new DataView(new ArrayBuffer(1024)); |
| for (let i = 0; i < 1024; ++i) |
| data.setUint8(i, i & 0xff); |
| const rangeError = 'The specified endpoint number is out of range.'; |
| return device.open() |
| .then(() => device.selectConfiguration(1)) |
| .then(() => device.claimInterface(0)) |
| .then(() => Promise.all([ |
| assertRejectsWithEndpointNotFoundError(device.transferIn(2, 8)), // Unclaimed |
| assertRejectsWithEndpointNotFoundError(device.transferIn(3, 8)), // Non-existent |
| assertRejectsWithError( |
| device.transferIn(16, 8), 'IndexSizeError', rangeError), |
| assertRejectsWithEndpointNotFoundError(device.transferOut(2, data)), // Unclaimed |
| assertRejectsWithEndpointNotFoundError(device.transferOut(3, data)), // Non-existent |
| assertRejectsWithError( |
| device.transferOut(16, data), 'IndexSizeError', rangeError), |
| ])); |
| }); |
| }, 'transfers to unavailable endpoints are rejected'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(1)) |
| .then(() => device.claimInterface(0)) |
| .then(() => device.transferIn(1, 8)) |
| .then(result => { |
| assert_true(result instanceof USBInTransferResult); |
| assert_equals(result.status, 'ok'); |
| assert_equals(result.data.byteLength, 8); |
| for (let i = 0; i < 8; ++i) |
| assert_equals(result.data.getUint8(i), i, 'mismatch at byte ' + i); |
| return device.close(); |
| }); |
| }); |
| }, 'can issue IN interrupt transfer'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(1)) |
| .then(() => device.claimInterface(1)) |
| .then(() => device.transferIn(2, 1024)) |
| .then(result => { |
| assert_true(result instanceof USBInTransferResult); |
| assert_equals(result.status, 'ok'); |
| assert_equals(result.data.byteLength, 1024); |
| for (let i = 0; i < 1024; ++i) |
| assert_equals(result.data.getUint8(i), i & 0xff, |
| 'mismatch at byte ' + i); |
| return device.close(); |
| }); |
| }); |
| }, 'can issue IN bulk transfer'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(1)) |
| .then(() => device.claimInterface(1)) |
| .then(() => waitForDisconnect(fakeDevice)) |
| .then(() => assertRejectsWithNotFoundError(device.transferIn(2, 1024))); |
| }); |
| }, 'transferIn rejects if called on a disconnected device'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(1)) |
| .then(() => device.claimInterface(1)) |
| .then(() => { |
| let data = new DataView(new ArrayBuffer(1024)); |
| for (let i = 0; i < 1024; ++i) |
| data.setUint8(i, i & 0xff); |
| return device.transferOut(2, data); |
| }) |
| .then(result => { |
| assert_true(result instanceof USBOutTransferResult); |
| assert_equals(result.status, 'ok'); |
| assert_equals(result.bytesWritten, 1024); |
| return device.close(); |
| }); |
| }); |
| }, 'can issue OUT bulk transfer'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(1)) |
| .then(() => device.claimInterface(1)) |
| .then(() => { |
| let data = new DataView(new ArrayBuffer(1024)); |
| for (let i = 0; i < 1024; ++i) |
| data.setUint8(i, i & 0xff); |
| return waitForDisconnect(fakeDevice) |
| .then(() => assertRejectsWithNotFoundError(device.transferOut(2, data))); |
| }); |
| }); |
| }, 'transferOut rejects if called on a disconnected device'); |
| |
| usb_test(async () => { |
| const { device } = await getFakeDevice(); |
| await device.open(); |
| await device.selectConfiguration(1); |
| await device.claimInterface(1); |
| |
| |
| try { |
| const array_buffer = new ArrayBuffer(64 * 8); |
| const result = await device.transferOut(2, array_buffer); |
| assert_equals(result.status, 'ok'); |
| |
| detachBuffer(array_buffer); |
| await device.transferOut(2, array_buffer); |
| assert_unreached(); |
| } catch (e) { |
| assert_equals(e.code, DOMException.INVALID_STATE_ERR); |
| } |
| |
| try { |
| const typed_array = new Uint8Array(64 * 8); |
| const result = await device.transferOut(2, typed_array); |
| assert_equals(result.status, 'ok'); |
| |
| detachBuffer(typed_array.buffer); |
| await device.transferOut(2, typed_array); |
| assert_unreached(); |
| } catch (e) { |
| assert_equals(e.code, DOMException.INVALID_STATE_ERR); |
| } |
| }, 'transferOut rejects if called with a detached buffer'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(2)) |
| .then(() => device.claimInterface(0)) |
| .then(() => device.selectAlternateInterface(0, 1)) |
| .then(() => device.isochronousTransferIn( |
| 1, [64, 64, 64, 64, 64, 64, 64, 64])) |
| .then(result => { |
| assert_true(result instanceof USBIsochronousInTransferResult); |
| assert_equals(result.data.byteLength, 64 * 8, 'buffer size'); |
| assert_equals(result.packets.length, 8, 'number of packets'); |
| let byteOffset = 0; |
| for (let i = 0; i < result.packets.length; ++i) { |
| assert_true( |
| result.packets[i] instanceof USBIsochronousInTransferPacket); |
| assert_equals(result.packets[i].status, 'ok'); |
| assert_equals(result.packets[i].data.byteLength, 64); |
| assert_equals(result.packets[i].data.buffer, result.data.buffer); |
| assert_equals(result.packets[i].data.byteOffset, byteOffset); |
| for (let j = 0; j < 64; ++j) |
| assert_equals(result.packets[i].data.getUint8(j), j & 0xff, |
| 'mismatch at byte ' + j + ' of packet ' + i); |
| byteOffset += result.packets[i].data.byteLength; |
| } |
| return device.close(); |
| }); |
| }); |
| }, 'can issue IN isochronous transfer'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(2)) |
| .then(() => device.claimInterface(0)) |
| .then(() => device.selectAlternateInterface(0, 1)) |
| .then(() => waitForDisconnect(fakeDevice)) |
| .then(() => assertRejectsWithNotFoundError(device.isochronousTransferIn( |
| 1, [64, 64, 64, 64, 64, 64, 64, 64]))); |
| }); |
| }, 'isochronousTransferIn rejects when called on a disconnected device'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(2)) |
| .then(() => device.claimInterface(0)) |
| .then(() => device.selectAlternateInterface(0, 1)) |
| .then(() => { |
| let data = new DataView(new ArrayBuffer(64 * 8)); |
| for (let i = 0; i < 8; ++i) { |
| for (let j = 0; j < 64; ++j) |
| data.setUint8(i * j, j & 0xff); |
| } |
| return device.isochronousTransferOut( |
| 1, data, [64, 64, 64, 64, 64, 64, 64, 64]); |
| }) |
| .then(result => { |
| assert_true(result instanceof USBIsochronousOutTransferResult); |
| assert_equals(result.packets.length, 8, 'number of packets'); |
| let byteOffset = 0; |
| for (let i = 0; i < result.packets.length; ++i) { |
| assert_true( |
| result.packets[i] instanceof USBIsochronousOutTransferPacket); |
| assert_equals(result.packets[i].status, 'ok'); |
| assert_equals(result.packets[i].bytesWritten, 64); |
| } |
| return device.close(); |
| }); |
| }); |
| }, 'can issue OUT isochronous transfer'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return device.open() |
| .then(() => device.selectConfiguration(2)) |
| .then(() => device.claimInterface(0)) |
| .then(() => device.selectAlternateInterface(0, 1)) |
| .then(() => { |
| let data = new DataView(new ArrayBuffer(64 * 8)); |
| for (let i = 0; i < 8; ++i) { |
| for (let j = 0; j < 64; ++j) |
| data.setUint8(i * j, j & 0xff); |
| } |
| return waitForDisconnect(fakeDevice) |
| .then(() => assertRejectsWithNotFoundError(device.isochronousTransferOut( |
| 1, data, [64, 64, 64, 64, 64, 64, 64, 64]))); |
| }); |
| }); |
| }, 'isochronousTransferOut rejects when called on a disconnected device'); |
| |
| usb_test(async () => { |
| const { device } = await getFakeDevice(); |
| await device.open(); |
| await device.selectConfiguration(2); |
| await device.claimInterface(0); |
| await device.selectAlternateInterface(0, 1); |
| |
| |
| try { |
| const array_buffer = new ArrayBuffer(64 * 8); |
| const result = await device.isochronousTransferOut( |
| 1, array_buffer, [64, 64, 64, 64, 64, 64, 64, 64]); |
| for (let i = 0; i < result.packets.length; ++i) |
| assert_equals(result.packets[i].status, 'ok'); |
| |
| detachBuffer(array_buffer); |
| await device.isochronousTransferOut( |
| 1, array_buffer, [64, 64, 64, 64, 64, 64, 64, 64]); |
| assert_unreached(); |
| } catch (e) { |
| assert_equals(e.code, DOMException.INVALID_STATE_ERR); |
| } |
| |
| try { |
| const typed_array = new Uint8Array(64 * 8); |
| const result = await device.isochronousTransferOut( |
| 1, typed_array, [64, 64, 64, 64, 64, 64, 64, 64]); |
| for (let i = 0; i < result.packets.length; ++i) |
| assert_equals(result.packets[i].status, 'ok'); |
| |
| detachBuffer(typed_array.buffer); |
| await device.isochronousTransferOut( |
| 1, typed_array, [64, 64, 64, 64, 64, 64, 64, 64]); |
| assert_unreached(); |
| } catch (e) { |
| assert_equals(e.code, DOMException.INVALID_STATE_ERR); |
| } |
| }, 'isochronousTransferOut rejects when called with a detached buffer'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device }) => { |
| return device.open().then(() => device.reset()).then(() => device.close()); |
| }); |
| }, 'can reset the device'); |
| |
| usb_test(() => { |
| return getFakeDevice().then(({ device, fakeDevice }) => { |
| return device.open() |
| .then(() => waitForDisconnect(fakeDevice)) |
| .then(() => assertRejectsWithNotFoundError(device.reset())); |
| }); |
| }, 'resetDevice rejects when called on a disconnected device'); |