use serde::{Deserialize, Serialize};
use super::common::{UdsPath, VsockId};
pub const MIN_GUEST_CID: u32 = 3;
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RawVsockConfig {
#[serde(default)]
pub vsock_id: Option<String>,
pub guest_cid: u32,
pub uds_path: String,
#[serde(default)]
pub tsi: bool,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct VsockConfig {
pub vsock_id: Option<VsockId>,
pub guest_cid: u32,
pub uds_path: UdsPath,
pub tsi: bool,
}
impl TryFrom<RawVsockConfig> for VsockConfig {
type Error = String;
fn try_from(raw: RawVsockConfig) -> Result<Self, Self::Error> {
if raw.guest_cid < MIN_GUEST_CID {
return Err(format!(
"Invalid guest_cid: must be >= {MIN_GUEST_CID}, got {}",
raw.guest_cid
));
}
let vsock_id = match raw.vsock_id {
Some(s) => Some(VsockId::new(s)?),
None => None,
};
let uds_path = UdsPath::new(raw.uds_path).map_err(|e| format!("Invalid uds_path: {e}"))?;
Ok(Self {
vsock_id,
guest_cid: raw.guest_cid,
uds_path,
tsi: raw.tsi,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_accept_minimal_vsock() {
let raw = RawVsockConfig {
vsock_id: None,
guest_cid: 3,
uds_path: "/tmp/vsock.sock".into(),
tsi: false,
};
let cfg = VsockConfig::try_from(raw).unwrap();
assert_eq!(cfg.guest_cid, 3);
}
#[test]
fn test_should_reject_guest_cid_below_3() {
let raw = RawVsockConfig {
vsock_id: None,
guest_cid: 2,
uds_path: "/tmp/vsock.sock".into(),
tsi: false,
};
assert!(VsockConfig::try_from(raw).is_err());
}
#[test]
fn test_should_reject_uds_path_above_darwin_cap() {
let raw = RawVsockConfig {
vsock_id: None,
guest_cid: 3,
uds_path: format!("/tmp/{}", "a".repeat(110)),
tsi: false,
};
let err = VsockConfig::try_from(raw).unwrap_err();
assert!(err.contains("uds_path"));
}
#[test]
fn test_should_default_tsi_to_false() {
let json = r#"{"guest_cid":3,"uds_path":"/tmp/v"}"#;
let raw: RawVsockConfig = serde_json::from_str(json).unwrap();
assert!(!raw.tsi);
}
}