<!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>