#include "test_support.h" #include #include #include #include #include "../../switch_input.h" #include "../../switch_uart_protocol.h" namespace { using ImuSamples = std::vector; void append_int16(std::vector& bytes, int16_t value) { const uint16_t encoded = static_cast(value); bytes.push_back(static_cast(encoded & 0xFF)); bytes.push_back(static_cast(encoded >> 8)); } std::vector make_frame( uint16_t buttons = 0, uint8_t hat = SWITCH_PRO_HAT_NOTHING, uint8_t imu_count = 0, const ImuSamples& samples = {}) { std::vector frame = { 0xAA, 0x02, 0x00, static_cast(buttons & 0xFF), static_cast(buttons >> 8), hat, 0x80, 0x80, 0x80, 0x80, imu_count, }; for (const SwitchImuSample& sample : samples) { append_int16(frame, sample.accel_x); append_int16(frame, sample.accel_y); append_int16(frame, sample.accel_z); append_int16(frame, sample.gyro_x); append_int16(frame, sample.gyro_y); append_int16(frame, sample.gyro_z); } frame[2] = static_cast(frame.size() - 3); uint8_t checksum = 0; for (uint8_t byte : frame) { checksum = static_cast(checksum + byte); } frame.push_back(checksum); return frame; } bool decode(const std::vector& frame, SwitchInputState& state) { return switch_uart_decode_input_frame( frame.data(), static_cast(frame.size()), &state); } bool uart_decoder_rejects_short_frame() { // Given: a frame shorter than the legacy 12-byte minimum. const std::array frame{}; SwitchInputState state{}; // When: the frame is decoded. Then: it is rejected. CHECK(!switch_uart_decode_input_frame(frame.data(), frame.size(), &state)); return true; } bool uart_decoder_rejects_wrong_header_and_version() { // Given: otherwise-valid frames with invalid framing bytes. std::vector wrong_header = make_frame(); std::vector wrong_version = make_frame(); wrong_header[0] = 0xAB; wrong_version[1] = 0x01; SwitchInputState state{}; // When: either frame is decoded. Then: both are rejected before payload use. CHECK(!decode(wrong_header, state)); CHECK(!decode(wrong_version, state)); return true; } bool uart_decoder_rejects_declared_length_and_checksum_mismatch() { // Given: valid frames corrupted independently at length and checksum. std::vector wrong_length = make_frame(); std::vector wrong_checksum = make_frame(); ++wrong_length[2]; ++wrong_checksum.back(); SwitchInputState state{}; state.lx = 0x1234; // When: either frame is decoded. Then: both validation failures are rejected. CHECK(!decode(wrong_length, state)); CHECK(!decode(wrong_checksum, state)); CHECK(state.lx == 0x1234); return true; } bool uart_decoder_decodes_neutral_frame() { // Given: the canonical 12-byte neutral frame. const std::vector frame = make_frame(); SwitchInputState state{}; // When: the frame is decoded. Then: buttons/hat/IMU are clear and sticks expand exactly. CHECK(decode(frame, state)); CHECK(!state.dpad_up && !state.dpad_down && !state.dpad_left && !state.dpad_right); CHECK(!state.button_a && !state.button_b && !state.button_x && !state.button_y); CHECK(!state.button_l && !state.button_r && !state.button_zl && !state.button_zr); CHECK(!state.button_plus && !state.button_minus && !state.button_home && !state.button_capture); CHECK(!state.button_l3 && !state.button_r3); CHECK(state.lx == 0x8080 && state.ly == 0x8080); CHECK(state.rx == 0x8080 && state.ry == 0x8080); CHECK(state.imu_sample_count == 0); return true; } bool uart_decoder_maps_every_button_bit() { struct ButtonCase { uint16_t mask; bool SwitchInputState::*field; }; static constexpr ButtonCase cases[] = { {SWITCH_PRO_MASK_Y, &SwitchInputState::button_y}, {SWITCH_PRO_MASK_B, &SwitchInputState::button_b}, {SWITCH_PRO_MASK_A, &SwitchInputState::button_a}, {SWITCH_PRO_MASK_X, &SwitchInputState::button_x}, {SWITCH_PRO_MASK_L, &SwitchInputState::button_l}, {SWITCH_PRO_MASK_R, &SwitchInputState::button_r}, {SWITCH_PRO_MASK_ZL, &SwitchInputState::button_zl}, {SWITCH_PRO_MASK_ZR, &SwitchInputState::button_zr}, {SWITCH_PRO_MASK_MINUS, &SwitchInputState::button_minus}, {SWITCH_PRO_MASK_PLUS, &SwitchInputState::button_plus}, {SWITCH_PRO_MASK_L3, &SwitchInputState::button_l3}, {SWITCH_PRO_MASK_R3, &SwitchInputState::button_r3}, {SWITCH_PRO_MASK_HOME, &SwitchInputState::button_home}, {SWITCH_PRO_MASK_CAPTURE, &SwitchInputState::button_capture}, }; // Given/When: each legacy button bit is decoded independently. for (const ButtonCase& button : cases) { SwitchInputState state{}; CHECK(decode(make_frame(button.mask), state)); // Then: the corresponding shared input field is set. CHECK(state.*(button.field)); const int pressed_count = state.button_y + state.button_b + state.button_a + state.button_x + state.button_l + state.button_r + state.button_zl + state.button_zr + state.button_minus + state.button_plus + state.button_l3 + state.button_r3 + state.button_home + state.button_capture; CHECK(pressed_count == 1); } return true; } bool uart_decoder_maps_every_hat_value() { struct HatCase { uint8_t hat; bool up; bool down; bool left; bool right; }; static constexpr HatCase cases[] = { {SWITCH_PRO_HAT_UP, true, false, false, false}, {SWITCH_PRO_HAT_UPRIGHT, true, false, false, true}, {SWITCH_PRO_HAT_RIGHT, false, false, false, true}, {SWITCH_PRO_HAT_DOWNRIGHT, false, true, false, true}, {SWITCH_PRO_HAT_DOWN, false, true, false, false}, {SWITCH_PRO_HAT_DOWNLEFT, false, true, true, false}, {SWITCH_PRO_HAT_LEFT, false, false, true, false}, {SWITCH_PRO_HAT_UPLEFT, true, false, true, false}, {SWITCH_PRO_HAT_NOTHING, false, false, false, false}, {0xFF, false, false, false, false}, }; // Given/When: every legacy hat value is decoded. for (const HatCase& hat : cases) { SwitchInputState state{}; CHECK(decode(make_frame(0, hat.hat), state)); // Then: its exact cardinal/diagonal field combination is produced. CHECK(state.dpad_up == hat.up && state.dpad_down == hat.down); CHECK(state.dpad_left == hat.left && state.dpad_right == hat.right); } return true; } bool uart_decoder_expands_stick_bytes() { // Given: a valid frame with distinct byte values on every axis. std::vector frame = make_frame(); frame[6] = 0x00; frame[7] = 0x7F; frame[8] = 0x80; frame[9] = 0xFF; frame.back() = 0; for (std::size_t index = 0; index + 1 < frame.size(); ++index) { frame.back() = static_cast(frame.back() + frame[index]); } SwitchInputState state{}; // When: the frame is decoded. Then: each byte is duplicated into 16 bits. CHECK(decode(frame, state)); CHECK(state.lx == 0x0000 && state.ly == 0x7F7F); CHECK(state.rx == 0x8080 && state.ry == 0xFFFF); return true; } bool uart_decoder_decodes_one_and_three_imu_samples() { // Given: one-sample and three-sample frames with signed extrema and distinct values. const SwitchImuSample first{-32768, -2, -1, 0, 1, 32767}; const SwitchImuSample second{10, 20, 30, 40, 50, 60}; const SwitchImuSample third{-10, -20, -30, -40, -50, -60}; SwitchInputState one{}; SwitchInputState three{}; // When: both frames are decoded. CHECK(decode(make_frame(0, SWITCH_PRO_HAT_NOTHING, 1, {first}), one)); CHECK(decode(make_frame(0, SWITCH_PRO_HAT_NOTHING, 3, {first, second, third}), three)); // Then: counts and little-endian signed sample fields remain exact. CHECK(one.imu_sample_count == 1 && one.imu_samples[0].accel_x == -32768); CHECK(one.imu_samples[0].gyro_z == 32767); CHECK(three.imu_sample_count == 3); CHECK(three.imu_samples[1].accel_z == 30 && three.imu_samples[1].gyro_y == 50); CHECK(three.imu_samples[2].accel_y == -20 && three.imu_samples[2].gyro_z == -60); return true; } bool uart_decoder_caps_imu_count_and_rejects_truncation() { // Given: a count of four backed by three samples, and a count of one with none. const SwitchImuSample sample{1, 2, 3, 4, 5, 6}; const std::vector capped = make_frame(0, SWITCH_PRO_HAT_NOTHING, 4, {sample, sample, sample}); const std::vector truncated = make_frame(0, SWITCH_PRO_HAT_NOTHING, 1); SwitchInputState state{}; // When: both frames are decoded. Then: three samples are accepted and truncation is rejected. CHECK(decode(capped, state)); CHECK(state.imu_sample_count == 3); CHECK(!decode(truncated, state)); return true; } bool uart_decoder_rejects_null_output() { // Given: an otherwise-valid frame. When: no output state is supplied. const std::vector frame = make_frame(); // Then: the legacy parser returns false rather than mutating driver state. CHECK(!switch_uart_decode_input_frame(frame.data(), frame.size(), nullptr)); return true; } } // namespace void run_switch_uart_protocol_tests(TestRunner& runner) { runner.run("UART rejects short frame", uart_decoder_rejects_short_frame); runner.run("UART rejects header and version", uart_decoder_rejects_wrong_header_and_version); runner.run("UART rejects length and checksum", uart_decoder_rejects_declared_length_and_checksum_mismatch); runner.run("UART decodes neutral frame", uart_decoder_decodes_neutral_frame); runner.run("UART maps every button", uart_decoder_maps_every_button_bit); runner.run("UART maps every hat", uart_decoder_maps_every_hat_value); runner.run("UART expands stick bytes", uart_decoder_expands_stick_bytes); runner.run("UART decodes IMU samples", uart_decoder_decodes_one_and_three_imu_samples); runner.run("UART caps and validates IMU count", uart_decoder_caps_imu_count_and_rejects_truncation); runner.run("UART rejects null output", uart_decoder_rejects_null_output); }