//! The device-manager protocol (docs/device-manager.md): what drivers and
//! applications say to the device manager over its well-known endpoint. The
//! vfs-protocol pattern — extern-struct messages, a version in the handshake,
//! reserved fields — so both sides depend on the contract by name. Deliberately
//! contains nothing lifecycle-shaped: stopping, liveness (the zero-length ping),
//! and exit reasons are the universal vocabulary of
//! docs/process-lifecycle.md, not this protocol.

/// The protocol version a driver states in its hello. A manager that cannot
/// serve a driver's version refuses the hello, and the mismatch is loud at
/// startup instead of quiet corruption later.
pub const version: u16 = 1;

/// What kind of driver is talking (docs/driver-model.md's shapes).
pub const Role = enum(u8) {
    /// Owns a controller and reports the devices behind it (`child_added`).
    bus = 1,
    /// Serves one device, reached through a bus's transfer protocol.
    device = 2,
};

/// The message kinds.
pub const Operation = enum(u8) {
    hello = 1,
    child_added = 2,
    child_removed = 3,
    enumerate = 4,
    subscribe = 5,
};

/// `Hello.device_id` for a driver that serves no enumerated device (a test
/// fixture, a synthetic source).
pub const no_device: u64 = ~@as(u64, 0);

/// The handshake, sent once by every driver the manager spawns — the manager's
/// one self-enforced deadline: spawned and silent past it means wrong binary,
/// wrong version, or wedged before main, and the stop sequence follows.
pub const Hello = extern struct {
    operation: u8 = @intFromEnum(Operation.hello),
    /// A Role value.
    role: u8,
    /// The protocol version this driver was built against (`version`).
    version: u16 = version,
    reserved: u32 = 0,
    /// The device this driver was assigned (its argv[1]), or `no_device`.
    device_id: u64,
};

pub const hello_size = @sizeOf(Hello);

/// The manager's answer to a hello. Nonzero status = refused (version mismatch,
/// unknown sender); a refused driver should exit cleanly.
pub const HelloReply = extern struct {
    status: i32,
    reserved: u32 = 0,
};

pub const reply_size = @sizeOf(HelloReply);

/// A bus driver reporting one device it discovered behind its controller
/// (docs/device-manager.md "the tree"). Identity is the bus's native language —
/// for USB a port-speed class; the (class, subclass, protocol) triple joins it
/// once control transfers exist (the USB track). The manager mirrors the child
/// into its tree; when the reporting driver dies, the manager prunes everything
/// it reported (the children describe protocol state that died with it) and the
/// restarted instance rediscovers and re-reports.
pub const ChildAdded = extern struct {
    operation: u8 = @intFromEnum(Operation.child_added),
    reserved0: u8 = 0,
    reserved1: u16 = 0,
    reserved2: u32 = 0,
    /// The reporting driver's own device (the controller) — the child's parent.
    parent: u64,
    /// Where on the bus (for USB: the root port number, 1-based).
    bus_address: u64,
    /// Bus-specific identity (for USB: the PORTSC port-speed class; for PCI:
    /// the class triple; for ACPI devices, 0 — identity is the hid below).
    identity: u64,
    /// The kernel device id this child was `device_register`ed as — what the
    /// manager hands a matched driver as its argv assignment — or `no_device`
    /// for an unregistered leaf (a USB port before the descriptor track).
    device_id: u64 = no_device,
    /// The ACPI hardware id (`_HID`), EISA-decoded (e.g. "PNP0303"), for devices
    /// discovered by firmware string rather than a numeric bus identity. Empty
    /// (all zero) otherwise. Widens for FDT `compatible` strings later.
    hid: [8]u8 = .{0} ** 8,
};

pub const child_added_size = @sizeOf(ChildAdded);

/// A bus driver reporting a device gone (hot-unplug). Not yet sent by any
/// driver — the port scan has no unplug interrupt — but the manager handles it;
/// death-pruning covers removal until hotplug lands.
pub const ChildRemoved = extern struct {
    operation: u8 = @intFromEnum(Operation.child_removed),
    reserved0: u8 = 0,
    reserved1: u16 = 0,
    reserved2: u32 = 0,
    parent: u64,
    bus_address: u64,
};

pub const child_removed_size = @sizeOf(ChildRemoved);

/// The manager's answer to a tree report.
pub const ReportReply = extern struct {
    status: i32,
    reserved: u32 = 0,
};

/// An application asking for the tree (M18.3): the reply is an EnumerateReply
/// header followed by `count` ChildEntry records.
pub const Enumerate = extern struct {
    operation: u8 = @intFromEnum(Operation.enumerate),
    reserved0: u8 = 0,
    reserved1: u16 = 0,
    reserved2: u32 = 0,
};

pub const EnumerateReply = extern struct {
    status: i32,
    /// ChildEntry records following this header.
    count: u32,
};

pub const ChildEntry = extern struct {
    parent: u64,
    bus_address: u64,
    identity: u64,
};

/// An application subscribing to published add/remove events (the input-service
/// pattern): the subscriber's endpoint rides as the call's **capability**, and
/// events arrive on it as buffered messages whose payload is the same
/// ChildAdded / ChildRemoved struct the bus drivers send — one encoding, both
/// directions.
pub const Subscribe = extern struct {
    operation: u8 = @intFromEnum(Operation.subscribe),
    reserved0: u8 = 0,
    reserved1: u16 = 0,
    reserved2: u32 = 0,
};

/// Upper bound on any message in this protocol — sizes the endpoint buffers.
/// Capped by the kernel's IPC MESSAGE_MAXIMUM (256): an EnumerateReply carries
/// up to ten ChildEntry records per call, plenty for the mirror's current
/// bounds; paging joins the protocol if a tree ever outgrows one message.
pub const message_maximum = 256;
