Files
portersky dfe7e8b87d refactor: prefix private data members with m_
Rename all trailing-underscore private data members to the m_ prefix
required by AGENTS.md, across the auth, crypto, gip, mt76, and usb
transport classes plus the app session classes. Pure rename; no
behavior change. Full build and test suite pass.

Co-Authored-By: qwen3.8-27b@q3_k_xl: renamed members to m_ prefix
2026-08-29 14:36:28 +02:00

301 lines
10 KiB
C++

#pragma once
// ==============================================================================
// GIP protocol (Game Input Protocol)
// ==============================================================================
// Port target: medusalix/xone bus/protocol.c + bus/bus.c.
//
// The kernel driver's device model (struct device, sysfs, driver
// registration) is gone. The adapter owns up to 16 clients; the transport
// interface abstracts the MT76 data path (Phase 3), and client_listener
// delivers input/battery/audio events to the HID layer (Phase 4). The
// adapter is driven single-threaded from the USB read callback.
// ==============================================================================
#include <array>
#include <cstdint>
#include <memory>
#include <span>
#include <string>
#include <vector>
#include "auth/auth.hpp"
#include "common/types.hpp"
namespace xone::gip {
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
inline constexpr u16 k_vid_microsoft = 0x045e;
inline constexpr int k_audio_interval = 8; // ms between audio packets
inline constexpr u32 k_pkt_max_length = 58;
inline constexpr u32 k_chunk_buf_max_length = 0xffff;
inline constexpr int k_max_clients = 16;
enum : u8 {
GIP_CMD_ACKNOWLEDGE = 0x01,
GIP_CMD_ANNOUNCE = 0x02,
GIP_CMD_STATUS = 0x03,
GIP_CMD_IDENTIFY = 0x04,
GIP_CMD_POWER = 0x05,
GIP_CMD_AUTHENTICATE = 0x06,
GIP_CMD_VIRTUAL_KEY = 0x07,
GIP_CMD_AUDIO_CONTROL = 0x08,
GIP_CMD_RUMBLE = 0x09,
GIP_CMD_LED = 0x0a,
GIP_CMD_HID_REPORT = 0x0b,
GIP_CMD_FIRMWARE = 0x0c,
GIP_CMD_SERIAL_NUMBER = 0x1e,
GIP_CMD_INPUT = 0x20,
GIP_CMD_AUDIO_SAMPLES = 0x60,
};
enum : u8 {
GIP_OPT_ACKNOWLEDGE = 1 << 4,
GIP_OPT_INTERNAL = 1 << 5,
GIP_OPT_CHUNK_START = 1 << 6,
GIP_OPT_CHUNK = 1 << 7,
};
enum : u8 {
GIP_BATT_TYPE_NONE = 0x00,
GIP_BATT_TYPE_STANDARD = 0x01,
GIP_BATT_TYPE_KIT = 0x02,
};
enum : u8 {
GIP_BATT_LEVEL_LOW = 0x00,
GIP_BATT_LEVEL_NORMAL = 0x01,
GIP_BATT_LEVEL_HIGH = 0x02,
GIP_BATT_LEVEL_FULL = 0x03,
};
enum : u8 {
GIP_PWR_ON = 0x00,
GIP_PWR_SLEEP = 0x01,
GIP_PWR_OFF = 0x04,
GIP_PWR_RESET = 0x07,
};
enum : u8 {
GIP_LED_OFF = 0x00,
GIP_LED_ON = 0x01,
GIP_LED_BLINK_FAST = 0x02,
GIP_LED_BLINK_NORMAL = 0x03,
GIP_LED_BLINK_SLOW = 0x04,
GIP_LED_FADE_SLOW = 0x08,
GIP_LED_FADE_FAST = 0x09,
};
enum : u8 {
GIP_AUD_FORMAT_16KHZ_MONO = 0x05,
GIP_AUD_FORMAT_24KHZ_MONO = 0x09,
GIP_AUD_FORMAT_48KHZ_STEREO = 0x10,
};
enum : u8 {
GIP_AUD_FORMAT_CHAT_24KHZ = 0x04,
GIP_AUD_FORMAT_CHAT_16KHZ = 0x05,
};
// Decoded wire header (variable length, not packed).
struct gip_header {
u8 command = 0;
u8 options = 0;
u8 sequence = 0;
u32 packet_length = 0;
u32 chunk_offset = 0;
};
struct gip_hardware {
u16 vendor = 0;
u16 product = 0;
u16 version = 0;
};
struct gip_audio_config {
u8 format = 0;
int channels = 0;
int sample_rate = 0;
int buffer_size = 0;
int fragment_size = 0;
int packet_size = 0;
};
// Low-level data path to the MT76 chip (implemented by Phase 3).
class transport {
public:
virtual ~transport() = default;
// Send one framed GIP data buffer to the chip.
virtual auto send_frame(std::span<u8 const> frame) -> int = 0;
// Install the AES-CCMP session key for a client (no-op by default).
virtual auto set_encryption_key(u8 client_id, std::span<u8 const> key) -> int
{
(void)client_id;
(void)key;
return 0;
}
};
// Events delivered to the HID layer (Phase 4).
class client_listener {
public:
virtual ~client_listener() = default;
virtual auto on_client_added(class client&) -> void {}
virtual auto on_client_removed(u8) -> void {}
virtual auto on_battery(class client&, u8, u8) -> void {}
virtual auto on_guide_button(class client&, bool) -> void {}
virtual auto on_input(class client&, std::span<u8 const>) -> void {}
virtual auto on_hid_report(class client&, std::span<u8 const>) -> void {}
virtual auto on_audio_ready(class client&) -> void {}
virtual auto on_audio_volume(class client&, u8, u8) -> void {}
virtual auto on_audio_samples(class client&, std::span<u8 const>) -> void {}
};
class adapter;
// One connected controller. Also implements the auth_sink so the auth
// handshake can send AUTHENTICATE packets back through the GIP layer.
class client : public xone::auth::auth_sink {
public:
client(adapter& adapter, u8 id);
auto id() const -> u8 { return m_id; }
auto hardware() const -> gip_hardware const& { return m_hardware; }
auto audio_config_in() const -> gip_audio_config const& { return m_audio_config_in; }
auto audio_config_out() const -> gip_audio_config const& { return m_audio_config_out; }
auto classes() const -> std::vector<std::string> const& { return m_classes; }
auto has_interface(xone::guid_t const& guid) const -> bool;
auto set_power_mode(u8 mode) -> int;
auto send_rumble(std::span<u8 const> pkt) -> int;
auto set_led_mode(u8 mode, u8 brightness) -> int;
auto suggest_audio_format(u8 in, u8 out, bool chat) -> int;
auto set_audio_volume(u8 in, u8 chat, u8 out) -> int;
auto send_audio_samples(std::span<u8 const> samples) -> int;
auto enable_audio() -> int;
auto init_audio_in() -> int;
auto init_audio_out() -> int;
auto disable_audio() -> void;
// auth_sink implementation.
auto send(std::span<u8 const> pkt, bool acknowledge) -> int override;
auto set_encryption_key(std::span<u8 const> key) -> int override;
auto start_auth() -> int { return m_auth.start(); }
auto process_auth(std::span<u8 const> data) -> int
{
return m_auth.process_pkt(data);
}
private:
friend class adapter;
adapter& m_adapter;
u8 m_id;
gip_hardware m_hardware{};
struct info_element {
u8 count = 0;
std::vector<u8> data;
};
std::unique_ptr<info_element> m_client_commands;
std::unique_ptr<info_element> m_firmware_versions;
std::unique_ptr<info_element> m_audio_formats;
std::unique_ptr<info_element> m_capabilities_out;
std::unique_ptr<info_element> m_capabilities_in;
std::vector<std::string> m_classes;
std::unique_ptr<info_element> m_interfaces;
std::unique_ptr<info_element> m_hid_descriptor;
gip_audio_config m_audio_config_in;
gip_audio_config m_audio_config_out;
// Chunk reassembly buffers (large packets are split into chunks).
struct chunk_buffer {
gip_header header;
u32 length = 0;
std::vector<u8> data;
};
std::unique_ptr<chunk_buffer> m_chunk_buf_out;
std::unique_ptr<chunk_buffer> m_chunk_buf_in;
xone::auth::auth m_auth{*this};
};
class adapter {
public:
adapter(transport& transport, client_listener& listener,
int audio_packet_count = 8);
// Dispatch one USB data buffer (may contain multiple GIP packets).
auto process_buffer(std::span<u8 const> data) -> int;
auto get_client(u8 id) -> client*;
auto client_count() const -> int;
private:
friend class client;
auto send_pkt(client& c, gip_header& hdr, void const* data) -> int;
auto send_pkt_simple(gip_header& hdr, void const* data) -> int;
auto init_chunk_buffer(gip_header const& hdr,
std::unique_ptr<client::chunk_buffer>& buf) -> int;
auto send_remaining_chunks(client& c) -> int;
auto request_identification(client& c) -> int;
auto acknowledge_pkt(client& c, gip_header const& ack) -> int;
auto remove_client(client& c) -> void;
auto free_client_info(client& c) -> void;
auto add_client(client& c) -> void;
auto make_audio_config(gip_audio_config& cfg) -> int;
auto set_audio_format(client& c, u8 in, u8 out) -> int;
auto set_audio_format_chat(client& c, u8 in_out) -> int;
auto dispatch_pkt(client& c, gip_header const& hdr, void const* data, u32 len) -> int;
auto process_pkt(client& c, gip_header& hdr, void const* data) -> int;
auto process_pkt_chunked(client& c, gip_header const& hdr, void const* data) -> int;
auto handle_pkt_acknowledge(client& c, void const* data, u32 len) -> int;
auto handle_pkt_announce(client& c, void const* data, u32 len) -> int;
auto handle_pkt_status(client& c, void const* data, u32 len) -> int;
auto handle_pkt_identify(client& c, void const* data, u32 len) -> int;
auto handle_pkt_authenticate(client& c, void const* data, u32 len) -> int;
auto handle_pkt_virtual_key(client& c, void const* data, u32 len) -> int;
auto handle_pkt_audio_control(client& c, void const* data, u32 len) -> int;
auto handle_pkt_hid_report(client& c, void const* data, u32 len) -> int;
auto handle_pkt_input(client& c, void const* data, u32 len) -> int;
auto handle_pkt_audio_samples(client& c, void const* data, u32 len) -> int;
auto parse_info_element(std::span<u8 const> data, u16 offset, int item_length,
std::unique_ptr<client::info_element>& out) -> int;
auto parse_client_commands(client& c, u16 const offsets[8],
std::span<u8 const> data) -> int;
auto parse_firmware_versions(client& c, u16 const offsets[8],
std::span<u8 const> data) -> int;
auto parse_audio_formats(client& c, u16 const offsets[8],
std::span<u8 const> data) -> int;
auto parse_capabilities(client& c, u16 const offsets[8],
std::span<u8 const> data) -> int;
auto parse_classes(client& c, std::span<u8 const> data, u16 offset) -> int;
auto parse_interfaces(client& c, u16 const offsets[8],
std::span<u8 const> data) -> int;
auto parse_hid_descriptor(client& c, u16 const offsets[8],
std::span<u8 const> data) -> int;
transport& m_transport;
client_listener& m_listener;
int m_audio_packet_count;
std::array<std::unique_ptr<client>, k_max_clients> m_clients;
u8 m_data_sequence = 0;
u8 m_audio_sequence = 0;
};
} // namespace xone::gip