squib_api/schemas/
boot_source.rs1use serde::{Deserialize, Serialize};
13
14use super::common::SafePath;
15
16pub const BOOT_ARGS_MAX: usize = 4096;
18
19#[derive(Debug, Clone, Deserialize)]
21#[serde(deny_unknown_fields)]
22pub struct RawBootSourceConfig {
23 pub kernel_image_path: String,
25 #[serde(default)]
27 pub initrd_path: Option<String>,
28 #[serde(default)]
30 pub boot_args: Option<String>,
31}
32
33#[derive(Debug, Clone, Serialize)]
35#[non_exhaustive]
36pub struct BootSourceConfig {
37 pub kernel_image_path: SafePath,
39 pub initrd_path: Option<SafePath>,
41 pub boot_args: Option<String>,
43}
44
45impl TryFrom<RawBootSourceConfig> for BootSourceConfig {
46 type Error = String;
47
48 fn try_from(raw: RawBootSourceConfig) -> Result<Self, Self::Error> {
49 let kernel_image_path = SafePath::new(raw.kernel_image_path)
50 .map_err(|e| format!("Invalid kernel_image_path: {e}"))?;
51 let initrd_path = match raw.initrd_path {
52 Some(p) => Some(SafePath::new(p).map_err(|e| format!("Invalid initrd_path: {e}"))?),
53 None => None,
54 };
55 if let Some(args) = raw.boot_args.as_deref() {
56 if args.len() > BOOT_ARGS_MAX {
57 return Err(format!(
58 "Invalid boot_args: exceeds {BOOT_ARGS_MAX} bytes (got {} bytes)",
59 args.len()
60 ));
61 }
62 if args.contains('\0') {
63 return Err("Invalid boot_args: must not contain NUL bytes".into());
64 }
65 }
66 Ok(Self {
67 kernel_image_path,
68 initrd_path,
69 boot_args: raw.boot_args,
70 })
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn test_should_accept_minimal_boot_source() {
80 let raw = RawBootSourceConfig {
81 kernel_image_path: "/tmp/vmlinux.bin".into(),
82 initrd_path: None,
83 boot_args: None,
84 };
85 let cfg = BootSourceConfig::try_from(raw).unwrap();
86 assert_eq!(
87 cfg.kernel_image_path.as_path().as_os_str(),
88 "/tmp/vmlinux.bin"
89 );
90 assert!(cfg.initrd_path.is_none());
91 }
92
93 #[test]
94 fn test_should_reject_empty_kernel_path() {
95 let raw = RawBootSourceConfig {
96 kernel_image_path: String::new(),
97 initrd_path: None,
98 boot_args: None,
99 };
100 assert!(BootSourceConfig::try_from(raw).is_err());
101 }
102
103 #[test]
104 fn test_should_reject_oversize_boot_args() {
105 let raw = RawBootSourceConfig {
106 kernel_image_path: "/tmp/vmlinux.bin".into(),
107 initrd_path: None,
108 boot_args: Some("a".repeat(BOOT_ARGS_MAX + 1)),
109 };
110 let err = BootSourceConfig::try_from(raw).unwrap_err();
111 assert!(err.contains("boot_args"));
112 }
113
114 #[test]
115 fn test_should_reject_nul_in_boot_args() {
116 let raw = RawBootSourceConfig {
117 kernel_image_path: "/tmp/vmlinux.bin".into(),
118 initrd_path: None,
119 boot_args: Some("rw\0bad".into()),
120 };
121 assert!(BootSourceConfig::try_from(raw).is_err());
122 }
123
124 #[test]
125 fn test_should_reject_unknown_fields() {
126 let json = r#"{"kernel_image_path":"/k","unknown":1}"#;
127 assert!(serde_json::from_str::<RawBootSourceConfig>(json).is_err());
128 }
129
130 #[test]
131 fn test_should_round_trip_through_serde() {
132 let json = r#"{"kernel_image_path":"/tmp/k","initrd_path":"/tmp/i","boot_args":"console=ttyAMA0"}"#;
133 let raw: RawBootSourceConfig = serde_json::from_str(json).unwrap();
134 let cfg = BootSourceConfig::try_from(raw).unwrap();
135 assert_eq!(cfg.boot_args.as_deref(), Some("console=ttyAMA0"));
136 }
137}