arcbox-constants 0.4.17

Shared protocol and runtime constants for ArcBox
/// Host↔agent RPC protocol version this build speaks.
///
/// Carried in `AgentPingResponse.protocol_version` and checked by the
/// host during the boot handshake. Bump when a change alters the
/// *meaning* of existing messages (new required semantics, repurposed
/// fields, behavioral contracts) — purely additive, ignorable changes
/// don't need a bump. Unknown `MessageType`s already fail cleanly; this
/// version catches the silent proto3 field-skew class instead.
pub const AGENT_PROTOCOL_VERSION: u32 = 1;

/// Oldest agent protocol version this host still accepts.
///
/// Agents reporting less (including `0` — agents that predate the
/// handshake field) are rejected at boot with an actionable error
/// instead of silently misbehaving under field skew.
pub const MIN_AGENT_PROTOCOL_VERSION: u32 = 1;

/// Number of bytes in the fixed RPC frame header (`length` + `type`).
pub const FRAME_HEADER_SIZE: usize = 8;

/// Number of bytes in the fixed RPC error header (`code` + `message_len`).
pub const ERROR_HEADER_SIZE: usize = 8;

/// Number of bytes in the message type field.
pub const TYPE_FIELD_SIZE: usize = 4;

/// Number of bytes in the trace length field.
pub const TRACE_LEN_FIELD_SIZE: usize = 2;

/// RPC message types used by host and guest agent.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum MessageType {
    // Request types (0x0000 - 0x0FFF).
    PingRequest = 0x0001,
    GetSystemInfoRequest = 0x0002,
    EnsureRuntimeRequest = 0x0003,
    RuntimeStatusRequest = 0x0004,
    KubernetesStartRequest = 0x0005,
    KubernetesStopRequest = 0x0006,
    KubernetesDeleteRequest = 0x0007,
    KubernetesStatusRequest = 0x0008,
    KubernetesKubeconfigRequest = 0x0009,
    ShutdownRequest = 0x000A,
    /// Test-only: force the guest to mmap + read a file so the VirtioFS
    /// DAX path issues `FUSE_SETUPMAPPING`. Used by the ABX-362 E2E
    /// harness; not wired to any production CLI path.
    MmapReadFileRequest = 0x000B,
    /// Request the guest to run `fstrim` on data mount points so the host
    /// sparse image reclaims freed blocks.
    DiskTrimRequest = 0x000C,
    /// Opens a guest-driven readiness event stream.
    WatchReadinessRequest = 0x000D,
    /// Test-only: ask the agent to exit so PID 1 (busybox init) respawns it,
    /// exercising the supervision path. Used by the hv_e2e harness; not wired to
    /// any production CLI path.
    KillAgentRequest = 0x000E,

    // Sandbox CRUD request types (0x0020 - 0x0026).
    SandboxCreateRequest = 0x0020,
    SandboxStopRequest = 0x0021,
    SandboxRemoveRequest = 0x0022,
    SandboxInspectRequest = 0x0023,
    SandboxListRequest = 0x0024,
    /// DNAT a reserved guest port to a sandbox port (payload:
    /// `sandbox.v1.SandboxPortForwardRequest`).
    SandboxPortForwardRequest = 0x0025,
    /// Remove a sandbox DNAT mapping (payload:
    /// `sandbox.v1.SandboxPortForwardRemoveRequest`).
    SandboxPortForwardRemoveRequest = 0x0026,

    // Sandbox workload request types (0x0030 - 0x0034).
    SandboxRunRequest = 0x0030,
    SandboxExecRequest = 0x0031,
    SandboxEventsRequest = 0x0032,
    /// Streaming stdin frame sent by the host after [`SandboxExecRequest`]
    /// to forward user input to the running process.
    SandboxExecInput = 0x0033,
    /// TTY resize frame sent by the host during an exec session. Payload is
    /// a prost-encoded `sandbox.v1.TerminalSize`.
    SandboxExecResize = 0x0034,
    /// Read a file from a sandbox (payload: `sandbox.v1.ReadFileRequest`).
    /// The agent answers with a stream of [`Self::SandboxFileData`] frames.
    SandboxFileReadRequest = 0x0035,
    /// Open a file-write stream into a sandbox (payload:
    /// `sandbox.v1.WriteFileOpen`), followed by [`Self::SandboxFileChunk`]
    /// frames and answered with [`Self::SandboxFileWriteResponse`].
    SandboxFileWriteRequest = 0x0036,
    /// One chunk of file content during a write stream (payload:
    /// `sandbox.v1.FileChunk`; `done == true` on the last chunk).
    SandboxFileChunk = 0x0037,

    // Sandbox snapshot request types (0x0040 - 0x0043).
    SandboxCheckpointRequest = 0x0040,
    SandboxRestoreRequest = 0x0041,
    SandboxListSnapshotsRequest = 0x0042,
    SandboxDeleteSnapshotRequest = 0x0043,

    // Response types (0x1000 - 0x1FFF).
    PingResponse = 0x1001,
    GetSystemInfoResponse = 0x1002,
    EnsureRuntimeResponse = 0x1003,
    RuntimeStatusResponse = 0x1004,
    KubernetesStartResponse = 0x1005,
    KubernetesStopResponse = 0x1006,
    KubernetesDeleteResponse = 0x1007,
    KubernetesStatusResponse = 0x1008,
    KubernetesKubeconfigResponse = 0x1009,
    ShutdownResponse = 0x100A,
    /// Test-only: response for `MmapReadFileRequest` (ABX-362).
    MmapReadFileResponse = 0x100B,
    /// Response to `DiskTrimRequest` with per-mount trim summary.
    DiskTrimResponse = 0x100C,
    /// Guest-driven readiness event frame.
    ReadinessEvent = 0x100D,
    /// Test-only: acknowledgement for `KillAgentRequest`.
    KillAgentResponse = 0x100E,
    PortBindingsChanged = 0x1030,
    PortBindingsRemoved = 0x1031,

    // Sandbox CRUD response types (0x1020 - 0x1026).
    SandboxCreateResponse = 0x1020,
    SandboxStopResponse = 0x1021,
    SandboxRemoveResponse = 0x1022,
    SandboxInspectResponse = 0x1023,
    SandboxListResponse = 0x1024,
    /// Answers [`Self::SandboxPortForwardRequest`] (payload:
    /// `sandbox.v1.SandboxPortForwardResponse`).
    SandboxPortForwardResponse = 0x1025,
    /// Acknowledges [`Self::SandboxPortForwardRemoveRequest`] (empty payload).
    SandboxPortForwardRemoveResponse = 0x1026,

    // Sandbox workload response types (streaming).
    SandboxRunOutput = 0x1035,
    SandboxExecOutput = 0x1036,
    SandboxEvent = 0x1037,
    /// One chunk of file content answering [`Self::SandboxFileReadRequest`]
    /// (payload: `sandbox.v1.FileChunk`; `done == true` on the last chunk).
    SandboxFileData = 0x1038,
    /// Acknowledges a completed file-write stream (empty payload).
    SandboxFileWriteResponse = 0x1039,

    // Sandbox snapshot response types (0x1040 - 0x1043).
    SandboxCheckpointResponse = 0x1040,
    SandboxRestoreResponse = 0x1041,
    SandboxListSnapshotsResponse = 0x1042,
    SandboxDeleteSnapshotResponse = 0x1043,

    // Special types.
    Empty = 0x0000,
    Error = 0xFFFF,
}

