Skip to main content

vm_proto/
frame.rs

1use std::io::{self, Read, Write};
2
3// I/O streams
4pub const STDIN: u8 = 0x01;
5pub const STDOUT: u8 = 0x02;
6pub const STDERR: u8 = 0x03;
7
8// Control
9pub const RESIZE: u8 = 0x04;
10pub const EXIT: u8 = 0x05;
11pub const ERROR: u8 = 0x06;
12pub const KILL: u8 = 0x07;
13
14// Exec handshake
15pub const EXEC_REQ: u8 = 0x10;
16
17// Mount handshake
18pub const MOUNT_REQ: u8 = 0x11;
19pub const MOUNT_RESP: u8 = 0x12;
20
21// File I/O
22pub const READ_FILE_REQ: u8 = 0x13;
23pub const READ_FILE_RESP: u8 = 0x14;
24pub const WRITE_FILE_REQ: u8 = 0x15;
25pub const WRITE_FILE_DATA: u8 = 0x16;
26pub const WRITE_FILE_RESP: u8 = 0x17;
27
28// Port forwarding
29pub const FWD_REQ: u8 = 0x20;
30pub const FWD_RESP: u8 = 0x21;
31
32// File watching
33pub const WATCH_REQ: u8 = 0x30;
34pub const WATCH_EVENT: u8 = 0x31;
35
36// Filesystem operations
37pub const MKDIR_REQ: u8 = 0x40;
38pub const FS_OK_RESP: u8 = 0x41;
39pub const READ_DIR_REQ: u8 = 0x42;
40pub const READ_DIR_RESP: u8 = 0x43;
41pub const STAT_REQ: u8 = 0x44;
42pub const STAT_RESP: u8 = 0x45;
43pub const REMOVE_REQ: u8 = 0x46;
44pub const RENAME_REQ: u8 = 0x48;
45pub const COPY_REQ: u8 = 0x4A;
46pub const CHMOD_REQ: u8 = 0x4C;
47
48// Overlay operations
49pub const DISCARD_REQ: u8 = 0x4E;
50pub const DISCARD_RESP: u8 = 0x4F;
51
52// Download
53pub const DOWNLOAD_REQ: u8 = 0x50;
54pub const DOWNLOAD_PROGRESS: u8 = 0x51;
55
56// Attestation. The request payload is 64 raw bytes and nothing else — the
57// figure a hardware report is taken over. It carries no structure because the
58// guest gives it none: it hands the bytes to the platform and hands back what
59// the platform said. The response is a JSON `vm_measure::attest::Status`.
60pub const ATTEST_REQ: u8 = 0x60;
61pub const ATTEST_RESP: u8 = 0x61;
62
63const MAX_FRAME: u32 = 1 << 20; // 1 MB
64
65/// Write a binary frame: `[u32 BE length][u8 type][payload]`.
66///
67/// Assembles the header + payload into a single buffer so that the entire
68/// frame is sent in one `write_all` call. This avoids multiple small TCP
69/// segments when `TCP_NODELAY` is enabled.
70pub fn write_frame(w: &mut impl Write, msg_type: u8, payload: &[u8]) -> io::Result<()> {
71    let len = 1u32 + payload.len() as u32;
72    let mut buf = Vec::with_capacity(4 + 1 + payload.len());
73    buf.extend_from_slice(&len.to_be_bytes());
74    buf.push(msg_type);
75    buf.extend_from_slice(payload);
76    w.write_all(&buf)?;
77    w.flush()
78}
79
80/// Read a binary frame. Returns `None` on clean EOF, `Err` on protocol
81/// violations or I/O errors.
82pub fn read_frame(r: &mut impl Read) -> io::Result<Option<(u8, Vec<u8>)>> {
83    let mut len_buf = [0u8; 4];
84    match r.read_exact(&mut len_buf) {
85        Ok(()) => {}
86        Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
87        Err(e) => return Err(e),
88    }
89    let len = u32::from_be_bytes(len_buf);
90    if len == 0 || len > MAX_FRAME {
91        return Err(io::Error::new(
92            io::ErrorKind::InvalidData,
93            format!("frame length out of range: {}", len),
94        ));
95    }
96    let mut type_buf = [0u8; 1];
97    r.read_exact(&mut type_buf)?;
98    let payload_len = (len - 1) as usize;
99    let mut payload = vec![0u8; payload_len];
100    if payload_len > 0 {
101        r.read_exact(&mut payload)?;
102    }
103    Ok(Some((type_buf[0], payload)))
104}
105
106/// Serialize `msg` as JSON and send it as a typed frame.
107pub fn send_json(w: &mut impl Write, msg_type: u8, msg: &impl serde::Serialize) -> io::Result<()> {
108    let payload = serde_json::to_vec(msg).map_err(io::Error::other)?;
109    write_frame(w, msg_type, &payload)
110}
111
112/// Try to parse a complete frame from the front of `buf`.
113/// Returns `Some((msg_type, payload_start, total_len))` if a full
114/// frame is available, `None` if more data is needed.
115pub fn try_parse(buf: &[u8]) -> Option<(u8, usize, usize)> {
116    if buf.len() < 5 {
117        return None;
118    }
119    let len = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
120    if len == 0 || len > MAX_FRAME {
121        return None;
122    }
123    let total = 4 + len as usize;
124    if buf.len() < total {
125        return None;
126    }
127    let msg_type = buf[4];
128    Some((msg_type, 5, total))
129}
130
131/// Build a RESIZE payload: `[u16 BE rows][u16 BE cols]`.
132pub fn resize_payload(rows: u16, cols: u16) -> [u8; 4] {
133    let mut buf = [0u8; 4];
134    buf[0..2].copy_from_slice(&rows.to_be_bytes());
135    buf[2..4].copy_from_slice(&cols.to_be_bytes());
136    buf
137}
138
139/// Parse a RESIZE payload into (rows, cols).
140pub fn parse_resize(payload: &[u8]) -> Option<(u16, u16)> {
141    if payload.len() < 4 {
142        return None;
143    }
144    let rows = u16::from_be_bytes([payload[0], payload[1]]);
145    let cols = u16::from_be_bytes([payload[2], payload[3]]);
146    Some((rows, cols))
147}
148
149/// Build an EXIT payload: `[i32 BE code]`.
150pub fn exit_payload(code: i32) -> [u8; 4] {
151    code.to_be_bytes()
152}
153
154/// Parse an EXIT payload into an i32 exit code.
155pub fn parse_exit_code(payload: &[u8]) -> Option<i32> {
156    if payload.len() < 4 {
157        return None;
158    }
159    Some(i32::from_be_bytes([
160        payload[0], payload[1], payload[2], payload[3],
161    ]))
162}