switch-pico/tests/firmware/test_legacy_descriptors.cpp
Joey Yakimowich-Payne e2a7635f2f Harden legacy bounds
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

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

173 lines
8.8 KiB
C++

#include "test_support.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include "../../switch_pro_descriptors.h"
#include "../../switch_pro_bounds.h"
namespace {
template <std::size_t ActualSize, std::size_t ExpectedSize>
bool bytes_equal(
const uint8_t (&actual)[ActualSize],
const uint8_t (&expected)[ExpectedSize]) {
return ActualSize == ExpectedSize &&
std::memcmp(actual, expected, ExpectedSize) == 0;
}
bool legacy_device_descriptor_matches_exact_bytes() {
// Given: the captured legacy device descriptor bytes.
static constexpr uint8_t expected[] = {
0x12, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x40, 0x7E,
0x05, 0x09, 0x20, 0x10, 0x02, 0x01, 0x02, 0x03, 0x01,
};
// When: the compiled legacy descriptor is inspected.
// Then: its identity and all 18 bytes remain unchanged.
CHECK(sizeof(switch_pro_device_descriptor) == 18);
CHECK(bytes_equal(switch_pro_device_descriptor, expected));
return true;
}
bool legacy_configuration_descriptor_matches_exact_bytes() {
// Given: the captured single-interface legacy configuration.
static constexpr uint8_t expected[] = {
0x09, 0x02, 0x29, 0x00, 0x01, 0x01, 0x00, 0xA0, 0xFA,
0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00,
0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0xCB, 0x00,
0x07, 0x05, 0x81, 0x03, 0x40, 0x00, 0x08,
0x07, 0x05, 0x01, 0x03, 0x40, 0x00, 0x08,
};
// When: the compiled legacy configuration is inspected.
// Then: the interface and both interrupt endpoints remain byte-identical.
CHECK(sizeof(switch_pro_configuration_descriptor) == 41);
CHECK(bytes_equal(switch_pro_configuration_descriptor, expected));
return true;
}
bool legacy_hid_report_descriptor_matches_exact_bytes() {
// Given: the complete captured 203-byte legacy HID report descriptor.
static constexpr uint8_t expected[] = {
0x05, 0x01, 0x15, 0x00, 0x09, 0x04, 0xA1, 0x01, 0x85, 0x30, 0x05, 0x01, 0x05, 0x09, 0x19, 0x01,
0x29, 0x0A, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0A, 0x55, 0x00, 0x65, 0x00, 0x81, 0x02,
0x05, 0x09, 0x19, 0x0B, 0x29, 0x0E, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81, 0x02,
0x75, 0x01, 0x95, 0x02, 0x81, 0x03, 0x0B, 0x01, 0x00, 0x01, 0x00, 0xA1, 0x00, 0x0B, 0x30, 0x00,
0x01, 0x00, 0x0B, 0x31, 0x00, 0x01, 0x00, 0x0B, 0x32, 0x00, 0x01, 0x00, 0x0B, 0x35, 0x00, 0x01,
0x00, 0x15, 0x00, 0x27, 0xFF, 0xFF, 0x00, 0x00, 0x75, 0x10, 0x95, 0x04, 0x81, 0x02, 0xC0, 0x0B,
0x39, 0x00, 0x01, 0x00, 0x15, 0x00, 0x25, 0x07, 0x35, 0x00, 0x46, 0x3B, 0x01, 0x65, 0x14, 0x75,
0x04, 0x95, 0x01, 0x81, 0x02, 0x05, 0x09, 0x19, 0x0F, 0x29, 0x12, 0x15, 0x00, 0x25, 0x01, 0x75,
0x01, 0x95, 0x04, 0x81, 0x02, 0x75, 0x08, 0x95, 0x34, 0x81, 0x03, 0x06, 0x00, 0xFF, 0x85, 0x21,
0x09, 0x01, 0x75, 0x08, 0x95, 0x3F, 0x81, 0x03, 0x85, 0x81, 0x09, 0x02, 0x75, 0x08, 0x95, 0x3F,
0x81, 0x03, 0x85, 0x01, 0x09, 0x03, 0x75, 0x08, 0x95, 0x3F, 0x91, 0x83, 0x85, 0x10, 0x09, 0x04,
0x75, 0x08, 0x95, 0x3F, 0x91, 0x83, 0x85, 0x80, 0x09, 0x05, 0x75, 0x08, 0x95, 0x3F, 0x91, 0x83,
0x85, 0x82, 0x09, 0x06, 0x75, 0x08, 0x95, 0x3F, 0x91, 0x83, 0xC0,
};
// When: the compiled legacy HID descriptor is inspected.
// Then: every report item remains byte-identical.
CHECK(sizeof(switch_pro_report_descriptor) == 203);
CHECK(bytes_equal(switch_pro_report_descriptor, expected));
return true;
}
bool legacy_string_descriptors_match_exact_bytes() {
// Given: the legacy language, manufacturer, product, and serial strings.
static constexpr uint8_t language[] = {0x09, 0x04};
static constexpr uint8_t manufacturer[] = "Nintendo Co., Ltd.";
static constexpr uint8_t product[] = "Pro Controller";
static constexpr uint8_t version[] = "000000000001";
// When: the compiled string tables are inspected.
// Then: every string byte and terminator remains unchanged.
CHECK(bytes_equal(switch_pro_string_language, language));
CHECK(bytes_equal(switch_pro_string_manufacturer, manufacturer));
CHECK(bytes_equal(switch_pro_string_product, product));
CHECK(bytes_equal(switch_pro_string_version, version));
return true;
}
bool legacy_output_classifier_rejects_invalid_report_framing() {
const uint8_t report[] = {0x01, 0x00};
CHECK(switch_pro_classify_output_report(nullptr, 2) == SwitchProOutputReportKind::Ignore);
CHECK(switch_pro_classify_output_report(report, 0) == SwitchProOutputReportKind::Ignore);
CHECK(switch_pro_classify_output_report(report, 1) == SwitchProOutputReportKind::Ignore);
CHECK(switch_pro_classify_output_report(report, 65) == SwitchProOutputReportKind::Ignore);
return true;
}
bool legacy_feature_reports_reject_short_payloads_and_accept_bounds() {
const uint8_t report[] = {0x01, 0x00};
CHECK(switch_pro_classify_output_report(report, 15) == SwitchProOutputReportKind::Ignore);
CHECK(switch_pro_classify_output_report(report, 16) == SwitchProOutputReportKind::Feature);
CHECK(switch_pro_classify_output_report(report, 64) == SwitchProOutputReportKind::Feature);
return true;
}
bool legacy_configuration_and_rumble_reports_reject_short_payloads() {
const uint8_t configuration[] = {0x80, 0x00};
const uint8_t rumble[] = {0x10, 0x00};
const uint8_t noop[] = {0x00, 0x00};
CHECK(switch_pro_classify_output_report(configuration, 1) == SwitchProOutputReportKind::Ignore);
CHECK(switch_pro_classify_output_report(configuration, 2) == SwitchProOutputReportKind::Configuration);
CHECK(switch_pro_classify_output_report(rumble, 9) == SwitchProOutputReportKind::Ignore);
CHECK(switch_pro_classify_output_report(rumble, 10) == SwitchProOutputReportKind::Rumble);
CHECK(switch_pro_classify_output_report(noop, 2) == SwitchProOutputReportKind::Noop);
return true;
}
bool legacy_spi_read_rejects_payload_overflow_at_forty_five_bytes() {
CHECK(switch_pro_spi_read_size_fits(0));
CHECK(switch_pro_spi_read_size_fits(44));
CHECK(!switch_pro_spi_read_size_fits(45));
CHECK(!switch_pro_spi_read_size_fits(255));
return true;
}
bool legacy_flash_read_copies_in_range_data_through_exact_end() {
const uint8_t source[] = {1, 2, 3, 4};
uint8_t destination[] = {0xAA, 0xAA, 0xAA, 0xAA};
CHECK(switch_pro_fill_flash_read(destination, 4, source, 4, 0, 4) == 4);
CHECK(destination[0] == 1 && destination[3] == 4);
return true;
}
bool legacy_flash_read_prefills_partial_source_end_with_ff() {
const uint8_t source[] = {1, 2};
uint8_t destination[] = {0xAA, 0xAA, 0xAA, 0xAA};
CHECK(switch_pro_fill_flash_read(destination, 4, source, 2, 0, 4) == 4);
CHECK(destination[0] == 1 && destination[1] == 2);
CHECK(destination[2] == 0xFF && destination[3] == 0xFF);
return true;
}
bool legacy_flash_read_leaves_canaries_on_invalid_range_or_null_source() {
const uint8_t source[] = {1, 2};
uint8_t destination[] = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA};
CHECK(switch_pro_fill_flash_read(destination + 1, 4, source, 2, 3, 4) == 4);
CHECK(destination[0] == 0xAA && destination[5] == 0xAA);
CHECK(destination[1] == 0xFF && destination[4] == 0xFF);
CHECK(switch_pro_fill_flash_read(destination + 1, 4, nullptr, 2, 0, 4) == 4);
CHECK(destination[0] == 0xAA && destination[5] == 0xAA);
CHECK(destination[1] == 0xFF && destination[4] == 0xFF);
CHECK(switch_pro_fill_flash_read(nullptr, 4, source, 2, 0, 4) == 0);
return true;
}
} // namespace
void run_legacy_descriptor_tests(TestRunner& runner) {
runner.run("legacy device descriptor exact bytes", legacy_device_descriptor_matches_exact_bytes);
runner.run("legacy configuration descriptor exact bytes", legacy_configuration_descriptor_matches_exact_bytes);
runner.run("legacy HID report descriptor exact bytes", legacy_hid_report_descriptor_matches_exact_bytes);
runner.run("legacy string descriptors exact bytes", legacy_string_descriptors_match_exact_bytes);
runner.run("legacy output classifier rejects invalid report framing", legacy_output_classifier_rejects_invalid_report_framing);
runner.run("legacy feature reports reject short payloads and accept bounds", legacy_feature_reports_reject_short_payloads_and_accept_bounds);
runner.run("legacy configuration and rumble reports reject short payloads", legacy_configuration_and_rumble_reports_reject_short_payloads);
runner.run("legacy SPI read rejects payload overflow at 45 bytes", legacy_spi_read_rejects_payload_overflow_at_forty_five_bytes);
runner.run("legacy flash read copies in-range data through exact end", legacy_flash_read_copies_in_range_data_through_exact_end);
runner.run("legacy flash read prefills partial source end with FF", legacy_flash_read_prefills_partial_source_end_with_ff);
runner.run("legacy flash read leaves canaries on invalid range or null source", legacy_flash_read_leaves_canaries_on_invalid_range_or_null_source);
}