Skip to main content

hyperlight_common/virtq/
msg.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 The Hyperlight Authors.
3
4//! Wire format header for all virtqueue messages.
5//!
6//! Every payload on both the G2H and H2G queues starts with this
7//! fixed 8-byte header, enabling message type discrimination and
8//! request/response correlation.
9
10use bitflags::bitflags;
11
12/// Message types for the virtqueue wire protocol.
13#[repr(u8)]
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum MsgKind {
16    /// A function call request (FunctionCall payload follows).
17    Request = 0x01,
18    /// A function call response (FunctionCallResult payload follows).
19    Response = 0x02,
20    /// A stream data chunk.
21    StreamChunk = 0x03,
22    /// End-of-stream marker.
23    StreamEnd = 0x04,
24    /// Cancel a pending request.
25    Cancel = 0x05,
26    /// A guest log message (GuestLogData payload follows).
27    Log = 0x06,
28}
29
30impl TryFrom<u8> for MsgKind {
31    type Error = u8;
32
33    fn try_from(value: u8) -> Result<Self, Self::Error> {
34        match value {
35            0x01 => Ok(Self::Request),
36            0x02 => Ok(Self::Response),
37            0x03 => Ok(Self::StreamChunk),
38            0x04 => Ok(Self::StreamEnd),
39            0x05 => Ok(Self::Cancel),
40            0x06 => Ok(Self::Log),
41            other => Err(other),
42        }
43    }
44}
45
46bitflags! {
47    #[repr(transparent)]
48    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
49    pub struct MsgFlags: u8 {
50        /// More descriptors follow for this message.
51        const MORE = 1 << 0;
52    }
53}
54
55/// Wire header for all virtqueue messages
56#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
57#[repr(C)]
58pub struct VirtqMsgHeader {
59    /// Discriminates the message type.
60    pub kind: u8,
61    /// Per-message flags (see [`MsgFlags`]).
62    pub flags: u8,
63    /// Caller-assigned correlation ID. Responses echo the request's ID.
64    pub req_id: u16,
65    /// Byte length of the payload following this header in this descriptor.
66    pub payload_len: u32,
67}
68
69impl VirtqMsgHeader {
70    pub const SIZE: usize = core::mem::size_of::<Self>();
71
72    /// Create a new message header with no flags set.
73    pub const fn new(kind: MsgKind, req_id: u16, payload_len: u32) -> Self {
74        Self {
75            kind: kind as u8,
76            flags: 0,
77            req_id,
78            payload_len,
79        }
80    }
81
82    /// Create a new header with flags.
83    pub const fn with_flags(kind: MsgKind, flags: MsgFlags, req_id: u16, payload_len: u32) -> Self {
84        Self {
85            kind: kind as u8,
86            flags: flags.bits(),
87            req_id,
88            payload_len,
89        }
90    }
91
92    /// Parse the kind field into a [`MsgKind`] enum.
93    pub fn msg_kind(&self) -> Result<MsgKind, u8> {
94        MsgKind::try_from(self.kind)
95    }
96
97    /// Interpret the raw flags field as [`MsgFlags`].
98    pub fn msg_flags(&self) -> MsgFlags {
99        MsgFlags::from_bits_truncate(self.flags)
100    }
101
102    /// Returns true if [`MsgFlags::MORE`] is set, indicating more
103    /// descriptors follow for this message.
104    pub const fn has_more(&self) -> bool {
105        self.flags & MsgFlags::MORE.bits() != 0
106    }
107}