| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title></title> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <script src="resources/manual.js"></script> | |
| </head> | |
| <body> | |
| <p> | |
| These tests require a USB device to be connected. | |
| </p> | |
| <script> | |
| const kGetDescriptorRequest = 0x06; | |
| const kDeviceDescriptorType = 0x01; | |
| const kDeviceDescriptorLength = 18; | |
| const kConfigurationDescriptorType = 0x02; | |
| const kConfigurationDescriptorLength = 9; | |
| const kStringDescriptorType = 0x03; | |
| const kStringDescriptorMaxLength = 0xFF; | |
| const kInterfaceDescriptorType = 0x04; | |
| const kInterfaceDescriptorLength = 9; | |
| const kEndpointDescriptorType = 0x05; | |
| const kEndpointDescriptorLength = 7; | |
| let device = null; | |
| let pending_subtests = 0; | |
| const string_tests = new Set(); | |
| const string_languages = []; | |
| async function subtest_complete() { | |
| if (--pending_subtests == 0) { | |
| await device.close(); | |
| } | |
| } | |
| function manual_usb_subtest(func, name, properties) { | |
| pending_subtests++; | |
| promise_test(async (test) => { | |
| test.add_cleanup(subtest_complete); | |
| await func(test); | |
| }, name, properties); | |
| } | |
| function read_string(index, expected) { | |
| // A device may use the same string in multiple places. Don't bother | |
| // repeating the test. | |
| if (string_tests.has(index)) { | |
| return; | |
| } | |
| string_tests.add(index); | |
| const string_values = new Set(); | |
| const decoder = new TextDecoder('utf-16le'); | |
| for (const language of string_languages) { | |
| const language_string = language.toString(16).padStart(4, '0'); | |
| manual_usb_subtest(async (t) => { | |
| if (expected != undefined) { | |
| t.add_cleanup(() => { | |
| if (string_values.size == string_languages.length) { | |
| assert_true(string_values.has(expected)); | |
| } | |
| }); | |
| } | |
| const result = await device.controlTransferIn({ | |
| requestType: 'standard', | |
| recipient: 'device', | |
| request: kGetDescriptorRequest, | |
| value: kStringDescriptorType << 8 | index, | |
| index: language | |
| }, kStringDescriptorMaxLength); | |
| assert_equals(result.status, 'ok', 'transfer status'); | |
| const length = result.data.getUint8(0); | |
| assert_greater_than_equal(length, 2, 'descriptor length'); | |
| assert_equals(result.data.byteLength, length, 'transfer length'); | |
| assert_equals(result.data.getUint8(1), kStringDescriptorType, | |
| 'descriptor type'); | |
| const string_buffer = new Uint8Array( | |
| result.data.buffer, result.data.byteOffset + 2, length - 2); | |
| string_values.add(decoder.decode(string_buffer)); | |
| }, | |
| `Read string descriptor ${index} in language 0x${language_string}`); | |
| } | |
| } | |
| function check_interface_descriptor(configuration, data) { | |
| assert_greater_than_equal( | |
| data.getUint8(0), kInterfaceDescriptorLength, 'descriptor length'); | |
| const interface_number = data.getUint8(2); | |
| const iface = configuration.interfaces.find((iface) => { | |
| return iface.interfaceNumber == interface_number; | |
| }); | |
| assert_not_equals( | |
| iface, undefined, `unknown interface ${interface_number}`); | |
| const alternate_setting = data.getUint8(3); | |
| const alternate = iface.alternates.find((alternate) => { | |
| return alternate.alternateSetting == alternate_setting; | |
| }); | |
| assert_not_equals( | |
| alternate, undefined, `unknown alternate ${alternate_setting}`); | |
| assert_equals(data.getUint8(4), alternate.endpoints.length, | |
| 'number of endpoints'); | |
| assert_equals( | |
| data.getUint8(5), alternate.interfaceClass, 'interface class'); | |
| assert_equals(data.getUint8(6), alternate.interfaceSubclass, | |
| 'interface subclass'); | |
| assert_equals(data.getUint8(7), alternate.interfaceProtocol, | |
| 'interface protocol'); | |
| const interface_string = data.getUint8(8); | |
| if (interface_string != 0) { | |
| // TODO(crbug.com/727819): Check that the string descriptor matches | |
| // iface.interfaceName. | |
| read_string(interface_string); | |
| } | |
| return alternate; | |
| } | |
| function check_endpoint_descriptor(alternate, data) { | |
| assert_greater_than_equal( | |
| data.getUint8(0), kEndpointDescriptorLength, 'descriptor length'); | |
| const endpoint_address = data.getUint8(2); | |
| const direction = endpoint_address & 0x80 ? 'in' : 'out'; | |
| const endpoint_number = endpoint_address & 0x0f; | |
| const endpoint = alternate.endpoints.find((endpoint) => { | |
| return endpoint.direction == direction && | |
| endpoint.endpointNumber == endpoint_number; | |
| }); | |
| assert_not_equals( | |
| endpoint, undefined, `unknown endpoint ${endpoint_number}`); | |
| const attributes = data.getUint8(3); | |
| switch (attributes & 0x03) { | |
| case 0: | |
| assert_equals(endpoint.type, 'control', 'endpoint type'); | |
| break; | |
| case 1: | |
| assert_equals(endpoint.type, 'isochronous', 'endpoint type'); | |
| break; | |
| case 2: | |
| assert_equals(endpoint.type, 'bulk', 'endpoint type'); | |
| break; | |
| case 3: | |
| assert_equals(endpoint.type, 'interrupt', 'endpoint type'); | |
| break; | |
| } | |
| assert_equals(data.getUint16(4, /*littleEndian=*/true), | |
| endpoint.packetSize, 'packet size'); | |
| } | |
| function read_config_descriptor(config_value) { | |
| manual_usb_subtest(async (t) => { | |
| const configuration = device.configurations.find((config) => { | |
| return config.configurationValue == config_value; | |
| }); | |
| assert_not_equals(configuration, undefined); | |
| let result = await device.controlTransferIn({ | |
| requestType: 'standard', | |
| recipient: 'device', | |
| request: kGetDescriptorRequest, | |
| value: kConfigurationDescriptorType << 8 | (config_value - 1), | |
| index: 0 | |
| }, kConfigurationDescriptorLength); | |
| assert_equals(result.status, 'ok', 'transfer status'); | |
| let length = result.data.getUint8(0); | |
| assert_greater_than_equal( | |
| length, kConfigurationDescriptorLength, 'descriptor length'); | |
| assert_equals(result.data.byteLength, length, 'transfer length'); | |
| const total_length = result.data.getUint16(2, /*littleEndian=*/true); | |
| result = await device.controlTransferIn({ | |
| requestType: 'standard', | |
| recipient: 'device', | |
| request: kGetDescriptorRequest, | |
| value: kConfigurationDescriptorType << 8 | (config_value - 1), | |
| index: 0 | |
| }, total_length); | |
| assert_equals(result.status, 'ok', 'transfer status'); | |
| assert_equals( | |
| result.data.byteLength, total_length, 'transfer length'); | |
| assert_equals(result.data.getUint8(0), length, 'descriptor length'); | |
| assert_equals(result.data.getUint8(1), kConfigurationDescriptorType, | |
| 'descriptor type'); | |
| assert_equals(result.data.getUint16(2, /*littleEndian=*/true), | |
| total_length, 'total length'); | |
| assert_equals( | |
| result.data.getUint8(4), configuration.interfaces.length, | |
| 'number of interfaces'); | |
| assert_equals( | |
| result.data.getUint8(5), config_value, 'configuration value'); | |
| const configuration_string = result.data.getUint8(6); | |
| if (configuration_string != 0) { | |
| // TODO(crbug.com/727819): Check that the string descriptor matches | |
| // configuration.configurationName. | |
| read_string(configuration_string); | |
| } | |
| let offset = length; | |
| let alternate = undefined; | |
| while (offset < total_length) { | |
| length = result.data.getUint8(offset); | |
| assert_less_than_equal(offset + length, total_length); | |
| const view = new DataView( | |
| result.data.buffer, result.data.byteOffset + offset, length); | |
| switch (view.getUint8(1)) { | |
| case kConfigurationDescriptorType: | |
| assert_unreached('cannot contain multiple config descriptors'); | |
| break; | |
| case kInterfaceDescriptorType: | |
| alternate = check_interface_descriptor(configuration, view); | |
| break; | |
| case kEndpointDescriptorType: | |
| assert_not_equals(alternate, undefined, | |
| 'endpoint not defined after interface'); | |
| check_endpoint_descriptor(alternate, view); | |
| break; | |
| } | |
| offset += length; | |
| } | |
| }, `Read config descriptor ${config_value}`); | |
| } | |
| function read_string_descriptor_languages(device_descriptor) { | |
| manual_usb_subtest(async (t) => { | |
| const result = await device.controlTransferIn({ | |
| requestType: 'standard', | |
| recipient: 'device', | |
| request: kGetDescriptorRequest, | |
| value: kStringDescriptorType << 8, | |
| index: 0 | |
| }, kStringDescriptorMaxLength); | |
| assert_equals(result.status, 'ok', 'transfer status'); | |
| assert_equals(result.data.getUint8(1), kStringDescriptorType, | |
| 'descriptor type'); | |
| const length = result.data.getUint8(0); | |
| assert_greater_than_equal(length, 2, 'descriptor length') | |
| assert_greater_than_equal( | |
| result.data.byteLength, length, 'transfer length'); | |
| for (let index = 2; index < length; index += 2) { | |
| string_languages.push( | |
| result.data.getUint16(index, /*littleEndian=*/true)); | |
| } | |
| const manufacturer_string = device_descriptor.getUint8(14); | |
| if (manufacturer_string != 0) { | |
| assert_not_equals(device.manufacturerName, undefined); | |
| read_string(manufacturer_string, device.manufacturerName); | |
| } | |
| const product_string = device_descriptor.getUint8(15); | |
| if (product_string != 0) { | |
| assert_not_equals(device.productName, undefined); | |
| read_string(product_string, device.productName); | |
| } | |
| const serial_number_string = device_descriptor.getUint8(16); | |
| if (serial_number_string != 0) { | |
| assert_not_equals(device.serialNumber, undefined); | |
| read_string(serial_number_string, device.serialNumber); | |
| } | |
| const num_configurations = device_descriptor.getUint8(17); | |
| for (let config_value = 1; config_value <= num_configurations; | |
| ++config_value) { | |
| read_config_descriptor(config_value); | |
| } | |
| }, `Read supported languages`); | |
| } | |
| promise_test(async (t) => { | |
| device = await getDeviceForManualTest(); | |
| await device.open(); | |
| const result = await device.controlTransferIn({ | |
| requestType: 'standard', | |
| recipient: 'device', | |
| request: kGetDescriptorRequest, | |
| value: kDeviceDescriptorType << 8, | |
| index: 0, | |
| }, kDeviceDescriptorLength); | |
| assert_equals(result.status, 'ok', 'transfer status'); | |
| assert_equals( | |
| result.data.byteLength, kDeviceDescriptorLength, 'transfer length'); | |
| assert_greater_than_equal( | |
| result.data.getUint8(0), | |
| kDeviceDescriptorLength, 'descriptor length'); | |
| assert_equals(result.data.getUint8(1), kDeviceDescriptorType, | |
| 'descriptor type'); | |
| const bcd_usb = result.data.getUint16(2, /*littleEndian=*/true); | |
| assert_equals( | |
| bcd_usb >> 8, device.usbVersionMajor, 'USB version major'); | |
| assert_equals( | |
| (bcd_usb & 0xf0) >> 4, device.usbVersionMinor, 'USB version minor'); | |
| assert_equals( | |
| bcd_usb & 0xf, device.usbVersionSubminor, 'USV version subminor'); | |
| assert_equals( | |
| result.data.getUint8(4), device.deviceClass, 'device class'); | |
| assert_equals( | |
| result.data.getUint8(5), device.deviceSubclass, 'device subclass'); | |
| assert_equals( | |
| result.data.getUint8(6), device.deviceProtocol, 'device protocol'); | |
| assert_equals(result.data.getUint16(8, /*littleEndian=*/true), | |
| device.vendorId, 'vendor id'); | |
| assert_equals(result.data.getUint16(10, /*littleEndian=*/true), | |
| device.productId, 'product id'); | |
| const bcd_device = result.data.getUint16(12, /*littleEndian=*/true); | |
| assert_equals( | |
| bcd_device >> 8, device.deviceVersionMajor, 'device version major'); | |
| assert_equals((bcd_device & 0xf0) >> 4, device.deviceVersionMinor, | |
| 'device version minor'); | |
| assert_equals(bcd_device & 0xf, device.deviceVersionSubminor, | |
| 'device version subminor'); | |
| assert_equals(result.data.getUint8(17), device.configurations.length, | |
| 'number of configurations'); | |
| read_string_descriptor_languages(result.data); | |
| if (pending_subtests == 0) { | |
| await device.close(); | |
| } | |
| }, 'Read device descriptor'); | |
| </script> | |
| </body> | |
| </html> |