/*
 * vhid_shared.h
 *
 * Layout of the shared memory section used as the single IPC surface
 * between user-mode callers and the vhidkm.sys HID minidriver.
 *
 * The section is a named kernel section object created by vhidkm.sys
 * during device start (see hid/shared_mem.c) and mapped into any
 * user-mode process that has the privilege to open its name. From
 * kernel side it is also mapped into system space so the DISPATCH_
 * LEVEL injection timer (hid/inject_timer.c) can read the current
 * snapshot without any transition out of the dispatcher.
 *
 * There is no IOCTL surface. No CreateFile / DeviceIoControl. No
 * control device. A process with sufficient rights:
 *
 *     1. OpenFileMappingW(FILE_MAP_READ | FILE_MAP_WRITE, FALSE,
 *                         VHID_SHARED_NAME_W);
 *     2. MapViewOfFile(..);
 *     3. Verifies Magic == VHID_SHARED_MAGIC, Version == VHID_API_LEVEL,
 *        HeaderBytes >= sizeof(VHID_SHARED_STATE);
 *     4. Writes mouse / keyboard fields and InterlockedIncrement(&Seq).
 *
 * Concurrency model:
 *
 *   The two submit paths (mouse, keyboard) and the LED publish path
 *   use a classic seqlock rather than any kind of shared lock. User
 *   process is producer of mouse/keyboard data and consumer of LED
 *   data; kernel is consumer of the former and producer of the
 *   latter. Each lane has a single 32-bit Seq counter that encodes
 *   both the version and a "write in progress" bit in its parity:
 *
 *       even Seq  -> fields form a committed snapshot
 *       odd  Seq  -> producer is mid-write; fields may be torn
 *
 *   User publishes a mouse event (two-phase commit):
 *       InterlockedIncrement(&Mouse.Seq);          // even -> odd
 *       writes Buttons/AbsMode/Wheel/Dx/Dy/AbsX/AbsY fields
 *       _WriteBarrier(); MemoryBarrier();          (release)
 *       InterlockedIncrement(&Mouse.Seq);          // odd  -> even
 *
 *   Kernel timer consumes (retry on tear):
 *       seq1 = InterlockedOr(&Mouse.Seq, 0);       (acquire)
 *       if (seq1 is odd)           -> bail; writer busy
 *       if (seq1 == LastMouseSeq)  -> nothing new
 *       snapshot fields, _ReadBarrier();
 *       seq2 = InterlockedOr(&Mouse.Seq, 0);       (acquire)
 *       if (seq2 != seq1)          -> retry; producer raced us
 *       post HID report;
 *       InterlockedExchange(&Mouse.ConsumedSeq, seq1);
 *       LastMouseSeq = seq1;
 *
 *   The odd-during-write invariant is what turns this into a real
 *   seqlock: without it, the previous "plain counter" scheme could
 *   hand the kernel a torn snapshot when the producer's field stores
 *   interleaved with the reader's loads on a different core.
 *
 *   Each lane has a single producer. Multi-threaded callers that
 *   want to race publishes on the same lane must serialize on the
 *   SDK's ctx themselves; the seqlock does not arbitrate between
 *   concurrent writers (two overlapping producers would break the
 *   odd/even invariant).
 *
 *   Seq lives in user-writable memory so it must be treated as
 *   untrusted — the kernel only uses it to detect *change*, not as
 *   any kind of offset or length. Malicious or buggy user code that
 *   churns Seq only causes the driver to emit extra zero-delta
 *   reports, or to briefly see odd values and skip a tick, both of
 *   which are harmless.
 *
 *   Every transient input field (Dx, Dy, Wheel, HWheel, Keys[])
 *   represents a one-shot value at the time of Seq increment. The
 *   driver does not accumulate, does not replay, does not mutate
 *   user-writable fields. User code is responsible for clearing
 *   deltas between events when that's the desired semantic.
 *
 *   ConsumedSeq mirrors Seq after the kernel has posted the matching
 *   HID report to hidclass.sys. Callers that want to block until the
 *   event has been delivered can spin-wait on ConsumedSeq == Seq.
 *   For fire-and-forget callers (the common case) ConsumedSeq can be
 *   ignored.
 *
 * ABI:
 *
 *   All fields are fixed-width types (UINT8 / INT8 / INT32 / LONG)
 *   so 32-bit and 64-bit user-mode clients share the same layout.
 *   HeaderBytes records the driver-side sizeof so a newer driver can
 *   append fields without breaking older consumers; callers must
 *   treat any trailing bytes as Reserved. Version bump is mandatory
 *   when the semantic contract (not just the size) changes.
 */

#pragma once
#ifndef VHID_SHARED_H_
#define VHID_SHARED_H_

#if defined(_KERNEL_MODE) || defined(_NTDDK_)
#include <ntddk.h>
#else
#include <windows.h>
#endif

#include "vhid_version.h"
#include "vhid_reports.h"

/*
 * Kernel-mode absolute path used by ZwCreateSection. The "\Global\"
 * prefix under \BaseNamedObjects places the object in the global
 * namespace so session-0 (SYSTEM) and logged-in user sessions map
 * the same underlying pages.
 */
#define VHID_SHARED_KERNEL_NAME_W   L"\\BaseNamedObjects\\Global\\VhidkmShared"

/*
 * User-mode name passed to OpenFileMappingW. "Global\" is required
 * when the caller is running in a non-session-0 session; if the
 * caller is SYSTEM the "Global\" prefix is still accepted and resolves
 * to the same object as the plain name. Callers can strip the
 * "Global\" prefix for local-session-only semantics but the driver
 * only publishes the global name.
 */