impl MessageType {
    /// Converts a numeric wire value into a typed message kind.
    #[must_use]
    pub const fn from_u32(value: u32) -> Option<Self> {
        match value {
            0x0001 => Some(Self::PingRequest),
            0x0002 => Some(Self::GetSystemInfoRequest),
            0x0003 => Some(Self::EnsureRuntimeRequest),
            0x0004 => Some(Self::RuntimeStatusRequest),
            0x0005 => Some(Self::KubernetesStartRequest),
            0x0006 => Some(Self::KubernetesStopRequest),
            0x0007 => Some(Self::KubernetesDeleteRequest),
            0x0008 => Some(Self::KubernetesStatusRequest),
            0x0009 => Some(Self::KubernetesKubeconfigRequest),
            0x000A => Some(Self::ShutdownRequest),
            0x000B => Some(Self::MmapReadFileRequest),
            0x000C => Some(Self::DiskTrimRequest),
            0x000D => Some(Self::WatchReadinessRequest),
            0x000E => Some(Self::KillAgentRequest),
            // Sandbox CRUD requests.
            0x0020 => Some(Self::SandboxCreateRequest),
            0x0021 => Some(Self::SandboxStopRequest),
            0x0022 => Some(Self::SandboxRemoveRequest),
            0x0023 => Some(Self::SandboxInspectRequest),
            0x0024 => Some(Self::SandboxListRequest),
            0x0025 => Some(Self::SandboxPortForwardRequest),
            0x0026 => Some(Self::SandboxPortForwardRemoveRequest),
            // Sandbox workload requests.
            0x0030 => Some(Self::SandboxRunRequest),
            0x0031 => Some(Self::SandboxExecRequest),
            0x0032 => Some(Self::SandboxEventsRequest),
            0x0033 => Some(Self::SandboxExecInput),
            0x0034 => Some(Self::SandboxExecResize),
            0x0035 => Some(Self::SandboxFileReadRequest),
            0x0036 => Some(Self::SandboxFileWriteRequest),
            0x0037 => Some(Self::SandboxFileChunk),
            // Sandbox snapshot requests.
            0x0040 => Some(Self::SandboxCheckpointRequest),
            0x0041 => Some(Self::SandboxRestoreRequest),
            0x0042 => Some(Self::SandboxListSnapshotsRequest),
            0x0043 => Some(Self::SandboxDeleteSnapshotRequest),
            // Responses.
            0x1001 => Some(Self::PingResponse),
            0x1002 => Some(Self::GetSystemInfoResponse),
            0x1003 => Some(Self::EnsureRuntimeResponse),
            0x1004 => Some(Self::RuntimeStatusResponse),
            0x1005 => Some(Self::KubernetesStartResponse),
            0x1006 => Some(Self::KubernetesStopResponse),
            0x1007 => Some(Self::KubernetesDeleteResponse),
            0x1008 => Some(Self::KubernetesStatusResponse),
            0x1009 => Some(Self::KubernetesKubeconfigResponse),
            0x100A => Some(Self::ShutdownResponse),
            0x100B => Some(Self::MmapReadFileResponse),
            0x100C => Some(Self::DiskTrimResponse),
            0x100D => Some(Self::ReadinessEvent),
            0x100E => Some(Self::KillAgentResponse),
            0x1030 => Some(Self::PortBindingsChanged),
            0x1031 => Some(Self::PortBindingsRemoved),
            // Sandbox CRUD responses.
            0x1020 => Some(Self::SandboxCreateResponse),
            0x1021 => Some(Self::SandboxStopResponse),
            0x1022 => Some(Self::SandboxRemoveResponse),
            0x1023 => Some(Self::SandboxInspectResponse),
            0x1024 => Some(Self::SandboxListResponse),
            0x1025 => Some(Self::SandboxPortForwardResponse),
            0x1026 => Some(Self::SandboxPortForwardRemoveResponse),
            // Sandbox workload responses (streaming).
            0x1035 => Some(Self::SandboxRunOutput),
            0x1036 => Some(Self::SandboxExecOutput),
            0x1037 => Some(Self::SandboxEvent),
            0x1038 => Some(Self::SandboxFileData),
            0x1039 => Some(Self::SandboxFileWriteResponse),
            // Sandbox snapshot responses.
            0x1040 => Some(Self::SandboxCheckpointResponse),
            0x1041 => Some(Self::SandboxRestoreResponse),
            0x1042 => Some(Self::SandboxListSnapshotsResponse),
            0x1043 => Some(Self::SandboxDeleteSnapshotResponse),
            0x0000 => Some(Self::Empty),
            0xFFFF => Some(Self::Error),
            _ => None,
        }
    }

