Skip to main content

squib_api/schemas/
cpu_config.rs

1//! `/cpu-config` PUT body.
2//!
3//! Per [21-api-compat-matrix.md `/cpu-config`
4//! PUT](../../../specs/21-api-compat-matrix.md#cpu-config-put):
5//!
6//! - aarch64 `reg_modifiers` and `vcpu_features` — `F (best-effort)`.
7//! - x86 `cpuid_modifiers` and `msr_modifiers` — `A` (accept-and-warn).
8//! - `kvm_capabilities` — `A`.
9//!
10//! At this layer we keep the shape loose (each subtree is a `serde_json::Value`) and
11//! defer the per-register honor/warn decision to the VMM. Validation here is structural:
12//! deny unknown top-level fields and enforce the per-class collection caps.
13
14use serde::{Deserialize, Serialize};
15
16/// Maximum number of `reg_modifiers` rows we accept; arbitrary but bounded.
17pub const MAX_REG_MODIFIERS: usize = 256;
18
19/// Raw `/cpu-config` PUT body off the wire.
20#[derive(Debug, Clone, Default, Deserialize)]
21#[serde(deny_unknown_fields)]
22pub struct RawCpuConfig {
23    /// aarch64 `reg_modifiers`.
24    #[serde(default)]
25    pub reg_modifiers: Option<Vec<serde_json::Value>>,
26    /// aarch64 `vcpu_features`.
27    #[serde(default)]
28    pub vcpu_features: Option<Vec<serde_json::Value>>,
29    /// x86 `cpuid_modifiers` (accept-and-warn).
30    #[serde(default)]
31    pub cpuid_modifiers: Option<Vec<serde_json::Value>>,
32    /// x86 `msr_modifiers` (accept-and-warn).
33    #[serde(default)]
34    pub msr_modifiers: Option<Vec<serde_json::Value>>,
35    /// `kvm_capabilities` (accept-and-warn).
36    #[serde(default)]
37    pub kvm_capabilities: Option<Vec<serde_json::Value>>,
38}
39
40/// Validated `/cpu-config` PUT body.
41#[derive(Debug, Clone, Default, Serialize)]
42#[non_exhaustive]
43pub struct CpuConfig {
44    /// aarch64 `reg_modifiers`, capped at `MAX_REG_MODIFIERS`.
45    pub reg_modifiers: Vec<serde_json::Value>,
46    /// aarch64 `vcpu_features`, capped at `MAX_REG_MODIFIERS`.
47    pub vcpu_features: Vec<serde_json::Value>,
48    /// x86 `cpuid_modifiers` — recorded, will warn at the controller.
49    pub cpuid_modifiers: Vec<serde_json::Value>,
50    /// x86 `msr_modifiers` — recorded, will warn at the controller.
51    pub msr_modifiers: Vec<serde_json::Value>,
52    /// `kvm_capabilities` — recorded, will warn at the controller.
53    pub kvm_capabilities: Vec<serde_json::Value>,
54}
55
56fn cap_check<T>(field: &str, v: &[T]) -> Result<(), String> {
57    if v.len() > MAX_REG_MODIFIERS {
58        return Err(format!(
59            "Invalid cpu-config: {field} exceeds {MAX_REG_MODIFIERS} entries"
60        ));
61    }
62    Ok(())
63}
64
65impl TryFrom<RawCpuConfig> for CpuConfig {
66    type Error = String;
67
68    fn try_from(raw: RawCpuConfig) -> Result<Self, Self::Error> {
69        let reg_modifiers = raw.reg_modifiers.unwrap_or_default();
70        let vcpu_features = raw.vcpu_features.unwrap_or_default();
71        let cpuid_modifiers = raw.cpuid_modifiers.unwrap_or_default();
72        let msr_modifiers = raw.msr_modifiers.unwrap_or_default();
73        let kvm_capabilities = raw.kvm_capabilities.unwrap_or_default();
74        cap_check("reg_modifiers", &reg_modifiers)?;
75        cap_check("vcpu_features", &vcpu_features)?;
76        cap_check("cpuid_modifiers", &cpuid_modifiers)?;
77        cap_check("msr_modifiers", &msr_modifiers)?;
78        cap_check("kvm_capabilities", &kvm_capabilities)?;
79        Ok(Self {
80            reg_modifiers,
81            vcpu_features,
82            cpuid_modifiers,
83            msr_modifiers,
84            kvm_capabilities,
85        })
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_should_accept_empty_cpu_config() {
95        let cfg = CpuConfig::try_from(RawCpuConfig::default()).unwrap();
96        assert!(cfg.reg_modifiers.is_empty());
97    }
98
99    #[test]
100    fn test_should_reject_oversize_reg_modifiers() {
101        let raw = RawCpuConfig {
102            reg_modifiers: Some(vec![serde_json::Value::Null; MAX_REG_MODIFIERS + 1]),
103            ..RawCpuConfig::default()
104        };
105        assert!(CpuConfig::try_from(raw).is_err());
106    }
107}