#define VHID_SHARED_NAME_W          L"Global\\VhidkmShared"

/*
 * 'VHID' little-endian: 0x44=='D', 0x49=='I', 0x48=='H', 0x56=='V'.
 * Used to detect a completely wrong section being mapped by accident.
 */
#define VHID_SHARED_MAGIC           0x44494856u

/*
 * Section size. A single 4 KiB page is more than enough for the
 * current fields and leaves room for growth without re-keying the
 * ABI. The page is backed by pagefile memory (SEC_COMMIT) — never
 * referenced at DISPATCH_LEVEL in the kernel mapping, which is in
 * system PTEs and always resident.
 */
#define VHID_SHARED_SECTION_BYTES   4096u

/*
 * Timer cadence advertised in the header. Provided for tooling and
 * for future drivers that might expose a configurable rate; v1 is
 * hard-wired to 1 ms (1000 Hz) to mimic a high-end gaming mouse.
 */
#define VHID_SHARED_HEARTBEAT_MS    1u

/*
 * AbsMode enumeration. v1 supports exactly two mouse motion modes:
 * relative (pure Dx/Dy deltas) and absolute-logical (AbsX/AbsY in
 * 0..VHID_MOUSE_ABS_MAX). Pixel-coordinate absolute mode is handled
 * entirely user-side by the SDK which converts to logical coords
 * before submission.
 */
#define VHID_ABSMODE_RELATIVE       0u
#define VHID_ABSMODE_ABSOLUTE       1u

#include <pshpack4.h>

/*
 * Mouse submission slot. User writes the transient fields and bumps
 * Seq; the kernel observes Seq change, snapshots the fields, and
 * emits exactly one HID report per increment. On relative mice a
 * zero-delta report is never emitted because the user controls the
 * cadence.
 */
typedef struct _VHID_SHARED_MOUSE {
    volatile LONG Seq;          /* user W, kernel R: event counter  */
    volatile LONG ConsumedSeq;  /* kernel W, user R: last posted seq */
    UINT8         Buttons;      /* VHID_MOUSE_BTN_* bitmap          */
    UINT8         AbsMode;      /* VHID_ABSMODE_*                   */
    INT8          Wheel;        /* signed 8-bit vertical wheel      */
    INT8          HWheel;       /* signed 8-bit AC Pan wheel        */
    INT32         Dx;           /* relative X delta                 */
    INT32         Dy;           /* relative Y delta                 */
    INT32         AbsX;         /* 0..VHID_MOUSE_ABS_MAX logical    */
    INT32         AbsY;
    UINT32        Reserved[3];  /* must be zero                     */
} VHID_SHARED_MOUSE, *PVHID_SHARED_MOUSE;

/*
 * Keyboard submission slot. Same protocol as mouse: user populates
 * Modifiers + Keys[] and bumps Seq. To release a key the user writes
 * a new Keys[] array without that key and bumps Seq again — there
 * is no explicit "key up" event, only state snapshots, matching the
 * boot-protocol semantics hidclass.sys expects.
 */
typedef struct _VHID_SHARED_KEYBOARD {
    volatile LONG Seq;
    volatile LONG ConsumedSeq;
    UINT8         Modifiers;
    UINT8         Reserved0;
    UINT8         Keys[VHID_KBD_MAX_KEYS];
    UINT32        Reserved1[3];
} VHID_SHARED_KEYBOARD, *PVHID_SHARED_KEYBOARD;

/*
 * The root header. First four fields are fixed and must never be
 * reordered — older user-mode SDKs will inspect them through the
 * same offsets regardless of any later additions further in the
 * struct. HeaderBytes records the driver-side sizeof so the SDK can
 * compare against its own and cap reads at the smaller value.
 */
typedef struct _VHID_SHARED_STATE {
    UINT32                Magic;        /* VHID_SHARED_MAGIC              */
    UINT32                Version;      /* VHID_API_LEVEL                 */
    UINT32                HeaderBytes;  /* sizeof(VHID_SHARED_STATE)      */
    UINT32                HeartbeatMs;  /* 1 in v1                        */

    VHID_SHARED_MOUSE     Mouse;
    VHID_SHARED_KEYBOARD  Keyboard;

    volatile LONG         LedSeq;       /* kernel W, user R: LED counter  */
    UINT8                 LedBits;      /* kernel W: VHID_KBD_LED_* bits  */
    UINT8                 Reserved2[3];
    UINT32                Reserved3[3];
} VHID_SHARED_STATE, *PVHID_SHARED_STATE;

#include <poppack.h>

/*
 * Layout invariants. The offsets are baked into the user-mode SDK,
 * Python ctypes mirror, and kernel mapping; if any of these asserts
 * trip, the ABI has drifted and API level must be bumped.
 */
#if defined(C_ASSERT)
C_ASSERT(sizeof(VHID_SHARED_MOUSE)    == 40);
C_ASSERT(sizeof(VHID_SHARED_KEYBOARD) == 28);
C_ASSERT(sizeof(VHID_SHARED_STATE)    == 104);
C_ASSERT(FIELD_OFFSET(VHID_SHARED_STATE, Mouse)    == 16);
C_ASSERT(FIELD_OFFSET(VHID_SHARED_STATE, Keyboard) == 56);
C_ASSERT(FIELD_OFFSET(VHID_SHARED_STATE, LedSeq)   == 84);
C_ASSERT(sizeof(VHID_SHARED_STATE) <= VHID_SHARED_SECTION_BYTES);
#endif

#endif /* VHID_SHARED_H_ */