    /// Returns true if this message type is a sandbox request that should be
    /// handled by the sandbox service rather than the standard RPC dispatcher.
    #[must_use]
    pub const fn is_sandbox_request(self) -> bool {
        matches!(
            self,
            Self::SandboxCreateRequest
                | Self::SandboxStopRequest
                | Self::SandboxRemoveRequest
                | Self::SandboxInspectRequest
                | Self::SandboxListRequest
                | Self::SandboxRunRequest
                | Self::SandboxExecRequest
                | Self::SandboxEventsRequest
                | Self::SandboxFileReadRequest
                | Self::SandboxFileWriteRequest
                | Self::SandboxPortForwardRequest
                | Self::SandboxPortForwardRemoveRequest
                | Self::SandboxCheckpointRequest
                | Self::SandboxRestoreRequest
                | Self::SandboxListSnapshotsRequest
                | Self::SandboxDeleteSnapshotRequest
        )
    }

    /// Returns true if this message type is a Kubernetes management request.
    #[must_use]
    pub const fn is_kubernetes_request(self) -> bool {
        matches!(
            self,
            Self::KubernetesStartRequest
                | Self::KubernetesStopRequest
                | Self::KubernetesDeleteRequest
                | Self::KubernetesStatusRequest
                | Self::KubernetesKubeconfigRequest
        )
    }
}

#[cfg(test)]
mod tests {
    use super::MessageType;

