//! The power protocol (docs/os-development/power.md): system power's
//! domain-named surface, bound at `/protocol/power`. On x86 the acpi service
//! provides it; on ARM a PSCI/mailbox service will bind the same name —
//! subscribers never learn which firmware they are on (docs/discovery.md —
//! firmware neutrality), which is the whole point of naming the contract rather
//! than the provider (docs/os-development/protocol-namespace.md).
//!
//! Defined through the envelope, so every packet begins with the folded
//! `Header`. Three shapes ride the channel, and the envelope names all three:
//!
//! - **subscribe** is the *reserved* verb, not one of this protocol's own: a
//!   synchronous call whose attached capability is the subscriber's endpoint is
//!   exactly what `envelope.operation_subscribe` means everywhere.
//! - **shutdown** is this protocol's one verb — the only operation that *does*
//!   something irreversible, and the reason the provider gates it by badge.
//! - **the events** are pushes: the service `ipc_send`s each one to every
//!   subscriber, no reply owed, so a slow or dead subscriber can never wedge the
//!   source. **The kind is the packet's operation** — one declared event per
//!   named kind, exactly as the input protocol delivers one per device class —
//!   so a subscriber reads *what happened* out of the header instead of a tag
//!   inside the payload. That is what the old `EventMessage`'s two leading bytes
//!   (an operation byte saying "this is an event", then the kind) fold into.
//!
//! `Header.target` is unused (0) in both directions: the provider is the only
//! object either side addresses. And no packet carries a version any more — the
//! reserved `describe` verb is the version handshake, asked once at connect time
//! rather than re-carried out of every packet's budget.

const std = @import("std");
const envelope = @import("envelope");

/// What a published event carries beyond its kind. The kind is the packet's
/// operation, so nothing here repeats it; `power_button`, `lid`, `ac` and
/// `battery` leave both fields zero and are fully described by the verb alone.
pub const Notice = extern struct {
    /// The device notification code (ACPI `Notify`'s second argument), or 0.
    code: u32 = 0,
    /// The notifying device's hardware id (EISA-decoded), or all zero.
    hid: [8]u8 = .{0} ** 8,
};

pub const Protocol = envelope.Define(.{
    .name = "power",
    .version = 1,
    .operations = &.{
        // Orderly shutdown's last step: enter S5. Honored only from a
        // subscriber — init, the process that has already run the stop sequence
        // over everything else (docs/os-development/power.md, "authority, not
        // information"). Nothing to say and nothing to answer, so the verb and
        // the reply's `Status` are the whole exchange.
        .{ .name = "shutdown" },
    },
    .events = &.{
        // The vocabulary is hardware-neutral: a lid is a lid whether ACPI or a
        // PSCI mailbox reported it. One event per kind, each carrying the same
        // `Notice`, because what differs between them is which thing happened —
        // and that is the header's job now.
        .{ .name = "power_button", .payload = Notice },
        .{ .name = "lid", .payload = Notice },
        .{ .name = "ac", .payload = Notice },
        .{ .name = "battery", .payload = Notice },
        // A device notification that maps to none of the named events — the
        // `code` and `hid` say which device and what happened.
        .{ .name = "notify", .payload = Notice },
    },
});

pub const Operation = Protocol.Operation;

/// What happened. The event *is* the kind: this is the generated event
/// enumeration, re-exported under the name this protocol has always called its
/// vocabulary, with the same members it has always had.
pub const Event = Protocol.Event;

/// What a provider and a subscriber size their buffers to. This module used to
/// declare 64 — the *push* floor — which was simply wrong for a protocol whose
/// requests ride `ipc_call`: a provider sizing its receive buffer to 64 refuses
/// any caller that sends up to the floor it is entitled to.
pub const message_maximum: usize = Protocol.message_maximum;

test "the kind is the verb, and an event fits the push floor" {
    try std.testing.expectEqual(@as(u32, 16), @intFromEnum(Operation.shutdown));
    try std.testing.expectEqual(@as(u32, 16), @intFromEnum(Event.power_button));
    try std.testing.expectEqual(@as(u32, 17), @intFromEnum(Event.lid));
    try std.testing.expectEqual(@as(u32, 18), @intFromEnum(Event.ac));
    try std.testing.expectEqual(@as(u32, 19), @intFromEnum(Event.battery));
    try std.testing.expectEqual(@as(u32, 20), @intFromEnum(Event.notify));
    // subscribe is the RESERVED verb, below the protocol range entirely.
    try std.testing.expectEqual(@as(u32, 2), envelope.operation_subscribe);
    try std.testing.expectEqual(envelope.prefix_size + @sizeOf(Notice), Protocol.event_maximum);
    try std.testing.expect(Protocol.event_maximum <= envelope.post_maximum);
    // The call floor, not the push floor: `shutdown` is a synchronous call.
    try std.testing.expectEqual(envelope.packet_maximum, message_maximum);
}

test "a pushed event names its kind in the header" {
    var buffer: [envelope.post_maximum]u8 = undefined;
    const packet = Protocol.encodeEvent(.power_button, 0, .{}, &buffer).?;
    try std.testing.expectEqual(Event.power_button, Protocol.eventOf(packet).?);

    const notified = Protocol.encodeEvent(.notify, 0, .{ .code = 0x80, .hid = "PNP0C0A\x00".* }, &buffer).?;
    try std.testing.expectEqual(Event.notify, Protocol.eventOf(notified).?);
    try std.testing.expectEqual(@as(u32, 0x80), Protocol.decodeEvent(.notify, notified).?.code);
}
