1#![expect(missing_docs)]
8#![forbid(unsafe_code)]
9
10use serde::Deserialize;
11use serde::Serialize;
12use std::collections::HashMap;
13use std::ffi::CString;
14use std::path::PathBuf;
15
16#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
18#[serde(rename_all = "snake_case")]
19pub enum UefiConfigType {
20 None,
22 Igvm,
24}
25
26#[derive(Serialize, Deserialize, Debug)]
28#[serde(rename_all = "snake_case")]
29pub enum SnpInjectionType {
30 Normal,
32 Restricted,
34}
35
36#[derive(Serialize, Deserialize, Debug)]
38#[serde(rename_all = "snake_case")]
39pub enum ConfigIsolationType {
40 None,
42 Vbs {
44 enable_debug: bool,
46 },
47 Snp {
49 shared_gpa_boundary_bits: Option<u8>,
53 policy: u64,
55 enable_debug: bool,
57 injection_type: SnpInjectionType,
59 },
60 Tdx {
62 enable_debug: bool,
64 sept_ve_disable: bool,
67 },
68}
69
70#[derive(Serialize, Deserialize, Debug)]
72#[serde(rename_all = "snake_case")]
73pub enum Image {
74 None,
76 Uefi { config_type: UefiConfigType },
78 Openhcl {
80 #[serde(default)]
82 command_line: String,
83 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
86 static_command_line: bool,
87 #[serde(skip_serializing_if = "Option::is_none")]
89 memory_page_base: Option<u64>,
90 memory_page_count: u64,
92 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
94 uefi: bool,
95 #[serde(skip_serializing_if = "Option::is_none")]
97 linux: Option<LinuxImage>,
98 },
99 Linux(LinuxImage),
102}
103
104#[derive(Serialize, Deserialize, Debug)]
105#[serde(rename_all = "snake_case")]
106pub struct LinuxImage {
107 pub use_initrd: bool,
109 pub command_line: CString,
111}
112
113impl Image {
114 pub fn required_resources(&self) -> Vec<ResourceType> {
116 match *self {
117 Image::None => vec![],
118 Image::Uefi { .. } => vec![ResourceType::Uefi],
119 Image::Openhcl {
120 uefi, ref linux, ..
121 } => [
122 ResourceType::UnderhillKernel,
123 ResourceType::OpenhclBoot,
124 ResourceType::UnderhillInitrd,
125 ]
126 .into_iter()
127 .chain(if uefi { Some(ResourceType::Uefi) } else { None })
128 .chain(linux.iter().flat_map(|linux| linux.required_resources()))
129 .collect(),
130 Image::Linux(ref linux) => linux.required_resources(),
131 }
132 }
133}
134
135impl LinuxImage {
136 fn required_resources(&self) -> Vec<ResourceType> {
137 [ResourceType::LinuxKernel]
138 .into_iter()
139 .chain(if self.use_initrd {
140 Some(ResourceType::LinuxInitrd)
141 } else {
142 None
143 })
144 .collect()
145 }
146}
147
148#[derive(Serialize, Deserialize, Debug)]
151pub struct GuestConfig {
152 pub guest_svn: u32,
154 pub max_vtl: u8,
156 pub isolation_type: ConfigIsolationType,
158 pub image: Image,
160}
161
162#[derive(Serialize, Deserialize, Debug)]
164#[serde(rename_all = "snake_case")]
165pub enum GuestArch {
166 X64,
168 Aarch64,
170}
171
172#[derive(Serialize, Deserialize, Debug)]
175#[serde(rename_all = "snake_case")]
176pub struct Config {
177 pub guest_arch: GuestArch,
179 pub guest_configs: Vec<GuestConfig>,
181}
182
183impl Config {
184 pub fn required_resources(&self) -> Vec<ResourceType> {
186 let mut resources = vec![];
187 for guest_config in &self.guest_configs {
188 resources.extend(guest_config.image.required_resources());
189 }
190 resources
191 }
192}
193
194#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
195#[serde(rename_all = "snake_case")]
196pub enum ResourceType {
197 Uefi,
198 UnderhillKernel,
199 OpenhclBoot,
200 UnderhillInitrd,
201 UnderhillSidecar,
202 LinuxKernel,
203 LinuxInitrd,
204}
205
206#[derive(Serialize, Deserialize, Debug)]
209#[serde(rename_all = "snake_case")]
210pub struct Resources {
211 #[serde(deserialize_with = "parse::resources")]
214 resources: HashMap<ResourceType, PathBuf>,
215}
216
217mod parse {
218 use super::*;
219 use serde::Deserialize;
220 use serde::Deserializer;
221 use std::collections::HashMap;
222
223 pub fn resources<'de, D: Deserializer<'de>>(
224 d: D,
225 ) -> Result<HashMap<ResourceType, PathBuf>, D::Error> {
226 let resources: HashMap<ResourceType, PathBuf> = Deserialize::deserialize(d)?;
227
228 for (resource, path) in &resources {
229 if !path.is_absolute() {
230 return Err(serde::de::Error::custom(AbsolutePathError(
231 *resource,
232 path.clone(),
233 )));
234 }
235 }
236
237 Ok(resources)
238 }
239}
240
241#[derive(Debug)]
243pub struct MissingResourcesError(pub Vec<ResourceType>);
244
245impl std::fmt::Display for MissingResourcesError {
246 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247 write!(f, "missing resources: {:?}", self.0)
248 }
249}
250
251impl std::error::Error for MissingResourcesError {}
252
253#[derive(Debug)]
255pub struct AbsolutePathError(ResourceType, PathBuf);
256
257impl std::fmt::Display for AbsolutePathError {
258 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259 write!(
260 f,
261 "resource {:?} path is not absolute: {:?}",
262 self.0, self.1
263 )
264 }
265}
266
267impl std::error::Error for AbsolutePathError {}
268
269impl Resources {
270 pub fn new(resources: HashMap<ResourceType, PathBuf>) -> Result<Self, AbsolutePathError> {
273 for (resource, path) in &resources {
274 if !path.is_absolute() {
275 return Err(AbsolutePathError(*resource, path.clone()));
276 }
277 }
278
279 Ok(Resources { resources })
280 }
281
282 pub fn resources(&self) -> &HashMap<ResourceType, PathBuf> {
284 &self.resources
285 }
286
287 pub fn get(&self, resource: ResourceType) -> Option<&PathBuf> {
289 self.resources.get(&resource)
290 }
291
292 pub fn check_required(&self, required: &[ResourceType]) -> Result<(), MissingResourcesError> {
295 let mut missing = vec![];
296 for resource in required {
297 if !self.resources.contains_key(resource) {
298 missing.push(*resource);
299 }
300 }
301
302 if missing.is_empty() {
303 Ok(())
304 } else {
305 Err(MissingResourcesError(missing))
306 }
307 }
308}
309
310#[cfg(test)]
311mod test {
312 use super::*;
313
314 #[test]
315 fn non_absolute_path_new() {
316 let mut resources = HashMap::new();
317 resources.insert(ResourceType::Uefi, PathBuf::from("./uefi"));
318 let result = Resources::new(resources);
319 assert!(result.is_err());
320 }
321
322 #[test]
323 fn parse_non_absolute_path() {
324 let resources = r#"{"uefi":"./uefi"}"#;
325 let result: Result<Resources, _> = serde_json::from_str(resources);
326 assert!(result.is_err());
327 }
328
329 #[test]
330 fn missing_resources() {
331 let resources = Resources {
332 resources: HashMap::new(),
333 };
334 let required = vec![ResourceType::Uefi];
335 let result = resources.check_required(&required);
336 assert!(result.is_err());
337 }
338}