Files
libvirtualhid/tests/unit/test_windows_driver_protocol.cpp

364 lines
15 KiB
C++

/**
* @file tests/unit/test_windows_driver_protocol.cpp
* @brief Unit tests for private Windows driver identity and controller protocol helpers.
*/
// local includes
#include "fixtures/fixtures.hpp"
#include "playstation_feature_protocol.hpp"
#include "shared/switch_pro_protocol.hpp"
#include "vhf_report_response.hpp"
#include "windows_device_identity.hpp"
// standard includes
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <span>
#include <string>
#include <vector>
namespace {
class WindowsDriverProtocolTest: public WindowsTest {};
std::vector<std::wstring> hardware_id_entries(const std::wstring &hardware_ids) {
std::vector<std::wstring> entries;
auto offset = std::size_t {};
while (offset < hardware_ids.size() && hardware_ids[offset] != L'\0') {
const auto end = hardware_ids.find(L'\0', offset);
entries.emplace_back(hardware_ids.substr(offset, end - offset));
offset = end + 1U;
}
return entries;
}
LvhWindowsCreateDeviceRequest series_request() {
LvhWindowsCreateDeviceRequest request {};
request.device_type = LVH_WINDOWS_DEVICE_GAMEPAD;
request.gamepad_kind = LVH_WINDOWS_GAMEPAD_XBOX_SERIES;
request.hardware_ids.vendor_id = 0x045E;
request.hardware_ids.product_id = 0x0B12;
request.hardware_ids.device_version = 0x0500;
return request;
}
LvhWindowsCreateDeviceRequest playstation_request(
std::uint32_t gamepad_kind,
std::uint32_t bus_type = LVH_WINDOWS_BUS_USB,
std::string_view stable_id = "10:20:30:40:50:60"
) {
LvhWindowsCreateDeviceRequest request {};
request.device_type = LVH_WINDOWS_DEVICE_GAMEPAD;
request.client_device_id = 0x12345678U;
request.gamepad_kind = gamepad_kind;
request.bus_type = bus_type;
request.report_sizes.stable_id_size = static_cast<std::uint32_t>(stable_id.size());
std::ranges::copy(stable_id, request.stable_id.begin());
return request;
}
std::array<std::uint8_t, lvh::detail::switch_pro_protocol::switch_pro_report_size> switch_output_report(
std::uint8_t report_id,
std::uint8_t command
) {
std::array<std::uint8_t, lvh::detail::switch_pro_protocol::switch_pro_report_size> report {};
report[0] = report_id;
if (report_id == 0x80U) {
report[1] = command;
} else {
report[1] = 0x07;
report[10] = command;
}
return report;
}
void write_u32(std::span<std::uint8_t> report, std::size_t offset, std::uint32_t value) {
report[offset] = static_cast<std::uint8_t>(value & 0xFFU);
report[offset + 1U] = static_cast<std::uint8_t>((value >> 8U) & 0xFFU);
report[offset + 2U] = static_cast<std::uint8_t>((value >> 16U) & 0xFFU);
report[offset + 3U] = static_cast<std::uint8_t>((value >> 24U) & 0xFFU);
}
} // namespace
TEST_F(WindowsDriverProtocolTest, VhfFeatureResponseDoesNotWriteToAdvertisedPadding) {
const auto request = playstation_request(LVH_WINDOWS_GAMEPAD_DUALSENSE);
for (const auto report_id : {0x05U, 0x09U, 0x20U}) {
SCOPED_TRACE(report_id);
const auto report = lvh::detail::windows::make_playstation_feature_report(request, report_id);
ASSERT_TRUE(report.has_value());
ASSERT_LE(report->size(), 64U);
// VHF allocates 64 bytes but advertises 128 in issue #199's PageHeap dump.
// Extra backing storage lets the test detect the overrun without invoking UB.
auto buffer = std::array<std::uint8_t, 128> {};
buffer.fill(0xA5);
ASSERT_TRUE(lvh::detail::windows::copy_vhf_report_response(buffer.data(), 128U, *report));
EXPECT_TRUE(std::ranges::equal(*report, std::span {buffer}.first(report->size())));
EXPECT_TRUE(std::ranges::all_of(std::span {buffer}.subspan(report->size()), [](auto byte) {
return byte == 0xA5;
}));
}
}
TEST_F(WindowsDriverProtocolTest, VhfInputResponseDoesNotWritePastTheReport) {
auto report = std::array<std::uint8_t, 64> {};
report.fill(0x5A);
report[0] = 0x01;
auto buffer = std::array<std::uint8_t, 128> {};
buffer.fill(0xA5);
ASSERT_TRUE(lvh::detail::windows::copy_vhf_report_response(buffer.data(), buffer.size(), report));
EXPECT_TRUE(std::ranges::equal(report, std::span {buffer}.first(report.size())));
EXPECT_TRUE(std::ranges::all_of(std::span {buffer}.subspan(report.size()), [](auto byte) {
return byte == 0xA5;
}));
}
TEST_F(WindowsDriverProtocolTest, VhfUnnumberedResponseWritesOnlyTheOptionalIdAndReport) {
const auto report = std::array<std::uint8_t, 8> {1, 2, 3, 4, 5, 6, 7, 8};
for (const auto prepend_zero_id : {false, true}) {
SCOPED_TRACE(prepend_zero_id);
const auto prefix_size = prepend_zero_id ? 1U : 0U;
auto buffer = std::array<std::uint8_t, 128> {};
buffer.fill(0xA5);
ASSERT_TRUE(lvh::detail::windows::copy_vhf_report_response(buffer.data(), buffer.size(), report, prepend_zero_id));
if (prepend_zero_id) {
EXPECT_EQ(buffer[0], 0U);
}
EXPECT_TRUE(std::ranges::equal(report, std::span {buffer}.subspan(prefix_size, report.size())));
EXPECT_TRUE(std::ranges::all_of(std::span {buffer}.subspan(prefix_size + report.size()), [](auto byte) {
return byte == 0xA5;
}));
}
}
TEST_F(WindowsDriverProtocolTest, VhfResponseRejectsShortRequestsWithoutWriting) {
const auto report = std::array<std::uint8_t, 8> {1, 2, 3, 4, 5, 6, 7, 8};
auto buffer = std::array<std::uint8_t, 16> {};
buffer.fill(0xA5);
EXPECT_FALSE(lvh::detail::windows::copy_vhf_report_response(buffer.data(), report.size() - 1U, report));
EXPECT_FALSE(lvh::detail::windows::copy_vhf_report_response(buffer.data(), report.size(), report, true));
EXPECT_TRUE(std::ranges::all_of(buffer, [](auto byte) {
return byte == 0xA5;
}));
}
TEST_F(WindowsDriverProtocolTest, VhfResponseAcceptsExactlySizedRequests) {
const auto report = std::array<std::uint8_t, 3> {1, 2, 3};
auto numbered_buffer = std::array<std::uint8_t, 3> {};
ASSERT_TRUE(lvh::detail::windows::copy_vhf_report_response(numbered_buffer.data(), numbered_buffer.size(), report));
EXPECT_EQ(numbered_buffer, report);
auto unnumbered_buffer = std::array<std::uint8_t, 4> {};
unnumbered_buffer.fill(0xA5);
ASSERT_TRUE(lvh::detail::windows::copy_vhf_report_response(unnumbered_buffer.data(), unnumbered_buffer.size(), report, true));
EXPECT_EQ(unnumbered_buffer, (std::array<std::uint8_t, 4> {0, 1, 2, 3}));
}
TEST_F(WindowsDriverProtocolTest, SeriesUsesCapturedXInputHidIdentity) {
auto request = series_request();
request.hardware_ids.product_id = 0x0B12;
request.hardware_ids.device_version = 0x0509;
const auto entries = hardware_id_entries(lvh::detail::windows::make_hardware_ids(request));
ASSERT_EQ(entries.size(), 8U);
EXPECT_TRUE(lvh::detail::windows::uses_xinputhid_match_id(request.gamepad_kind));
EXPECT_EQ(entries[0], L"HID\\VID_045E&PID_0B12&IG_00");
EXPECT_EQ(entries[1], L"HID\\VID_045E&PID_02FF&IG_00");
EXPECT_EQ(entries[2], L"HID\\VID_045E&PID_0B12&REV_0509");
EXPECT_EQ(entries[3], L"HID\\VID_045E&PID_0B12");
EXPECT_EQ(entries[4], L"HID\\VID_045E&UP:0001_U:0005");
EXPECT_EQ(entries[5], L"HID_DEVICE_SYSTEM_GAME");
EXPECT_EQ(entries[6], L"HID_DEVICE_UP:0001_U:0005");
EXPECT_EQ(entries[7], L"HID_DEVICE");
}
TEST_F(WindowsDriverProtocolTest, NonSeriesXboxUsesItsPublicIdentityForMatching) {
auto request = series_request();
request.gamepad_kind = LVH_WINDOWS_GAMEPAD_XBOX_ONE;
request.hardware_ids.product_id = 0x02EA;
request.hardware_ids.device_version = 0x0408;
const auto entries = hardware_id_entries(lvh::detail::windows::make_hardware_ids(request));
ASSERT_EQ(entries.size(), 3U);
EXPECT_TRUE(lvh::detail::windows::uses_xinputhid_match_id(request.gamepad_kind));
EXPECT_EQ(entries[0], L"HID\\VID_045E&PID_02EA&IG_00");
EXPECT_EQ(entries[1], L"HID\\VID_045E&PID_02EA&REV_0408");
EXPECT_EQ(entries[2], L"HID\\VID_045E&PID_02EA");
}
TEST_F(WindowsDriverProtocolTest, SwitchProRepliesToUsbStatusAndHandshakeCommands) {
auto status_report = switch_output_report(0x80, 0x01);
const auto status_reply = lvh::detail::switch_pro_protocol::make_switch_pro_reply(status_report);
ASSERT_TRUE(status_reply.has_value());
EXPECT_EQ(status_reply->at(0), 0x81);
EXPECT_EQ(status_reply->at(1), 0x01);
EXPECT_EQ(status_reply->at(3), 0x03);
EXPECT_TRUE(std::ranges::any_of(status_reply->begin() + 4, status_reply->begin() + 10, [](auto value) {
return value != 0U;
}));
for (const auto command : {0x02U, 0x03U}) {
const auto report = switch_output_report(0x80, static_cast<std::uint8_t>(command));
const auto reply = lvh::detail::switch_pro_protocol::make_switch_pro_reply(report);
ASSERT_TRUE(reply.has_value());
EXPECT_EQ(reply->at(0), 0x81);
EXPECT_EQ(reply->at(1), command);
}
const auto force_usb = switch_output_report(0x80, 0x04);
EXPECT_FALSE(lvh::detail::switch_pro_protocol::make_switch_pro_reply(force_usb).has_value());
}
TEST_F(WindowsDriverProtocolTest, SwitchProAcknowledgesInitializationSubcommands) {
for (const auto subcommand : {0x03U, 0x30U, 0x38U, 0x40U, 0x41U, 0x48U}) {
const auto report = switch_output_report(0x01, static_cast<std::uint8_t>(subcommand));
const auto reply = lvh::detail::switch_pro_protocol::make_switch_pro_reply(report, 0x42U);
ASSERT_TRUE(reply.has_value());
EXPECT_EQ(reply->at(0), 0x21);
EXPECT_EQ(reply->at(1), 0x42);
EXPECT_EQ(reply->at(2), 0x81);
EXPECT_EQ(reply->at(13), 0x80);
EXPECT_EQ(reply->at(14), subcommand);
EXPECT_EQ(reply->at(6), 0x00);
EXPECT_EQ(reply->at(7), 0x08);
EXPECT_EQ(reply->at(8), 0x80);
EXPECT_EQ(reply->at(9), 0x00);
EXPECT_EQ(reply->at(10), 0x08);
EXPECT_EQ(reply->at(11), 0x80);
}
const auto rumble_only = switch_output_report(0x10, 0x00);
EXPECT_FALSE(lvh::detail::switch_pro_protocol::make_switch_pro_reply(rumble_only).has_value());
}
TEST_F(WindowsDriverProtocolTest, SwitchProReturnsDeviceInfoAndFactoryCalibration) {
auto device_info = switch_output_report(0x01, 0x02);
const auto device_reply = lvh::detail::switch_pro_protocol::make_switch_pro_reply(device_info);
ASSERT_TRUE(device_reply.has_value());
EXPECT_EQ(device_reply->at(13), 0x82);
EXPECT_EQ(device_reply->at(14), 0x02);
EXPECT_EQ(device_reply->at(17), 0x03);
auto stick_read = switch_output_report(0x01, 0x10);
write_u32(stick_read, 11U, 0x603D);
stick_read[15] = 18U;
const auto stick_reply = lvh::detail::switch_pro_protocol::make_switch_pro_reply(stick_read);
ASSERT_TRUE(stick_reply.has_value());
EXPECT_EQ(stick_reply->at(13), 0x90);
EXPECT_EQ(stick_reply->at(14), 0x10);
EXPECT_TRUE(std::equal(stick_read.begin() + 11, stick_read.begin() + 16, stick_reply->begin() + 15));
constexpr std::array<std::uint8_t, 18> expected_stick_calibration {
0xFF,
0xF7,
0x7F,
0x00,
0x08,
0x80,
0xFF,
0xF7,
0x7F,
0x00,
0x08,
0x80,
0xFF,
0xF7,
0x7F,
0xFF,
0xF7,
0x7F,
};
EXPECT_TRUE(std::equal(expected_stick_calibration.begin(), expected_stick_calibration.end(), stick_reply->begin() + 20));
auto imu_read = switch_output_report(0x01, 0x10);
write_u32(imu_read, 11U, 0x6020);
imu_read[15] = 24U;
const auto imu_reply = lvh::detail::switch_pro_protocol::make_switch_pro_reply(imu_read);
ASSERT_TRUE(imu_reply.has_value());
EXPECT_TRUE(std::ranges::any_of(imu_reply->begin() + 20, imu_reply->begin() + 44, [](auto value) {
return value != 0U;
}));
auto user_read = switch_output_report(0x01, 0x10);
write_u32(user_read, 11U, 0x8010);
user_read[15] = 22U;
const auto user_reply = lvh::detail::switch_pro_protocol::make_switch_pro_reply(user_read);
ASSERT_TRUE(user_reply.has_value());
EXPECT_TRUE(std::ranges::all_of(user_reply->begin() + 20, user_reply->begin() + 42, [](auto value) {
return value == 0U;
}));
}
TEST_F(WindowsDriverProtocolTest, DualShock4ReturnsCalibrationPairingAndFirmwareFeatures) {
const auto request = playstation_request(LVH_WINDOWS_GAMEPAD_DUALSHOCK4);
const auto calibration = lvh::detail::windows::make_playstation_feature_report(request, 0x02);
ASSERT_TRUE(calibration.has_value());
ASSERT_EQ(calibration->size(), 37U);
EXPECT_EQ(calibration->front(), 0x02);
EXPECT_EQ(calibration->at(7), 0x10);
EXPECT_EQ(calibration->at(8), 0x27);
EXPECT_FALSE(lvh::detail::windows::make_playstation_feature_report(request, 0x05).has_value());
const auto pairing = lvh::detail::windows::make_playstation_feature_report(request, 0x12);
ASSERT_TRUE(pairing.has_value());
ASSERT_EQ(pairing->size(), 16U);
constexpr std::array<std::uint8_t, 6> expected_mac {0x60, 0x50, 0x40, 0x30, 0x20, 0x10};
EXPECT_TRUE(std::equal(expected_mac.begin(), expected_mac.end(), pairing->begin() + 1));
const auto firmware = lvh::detail::windows::make_playstation_feature_report(request, 0xA3);
ASSERT_TRUE(firmware.has_value());
EXPECT_EQ(firmware->size(), 49U);
EXPECT_EQ(firmware->front(), 0xA3);
}
TEST_F(WindowsDriverProtocolTest, DualSenseReturnsCalibrationPairingAndFirmwareFeatures) {
const auto request = playstation_request(LVH_WINDOWS_GAMEPAD_DUALSENSE);
const auto calibration = lvh::detail::windows::make_playstation_feature_report(request, 0x05);
ASSERT_TRUE(calibration.has_value());
EXPECT_EQ(calibration->size(), 41U);
EXPECT_EQ(calibration->front(), 0x05);
const auto pairing = lvh::detail::windows::make_playstation_feature_report(request, 0x09);
ASSERT_TRUE(pairing.has_value());
ASSERT_EQ(pairing->size(), 20U);
constexpr std::array<std::uint8_t, 6> expected_mac {0x60, 0x50, 0x40, 0x30, 0x20, 0x10};
EXPECT_TRUE(std::equal(expected_mac.begin(), expected_mac.end(), pairing->begin() + 1));
const auto firmware = lvh::detail::windows::make_playstation_feature_report(request, 0x20);
ASSERT_TRUE(firmware.has_value());
EXPECT_EQ(firmware->size(), 64U);
EXPECT_EQ(firmware->front(), 0x20);
EXPECT_EQ(firmware->at(22), 0x04);
EXPECT_EQ(firmware->at(23), 0x00);
EXPECT_EQ(firmware->at(44), 0x30);
EXPECT_EQ(firmware->at(45), 0x06);
EXPECT_FALSE(lvh::detail::windows::make_playstation_feature_report(request, 0xFF).has_value());
}
TEST_F(WindowsDriverProtocolTest, BluetoothPlayStationFeaturesCarryCrcAndGeneratedPairingMac) {
auto request = playstation_request(LVH_WINDOWS_GAMEPAD_DUALSENSE, LVH_WINDOWS_BUS_BLUETOOTH, "not-a-mac");
const auto pairing = lvh::detail::windows::make_playstation_feature_report(request, 0x09);
ASSERT_TRUE(pairing.has_value());
ASSERT_EQ(pairing->size(), 20U);
constexpr std::array<std::uint8_t, 6> expected_generated_mac {0x78, 0x56, 0x34, 0x12, 0x00, 0x02};
EXPECT_TRUE(std::equal(expected_generated_mac.begin(), expected_generated_mac.end(), pairing->begin() + 1));
EXPECT_TRUE(std::ranges::any_of(pairing->end() - 4, pairing->end(), [](auto value) {
return value != 0U;
}));
request.gamepad_kind = LVH_WINDOWS_GAMEPAD_DUALSHOCK4;
const auto bluetooth_calibration = lvh::detail::windows::make_playstation_feature_report(request, 0x05);
ASSERT_TRUE(bluetooth_calibration.has_value());
EXPECT_EQ(bluetooth_calibration->size(), 41U);
EXPECT_TRUE(std::ranges::any_of(bluetooth_calibration->end() - 4, bluetooth_calibration->end(), [](auto value) {
return value != 0U;
}));
}