    #[test]
    fn message_type_roundtrip_known_values() {
        const CASES: &[(u32, MessageType)] = &[
            (0x0001, MessageType::PingRequest),
            (0x0002, MessageType::GetSystemInfoRequest),
            (0x0003, MessageType::EnsureRuntimeRequest),
            (0x0004, MessageType::RuntimeStatusRequest),
            (0x0005, MessageType::KubernetesStartRequest),
            (0x0006, MessageType::KubernetesStopRequest),
            (0x0007, MessageType::KubernetesDeleteRequest),
            (0x0008, MessageType::KubernetesStatusRequest),
            (0x0009, MessageType::KubernetesKubeconfigRequest),
            (0x000A, MessageType::ShutdownRequest),
            (0x000B, MessageType::MmapReadFileRequest),
            (0x000C, MessageType::DiskTrimRequest),
            (0x000D, MessageType::WatchReadinessRequest),
            (0x000E, MessageType::KillAgentRequest),
            (0x1001, MessageType::PingResponse),
            (0x1002, MessageType::GetSystemInfoResponse),
            (0x1003, MessageType::EnsureRuntimeResponse),
            (0x1004, MessageType::RuntimeStatusResponse),
            (0x1005, MessageType::KubernetesStartResponse),
            (0x1006, MessageType::KubernetesStopResponse),
            (0x1007, MessageType::KubernetesDeleteResponse),
            (0x1008, MessageType::KubernetesStatusResponse),
            (0x1009, MessageType::KubernetesKubeconfigResponse),
            (0x100A, MessageType::ShutdownResponse),
            (0x100B, MessageType::MmapReadFileResponse),
            (0x100C, MessageType::DiskTrimResponse),
            (0x100D, MessageType::ReadinessEvent),
            (0x100E, MessageType::KillAgentResponse),
            (0x1030, MessageType::PortBindingsChanged),
            (0x1031, MessageType::PortBindingsRemoved),
            (0x0000, MessageType::Empty),
            (0xFFFF, MessageType::Error),
            // Sandbox CRUD.
            (0x0020, MessageType::SandboxCreateRequest),
            (0x0021, MessageType::SandboxStopRequest),
            (0x0022, MessageType::SandboxRemoveRequest),
            (0x0023, MessageType::SandboxInspectRequest),
            (0x0024, MessageType::SandboxListRequest),
            (0x1020, MessageType::SandboxCreateResponse),
            (0x1021, MessageType::SandboxStopResponse),
            (0x1022, MessageType::SandboxRemoveResponse),
            (0x1023, MessageType::SandboxInspectResponse),
            (0x1024, MessageType::SandboxListResponse),
            (0x0025, MessageType::SandboxPortForwardRequest),
            (0x0026, MessageType::SandboxPortForwardRemoveRequest),
            (0x1025, MessageType::SandboxPortForwardResponse),
            (0x1026, MessageType::SandboxPortForwardRemoveResponse),
            // Sandbox workload.
            (0x0030, MessageType::SandboxRunRequest),
            (0x0031, MessageType::SandboxExecRequest),
            (0x0032, MessageType::SandboxEventsRequest),
            (0x0033, MessageType::SandboxExecInput),
            (0x0034, MessageType::SandboxExecResize),
            (0x0035, MessageType::SandboxFileReadRequest),
            (0x0036, MessageType::SandboxFileWriteRequest),
            (0x0037, MessageType::SandboxFileChunk),
            (0x1035, MessageType::SandboxRunOutput),
            (0x1036, MessageType::SandboxExecOutput),
            (0x1037, MessageType::SandboxEvent),
            (0x1038, MessageType::SandboxFileData),
            (0x1039, MessageType::SandboxFileWriteResponse),
            // Sandbox snapshots.
            (0x0040, MessageType::SandboxCheckpointRequest),
            (0x0041, MessageType::SandboxRestoreRequest),
            (0x0042, MessageType::SandboxListSnapshotsRequest),
            (0x0043, MessageType::SandboxDeleteSnapshotRequest),
            (0x1040, MessageType::SandboxCheckpointResponse),
            (0x1041, MessageType::SandboxRestoreResponse),
            (0x1042, MessageType::SandboxListSnapshotsResponse),
            (0x1043, MessageType::SandboxDeleteSnapshotResponse),
        ];

        for (raw, expected) in CASES {
            assert_eq!(MessageType::from_u32(*raw), Some(*expected));
        }
    }

    #[test]
    fn message_type_rejects_unknown_values() {
        assert_eq!(MessageType::from_u32(0x9999), None);
        assert_eq!(MessageType::from_u32(0x1010), None);
    }

    #[test]
    fn is_sandbox_request_classifies_correctly() {
        assert!(MessageType::SandboxCreateRequest.is_sandbox_request());
        assert!(MessageType::SandboxRunRequest.is_sandbox_request());
        assert!(MessageType::SandboxFileReadRequest.is_sandbox_request());
        assert!(MessageType::SandboxFileWriteRequest.is_sandbox_request());
        assert!(MessageType::SandboxCheckpointRequest.is_sandbox_request());
        assert!(!MessageType::PingRequest.is_sandbox_request());
        assert!(!MessageType::SandboxCreateResponse.is_sandbox_request());
    }

    #[test]
    fn is_kubernetes_request_classifies_correctly() {
        assert!(MessageType::KubernetesStartRequest.is_kubernetes_request());
        assert!(MessageType::KubernetesKubeconfigRequest.is_kubernetes_request());
        assert!(!MessageType::PingRequest.is_kubernetes_request());
        assert!(!MessageType::KubernetesStatusResponse.is_kubernetes_request());
    }
}