Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
185 lines
7.7 KiB
C++
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);
|
|
}
|