@@ -7,16 +7,56 @@
#include "../tests_common.h"
// standard includes
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <string>
#include <vector>
// moonlight-common-c includes
extern "C" {
#include <moonlight-common-c/src/Input.h>
}
// local includes
#include "src/config.h"
#include "src/input.h"
#include "src/platform/virtualhid_input.h"
#include "src/utility.h"
namespace {
/**
* @brief Protocol metadata for a fixed-size input packet.
*/
struct input_packet_spec_t {
std::uint32_t magic; ///< Packet type identifier.
std::size_t size; ///< Complete fixed packet size.
};
/**
* @brief Create raw input bytes with a caller-selected header and buffer size.
*
* @param magic Packet type identifier.
* @param declared_size Packet size declared after the size field.
* @param actual_size Number of bytes available in the packet buffer.
* @return Raw packet bytes.
*/
std::vector<std::uint8_t> make_input_packet(std::uint32_t magic, std::uint32_t declared_size, std::size_t actual_size) {
std::vector<std::uint8_t> packet(actual_size);
const NV_INPUT_HEADER header {
util::endian::big(declared_size),
util::endian::little(magic),
};
if (!packet.empty()) {
std::memcpy(packet.data(), &header, std::min(packet.size(), sizeof(header)));
}
return packet;
}
/**
* @brief Fixture that installs a fake global virtual input backend.
*/
class InputGamepadSessionTest: public ::testing::Test {
@@ -74,6 +114,141 @@
config::input_t original_input_; ///< Input configuration restored after each test.
};
} // namespace
TEST(InputPacketValidationTest, RejectsEveryBufferShorterThanTheHeader) {
for (std::size_t actual_size = 0; actual_size < sizeof(NV_INPUT_HEADER); ++actual_size) {
const auto packet = make_input_packet(UTF8_TEXT_EVENT_MAGIC, sizeof(std::uint32_t), actual_size);
EXPECT_FALSE(input::testing::is_valid_input_packet(packet)) << "actual_size=" << actual_size;
}
}
TEST(InputPacketValidationTest, RejectsDeclaredSizesOutsideTheAvailableBuffer) {
constexpr std::uint32_t unknown_magic = 0x12345678;
for (std::uint32_t declared_size = 0; declared_size < sizeof(std::uint32_t); ++declared_size) {
const auto packet = make_input_packet(unknown_magic, declared_size, sizeof(NV_INPUT_HEADER));
EXPECT_FALSE(input::testing::is_valid_input_packet(packet)) << "declared_size=" << declared_size;
}
const auto truncated_packet = make_input_packet(unknown_magic, sizeof(std::uint32_t) + 1, sizeof(NV_INPUT_HEADER));
EXPECT_FALSE(input::testing::is_valid_input_packet(truncated_packet));
const auto overflowing_size = make_input_packet(
unknown_magic,
std::numeric_limits<std::uint32_t>::max(),
sizeof(NV_INPUT_HEADER)
);
EXPECT_FALSE(input::testing::is_valid_input_packet(overflowing_size));
}
TEST(InputPacketValidationTest, ValidatesUnicodePacketBoundsWithoutOverflow) {
const auto empty_packet = make_input_packet(UTF8_TEXT_EVENT_MAGIC, sizeof(std::uint32_t), sizeof(NV_INPUT_HEADER));
EXPECT_TRUE(input::testing::is_valid_input_packet(empty_packet));
constexpr std::uint32_t maximum_declared_size = sizeof(std::uint32_t) + UTF8_TEXT_EVENT_MAX_COUNT;
const auto maximum_packet = make_input_packet(
UTF8_TEXT_EVENT_MAGIC,
maximum_declared_size,
sizeof(std::uint32_t) + maximum_declared_size
);
EXPECT_TRUE(input::testing::is_valid_input_packet(maximum_packet));
const auto oversized_text = make_input_packet(
UTF8_TEXT_EVENT_MAGIC,
maximum_declared_size + 1,
sizeof(std::uint32_t) + maximum_declared_size + 1
);
EXPECT_FALSE(input::testing::is_valid_input_packet(oversized_text));
const auto truncated_text = make_input_packet(UTF8_TEXT_EVENT_MAGIC, maximum_declared_size, sizeof(NV_INPUT_HEADER));
EXPECT_FALSE(input::testing::is_valid_input_packet(truncated_text));
}
TEST(InputPacketValidationTest, EnforcesEveryFixedPacketSize) {
constexpr std::array packet_specs {
input_packet_spec_t {MOUSE_MOVE_REL_MAGIC_GEN5, sizeof(NV_REL_MOUSE_MOVE_PACKET)},
input_packet_spec_t {MOUSE_MOVE_ABS_MAGIC, sizeof(NV_ABS_MOUSE_MOVE_PACKET)},
input_packet_spec_t {MOUSE_BUTTON_DOWN_EVENT_MAGIC_GEN5, sizeof(NV_MOUSE_BUTTON_PACKET)},
input_packet_spec_t {MOUSE_BUTTON_UP_EVENT_MAGIC_GEN5, sizeof(NV_MOUSE_BUTTON_PACKET)},
input_packet_spec_t {SCROLL_MAGIC_GEN5, sizeof(NV_SCROLL_PACKET)},
input_packet_spec_t {SS_HSCROLL_MAGIC, sizeof(SS_HSCROLL_PACKET)},
input_packet_spec_t {KEY_DOWN_EVENT_MAGIC, sizeof(NV_KEYBOARD_PACKET)},
input_packet_spec_t {KEY_UP_EVENT_MAGIC, sizeof(NV_KEYBOARD_PACKET)},
input_packet_spec_t {MULTI_CONTROLLER_MAGIC_GEN5, sizeof(NV_MULTI_CONTROLLER_PACKET)},
input_packet_spec_t {SS_TOUCH_MAGIC, sizeof(SS_TOUCH_PACKET)},
input_packet_spec_t {SS_PEN_MAGIC, sizeof(SS_PEN_PACKET)},
input_packet_spec_t {SS_CONTROLLER_ARRIVAL_MAGIC, sizeof(SS_CONTROLLER_ARRIVAL_PACKET)},
input_packet_spec_t {SS_CONTROLLER_TOUCH_MAGIC, sizeof(SS_CONTROLLER_TOUCH_PACKET)},
input_packet_spec_t {SS_CONTROLLER_MOTION_MAGIC, sizeof(SS_CONTROLLER_MOTION_PACKET)},
input_packet_spec_t {SS_CONTROLLER_BATTERY_MAGIC, sizeof(SS_CONTROLLER_BATTERY_PACKET)},
};
for (const auto &[magic, size] : packet_specs) {
const auto declared_size = static_cast<std::uint32_t>(size - sizeof(std::uint32_t));
const auto valid_packet = make_input_packet(magic, declared_size, size);
EXPECT_TRUE(input::testing::is_valid_input_packet(valid_packet)) << "magic=" << magic;
const auto trailing_bytes = make_input_packet(magic, declared_size, size + 8);
EXPECT_TRUE(input::testing::is_valid_input_packet(trailing_bytes)) << "magic=" << magic;
const auto wrong_declared_size = make_input_packet(magic, declared_size - 1, size);
EXPECT_FALSE(input::testing::is_valid_input_packet(wrong_declared_size)) << "magic=" << magic;
const auto oversized_declared_size = make_input_packet(magic, declared_size + 1, size + 1);
EXPECT_FALSE(input::testing::is_valid_input_packet(oversized_declared_size)) << "magic=" << magic;
const auto truncated_packet = make_input_packet(magic, declared_size, size - 1);
EXPECT_FALSE(input::testing::is_valid_input_packet(truncated_packet)) << "magic=" << magic;
}
}
TEST(InputPacketValidationTest, PreservesUnknownPacketHandlingWithinDeclaredBounds) {
constexpr std::uint32_t unknown_magic = 0x12345678;
const auto packet = make_input_packet(unknown_magic, sizeof(std::uint32_t), sizeof(NV_INPUT_HEADER));
EXPECT_TRUE(input::testing::is_valid_input_packet(packet));
}
TEST_F(InputGamepadSessionTest, RejectsMalformedBatchablePacketsAtQueueIngress) {
ASSERT_FALSE(task_pool.running());
const std::shared_ptr<input::input_t> empty_input;
EXPECT_EQ(input::testing::queued_input_packet_count(empty_input), 0);
auto stream_input = input::alloc(std::make_shared<safe::mail_raw_t>(), "packet-validation-client");
ASSERT_NE(stream_input, nullptr);
auto short_header = make_input_packet(SS_PEN_MAGIC, sizeof(std::uint32_t), sizeof(NV_INPUT_HEADER) - 1);
input::passthrough(stream_input, std::move(short_header));
EXPECT_EQ(input::testing::queued_input_packet_count(stream_input), 0);
constexpr std::array batchable_packet_specs {
input_packet_spec_t {MOUSE_MOVE_REL_MAGIC_GEN5, sizeof(NV_REL_MOUSE_MOVE_PACKET)},
input_packet_spec_t {MOUSE_MOVE_ABS_MAGIC, sizeof(NV_ABS_MOUSE_MOVE_PACKET)},
input_packet_spec_t {SCROLL_MAGIC_GEN5, sizeof(NV_SCROLL_PACKET)},
input_packet_spec_t {SS_HSCROLL_MAGIC, sizeof(SS_HSCROLL_PACKET)},
input_packet_spec_t {MULTI_CONTROLLER_MAGIC_GEN5, sizeof(NV_MULTI_CONTROLLER_PACKET)},
input_packet_spec_t {SS_TOUCH_MAGIC, sizeof(SS_TOUCH_PACKET)},
input_packet_spec_t {SS_PEN_MAGIC, sizeof(SS_PEN_PACKET)},
input_packet_spec_t {SS_CONTROLLER_TOUCH_MAGIC, sizeof(SS_CONTROLLER_TOUCH_PACKET)},
input_packet_spec_t {SS_CONTROLLER_MOTION_MAGIC, sizeof(SS_CONTROLLER_MOTION_PACKET)},
};
std::size_t valid_packet_count = 0;
for (const auto &[magic, size] : batchable_packet_specs) {
const auto declared_size = static_cast<std::uint32_t>(size - sizeof(std::uint32_t));
auto truncated_packet = make_input_packet(magic, declared_size, size - 1);
input::passthrough(stream_input, std::move(truncated_packet));
EXPECT_EQ(input::testing::queued_input_packet_count(stream_input), valid_packet_count) << "magic=" << magic;
auto valid_packet = make_input_packet(magic, declared_size, size);
input::passthrough(stream_input, std::move(valid_packet));
++valid_packet_count;
EXPECT_EQ(input::testing::queued_input_packet_count(stream_input), valid_packet_count) << "magic=" << magic;
auto oversized_declared_size = make_input_packet(magic, declared_size + 1, size + 1);
input::passthrough(stream_input, std::move(oversized_declared_size));
EXPECT_EQ(input::testing::queued_input_packet_count(stream_input), valid_packet_count) << "magic=" << magic;
}
}
TEST_F(InputGamepadSessionTest, ReusesGamepadsAcrossPauseAndDestroysThemOnTermination) {
const std::string session_id = "paired-client-certificate";