Skip to main content

squib_api/schemas/
vsock.rs

1//! `/vsock` PUT body.
2//!
3//! Per [21-api-compat-matrix.md `/vsock` PUT](../../../specs/21-api-compat-matrix.md#vsock-put):
4//!
5//! - `guest_cid` — minimum 3 (CIDs 0–2 reserved by the spec).
6//! - `uds_path` — `UdsPath` (103-byte cap on Darwin).
7//! - `tsi` — squib extension; opt-in TSI mode (default false).
8//! - `vsock_id` — Firecracker still accepts this for back-compat though it's ignored at runtime; we
9//!   accept and validate via `VsockId`.
10
11use serde::{Deserialize, Serialize};
12
13use super::common::{UdsPath, VsockId};
14
15/// Lowest legal `guest_cid` (0 = HV, 1 = local, 2 = host — all reserved).
16pub const MIN_GUEST_CID: u32 = 3;
17
18/// Raw `/vsock` PUT body off the wire.
19#[derive(Debug, Clone, Deserialize)]
20#[serde(deny_unknown_fields)]
21pub struct RawVsockConfig {
22    /// Caller-supplied vsock ID (back-compat field; runtime-ignored upstream).
23    #[serde(default)]
24    pub vsock_id: Option<String>,
25    /// Guest-side CID. Must be `>= 3`.
26    pub guest_cid: u32,
27    /// UDS path the host multiplex listener binds.
28    pub uds_path: String,
29    /// Squib-only opt-in: enable the TSI mode. Default `false`.
30    #[serde(default)]
31    pub tsi: bool,
32}
33
34/// Validated `/vsock` PUT body.
35#[derive(Debug, Clone, Serialize)]
36#[non_exhaustive]
37pub struct VsockConfig {
38    /// Optional validated vsock ID.
39    pub vsock_id: Option<VsockId>,
40    /// Validated guest CID.
41    pub guest_cid: u32,
42    /// Validated UDS path.
43    pub uds_path: UdsPath,
44    /// TSI extension flag.
45    pub tsi: bool,
46}
47
48impl TryFrom<RawVsockConfig> for VsockConfig {
49    type Error = String;
50
51    fn try_from(raw: RawVsockConfig) -> Result<Self, Self::Error> {
52        if raw.guest_cid < MIN_GUEST_CID {
53            return Err(format!(
54                "Invalid guest_cid: must be >= {MIN_GUEST_CID}, got {}",
55                raw.guest_cid
56            ));
57        }
58        let vsock_id = match raw.vsock_id {
59            Some(s) => Some(VsockId::new(s)?),
60            None => None,
61        };
62        let uds_path = UdsPath::new(raw.uds_path).map_err(|e| format!("Invalid uds_path: {e}"))?;
63        Ok(Self {
64            vsock_id,
65            guest_cid: raw.guest_cid,
66            uds_path,
67            tsi: raw.tsi,
68        })
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn test_should_accept_minimal_vsock() {
78        let raw = RawVsockConfig {
79            vsock_id: None,
80            guest_cid: 3,
81            uds_path: "/tmp/vsock.sock".into(),
82            tsi: false,
83        };
84        let cfg = VsockConfig::try_from(raw).unwrap();
85        assert_eq!(cfg.guest_cid, 3);
86    }
87
88    #[test]
89    fn test_should_reject_guest_cid_below_3() {
90        let raw = RawVsockConfig {
91            vsock_id: None,
92            guest_cid: 2,
93            uds_path: "/tmp/vsock.sock".into(),
94            tsi: false,
95        };
96        assert!(VsockConfig::try_from(raw).is_err());
97    }
98
99    #[test]
100    fn test_should_reject_uds_path_above_darwin_cap() {
101        let raw = RawVsockConfig {
102            vsock_id: None,
103            guest_cid: 3,
104            uds_path: format!("/tmp/{}", "a".repeat(110)),
105            tsi: false,
106        };
107        let err = VsockConfig::try_from(raw).unwrap_err();
108        assert!(err.contains("uds_path"));
109    }
110
111    #[test]
112    fn test_should_default_tsi_to_false() {
113        let json = r#"{"guest_cid":3,"uds_path":"/tmp/v"}"#;
114        let raw: RawVsockConfig = serde_json::from_str(json).unwrap();
115        assert!(!raw.tsi);
116    }
117}