switch-pico/tests/firmware/test_switch2_descriptors.cpp
Joey Yakimowich-Payne 3251e49191 Implement Switch2 descriptors
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-08-11 12:23:07 +09:00

166 lines
7.6 KiB
C++

#include "test_support.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include "../../switch2_descriptors.h"
namespace {
bool bytes_equal(
const uint8_t* actual,
std::size_t actual_length,
const uint8_t* expected,
std::size_t expected_length) {
return actual_length == expected_length &&
std::memcmp(actual, expected, expected_length) == 0;
}
bool switch2_descriptor_bytes_match_pinned_capture_subset() {
// Given: captured Pro Controller 2 bytes with the documented subset edits.
static constexpr uint8_t expected_device[] = {
0x12, 0x01, 0x00, 0x02, 0xEF, 0x02, 0x01, 0x40, 0x7E,
0x05, 0x69, 0x20, 0x00, 0x02, 0x01, 0x02, 0x03, 0x01,
};
static constexpr uint8_t expected_configuration[] = {
0x09, 0x02, 0x50, 0x00, 0x02, 0x01, 0x00, 0xC0, 0xFA,
0x08, 0x0B, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00,
0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00,
0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x61, 0x00,
0x07, 0x05, 0x81, 0x03, 0x40, 0x00, 0x04,
0x07, 0x05, 0x01, 0x03, 0x40, 0x00, 0x04,
0x08, 0x0B, 0x01, 0x01, 0xFF, 0x00, 0x00, 0x00,
0x09, 0x04, 0x01, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x00,
0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
0x07, 0x05, 0x82, 0x02, 0x40, 0x00, 0x00,
};
static constexpr uint8_t expected_hid[] = {
0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x05, 0x05, 0xFF, 0x09, 0x01, 0x15, 0x00, 0x26, 0xFF,
0x00, 0x95, 0x3F, 0x75, 0x08, 0x81, 0x02, 0x85, 0x09, 0x09, 0x01, 0x95, 0x02, 0x81, 0x02, 0x05,
0x09, 0x19, 0x01, 0x29, 0x15, 0x25, 0x01, 0x95, 0x15, 0x75, 0x01, 0x81, 0x02, 0x95, 0x01, 0x75,
0x03, 0x81, 0x03, 0x05, 0x01, 0x09, 0x01, 0xA1, 0x00, 0x09, 0x30, 0x09, 0x31, 0x09, 0x33, 0x09,
0x35, 0x26, 0xFF, 0x0F, 0x95, 0x04, 0x75, 0x0C, 0x81, 0x02, 0xC0, 0x05, 0xFF, 0x09, 0x02, 0x26,
0xFF, 0x00, 0x95, 0x34, 0x75, 0x08, 0x81, 0x02, 0x85, 0x02, 0x09, 0x01, 0x95, 0x3F, 0x91, 0x02,
0xC0,
};
// When/Then: all exported descriptor bytes and lengths match exactly.
CHECK(bytes_equal(switch2_device_descriptor, switch2_device_descriptor_length,
expected_device, sizeof(expected_device)));
CHECK(bytes_equal(switch2_configuration_descriptor, switch2_configuration_descriptor_length,
expected_configuration, sizeof(expected_configuration)));
CHECK(bytes_equal(switch2_hid_report_descriptor, switch2_hid_report_descriptor_length,
expected_hid, sizeof(expected_hid)));
return true;
}
bool switch2_configuration_iterates_two_interfaces_and_four_endpoints() {
// Given: the deliberately reduced configuration descriptor.
std::size_t offset = 0;
uint8_t interface_count = 0;
uint8_t endpoint_count = 0;
const uint8_t expected_endpoints[][5] = {
{0x81, 0x03, 0x40, 0x00, 0x04},
{0x01, 0x03, 0x40, 0x00, 0x04},
{0x02, 0x02, 0x40, 0x00, 0x00},
{0x82, 0x02, 0x40, 0x00, 0x00},
};
// When: every USB descriptor is iterated by bLength.
while (offset < switch2_configuration_descriptor_length) {
const uint8_t length = switch2_configuration_descriptor[offset];
CHECK(length >= 2);
CHECK(offset + length <= switch2_configuration_descriptor_length);
const uint8_t type = switch2_configuration_descriptor[offset + 1];
if (type == 0x04) {
CHECK(length == 9);
CHECK(switch2_configuration_descriptor[offset + 5] != 0x01);
CHECK(switch2_configuration_descriptor[offset + 8] == 0x00);
++interface_count;
} else if (type == 0x05) {
CHECK(length == 7);
CHECK(endpoint_count < 4);
CHECK(std::memcmp(&switch2_configuration_descriptor[offset + 2],
expected_endpoints[endpoint_count], 5) == 0);
++endpoint_count;
}
offset += length;
}
// Then: iteration is exact, contains only HID/vendor, and omits audio.
CHECK(offset == switch2_configuration_descriptor_length);
CHECK(switch2_configuration_descriptor[6] == 0x00);
CHECK(interface_count == 2);
CHECK(endpoint_count == 4);
return true;
}
bool switch2_hid_reports_are_exactly_sixty_three_payload_bytes() {
// Given: the captured HID report descriptor.
uint32_t report_size = 0;
uint32_t report_count = 0;
uint32_t report_id = 0;
uint32_t input_05_bits = 0;
uint32_t input_09_bits = 0;
uint32_t output_02_bits = 0;
// When: HID short items are parsed and report fields accumulated.
for (std::size_t offset = 0; offset < switch2_hid_report_descriptor_length;) {
const uint8_t prefix = switch2_hid_report_descriptor[offset++];
CHECK(prefix != 0xFE);
const uint8_t size_code = prefix & 0x03;
const uint8_t data_size = size_code == 3 ? 4 : size_code;
CHECK(offset + data_size <= switch2_hid_report_descriptor_length);
uint32_t value = 0;
for (uint8_t index = 0; index < data_size; ++index) {
value |= static_cast<uint32_t>(switch2_hid_report_descriptor[offset + index]) << (8 * index);
}
offset += data_size;
const uint8_t type = (prefix >> 2) & 0x03;
const uint8_t tag = (prefix >> 4) & 0x0F;
if (type == 1 && tag == 7) report_size = value;
if (type == 1 && tag == 8) report_id = value;
if (type == 1 && tag == 9) report_count = value;
if (type == 0 && tag == 8 && report_id == 0x05) input_05_bits += report_size * report_count;
if (type == 0 && tag == 8 && report_id == 0x09) input_09_bits += report_size * report_count;
if (type == 0 && tag == 9 && report_id == 0x02) output_02_bits += report_size * report_count;
}
// Then: each supported transfer is 63 payload bytes plus its report ID.
CHECK(input_05_bits == 63 * 8);
CHECK(input_09_bits == 63 * 8);
CHECK(output_02_bits == 63 * 8);
return true;
}
bool switch2_string_bytes_match_pinned_capture() {
// Given/When: the known language and ASCII strings are inspected.
static constexpr uint8_t language[] = {0x09, 0x04};
static constexpr uint8_t manufacturer[] = "Nintendo";
static constexpr uint8_t product[] = "Switch 2 Pro Controller";
static constexpr uint8_t serial[] = "00";
// Then: exported lengths exclude the C terminator and bytes remain exact.
CHECK(bytes_equal(switch2_string_language, switch2_string_language_length,
language, sizeof(language)));
CHECK(bytes_equal(switch2_string_manufacturer, switch2_string_manufacturer_length,
manufacturer, sizeof(manufacturer) - 1));
CHECK(bytes_equal(switch2_string_product, switch2_string_product_length,
product, sizeof(product) - 1));
CHECK(bytes_equal(switch2_string_serial, switch2_string_serial_length,
serial, sizeof(serial) - 1));
CHECK(switch2_string_manufacturer[switch2_string_manufacturer_length] == 0);
CHECK(switch2_string_product[switch2_string_product_length] == 0);
CHECK(switch2_string_serial[switch2_string_serial_length] == 0);
return true;
}
} // namespace
void run_switch2_descriptor_tests(TestRunner& runner) {
runner.run("Switch 2 descriptor exact bytes", switch2_descriptor_bytes_match_pinned_capture_subset);
runner.run("Switch 2 configuration iteration", switch2_configuration_iterates_two_interfaces_and_four_endpoints);
runner.run("Switch 2 HID report sizes", switch2_hid_reports_are_exactly_sixty_three_payload_bytes);
runner.run("Switch 2 string bytes", switch2_string_bytes_match_pinned_capture);
}