348 lines
14 KiB
HTML
348 lines
14 KiB
HTML
<!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>
|