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}