#include "test_support.h" #include #include #include #include "../../switch_pro_descriptors.h" #include "../../switch_pro_bounds.h" namespace { template 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); }