switch-pico/tests/firmware/test_switch2_commands.cpp
Joey Yakimowich-Payne 950c5de1ea Implement Switch2 commands
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

185 lines
7.7 KiB
C++

#include "test_support.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include "../../switch2_commands.h"
namespace {
bool switch2_classifies_captured_report_selection_requests() {
// Given: captured common and Pro report-selection request vectors.
static constexpr uint8_t select_05[] = {
0x03, 0x91, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00,
};
static constexpr uint8_t select_09[] = {
0x03, 0x91, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00,
};
// When/Then: each captured vector selects only its represented report.
CHECK(switch2_classify_vendor_request(select_05, sizeof(select_05)) ==
Switch2VendorCommand::SelectReport05);
CHECK(switch2_classify_vendor_request(select_09, sizeof(select_09)) ==
Switch2VendorCommand::SelectReport09);
return true;
}
bool switch2_builds_exact_report_selection_ack() {
// Given: the two supported report-selection classifications.
static constexpr uint8_t expected[] = {
0x03, 0x01, 0x00, 0x0A, 0x00, 0xF8, 0x00, 0x00,
};
std::array<uint8_t, 12> output{};
// When: either response is built. Then: both equal the captured ACK.
CHECK(switch2_build_vendor_response(
Switch2VendorCommand::SelectReport05, output.data(), output.size()) ==
sizeof(expected));
CHECK(std::memcmp(output.data(), expected, sizeof(expected)) == 0);
output.fill(0);
CHECK(switch2_build_vendor_response(
Switch2VendorCommand::SelectReport09, output.data(), output.size()) ==
sizeof(expected));
CHECK(std::memcmp(output.data(), expected, sizeof(expected)) == 0);
return true;
}
bool switch2_classifies_captured_and_opaque_usb_init_requests() {
// Given: the pinned vector and another opaque host-address payload.
static constexpr uint8_t captured[] = {
0x03, 0x91, 0x00, 0x0D, 0x00, 0x08, 0x00, 0x00,
0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
};
static constexpr uint8_t opaque_address[] = {
0x03, 0x91, 0x00, 0x0D, 0x00, 0x08, 0x00, 0x00,
0x01, 0xA5, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60,
};
// When/Then: both structurally valid requests initialize USB.
CHECK(switch2_classify_vendor_request(captured, sizeof(captured)) ==
Switch2VendorCommand::InitializeUsb);
CHECK(switch2_classify_vendor_request(opaque_address, sizeof(opaque_address)) ==
Switch2VendorCommand::InitializeUsb);
return true;
}
bool switch2_builds_exact_usb_init_ack() {
// Given: the captured initialization acknowledgement.
static constexpr uint8_t expected[] = {
0x03, 0x01, 0x00, 0x0D, 0x00, 0xF8, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00,
};
std::array<uint8_t, sizeof(expected)> output{};
// When: the initialization response is built. Then: every byte is exact.
CHECK(switch2_build_vendor_response(
Switch2VendorCommand::InitializeUsb, output.data(), output.size()) ==
sizeof(expected));
CHECK(std::memcmp(output.data(), expected, sizeof(expected)) == 0);
return true;
}
bool switch2_rejects_malformed_header_and_length_fields() {
// Given: a structurally valid selection request as the mutation baseline.
static constexpr uint8_t valid[] = {
0x03, 0x91, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00,
};
// When/Then: truncation, extension, and every fixed header violation reject.
CHECK(switch2_classify_vendor_request(nullptr, 0) == Switch2VendorCommand::Unsupported);
for (std::size_t length = 0; length < sizeof(valid); ++length) {
CHECK(switch2_classify_vendor_request(valid, length) ==
Switch2VendorCommand::Unsupported);
}
std::array<uint8_t, sizeof(valid) + 1> extended{};
std::memcpy(extended.data(), valid, sizeof(valid));
CHECK(switch2_classify_vendor_request(extended.data(), extended.size()) ==
Switch2VendorCommand::Unsupported);
for (uint8_t offset : {0, 1, 2, 4, 6, 7}) {
std::array<uint8_t, sizeof(valid)> malformed{};
std::memcpy(malformed.data(), valid, sizeof(valid));
++malformed[offset];
CHECK(switch2_classify_vendor_request(malformed.data(), malformed.size()) ==
Switch2VendorCommand::Unsupported);
}
for (uint8_t declared_length : {0x03, 0x05}) {
std::array<uint8_t, sizeof(valid)> malformed{};
std::memcpy(malformed.data(), valid, sizeof(valid));
malformed[5] = declared_length;
CHECK(switch2_classify_vendor_request(malformed.data(), malformed.size()) ==
Switch2VendorCommand::Unsupported);
}
return true;
}
bool switch2_rejects_unsupported_commands_and_payloads() {
// Given: valid framing mutated to unsupported subcommands and payloads.
std::array<uint8_t, 12> request = {
0x03, 0x91, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00,
};
// When/Then: no command outside the captured subset is classified.
request[3] = 0x03;
CHECK(switch2_classify_vendor_request(request.data(), request.size()) ==
Switch2VendorCommand::Unsupported);
request[3] = 0x0A;
request[8] = 0x08;
CHECK(switch2_classify_vendor_request(request.data(), request.size()) ==
Switch2VendorCommand::Unsupported);
request[8] = 0x05;
request[9] = 0x01;
CHECK(switch2_classify_vendor_request(request.data(), request.size()) ==
Switch2VendorCommand::Unsupported);
std::array<uint8_t, 16> init = {
0x03, 0x91, 0x00, 0x0D, 0x00, 0x08, 0x00, 0x00,
0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
};
CHECK(switch2_classify_vendor_request(init.data(), init.size()) ==
Switch2VendorCommand::Unsupported);
return true;
}
bool switch2_response_builder_rejects_unsupported_or_small_outputs() {
// Given: a sentinel output buffer and every unsupported capacity.
std::array<uint8_t, 12> output{};
// When/Then: unsupported, null, and short outputs produce no bytes or writes.
output.fill(0xA5);
CHECK(switch2_build_vendor_response(
Switch2VendorCommand::Unsupported, output.data(), output.size()) == 0);
for (uint8_t byte : output) CHECK(byte == 0xA5);
CHECK(switch2_build_vendor_response(
Switch2VendorCommand::InitializeUsb, nullptr, output.size()) == 0);
for (std::size_t capacity = 0; capacity < 8; ++capacity) {
output.fill(0xA5);
CHECK(switch2_build_vendor_response(
Switch2VendorCommand::SelectReport05, output.data(), capacity) == 0);
for (uint8_t byte : output) CHECK(byte == 0xA5);
}
for (std::size_t capacity = 0; capacity < 12; ++capacity) {
output.fill(0xA5);
CHECK(switch2_build_vendor_response(
Switch2VendorCommand::InitializeUsb, output.data(), capacity) == 0);
for (uint8_t byte : output) CHECK(byte == 0xA5);
}
return true;
}
} // namespace
void run_switch2_command_tests(TestRunner& runner) {
runner.run("Switch 2 captured report selections", switch2_classifies_captured_report_selection_requests);
runner.run("Switch 2 report selection ACK", switch2_builds_exact_report_selection_ack);
runner.run("Switch 2 captured USB initialization", switch2_classifies_captured_and_opaque_usb_init_requests);
runner.run("Switch 2 USB initialization ACK", switch2_builds_exact_usb_init_ack);
runner.run("Switch 2 malformed command framing", switch2_rejects_malformed_header_and_length_fields);
runner.run("Switch 2 unsupported commands", switch2_rejects_unsupported_commands_and_payloads);
runner.run("Switch 2 response capacity", switch2_response_builder_rejects_unsupported_or_small_outputs);
}