1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ExecutionEnvironment {
9 Baremetal,
11 Nested,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Vendor {
18 Amd,
20 Intel,
22 Arm,
24}
25
26#[derive(Clone, Copy, Debug, PartialEq)]
28pub enum IsolationType {
29 Vbs,
31 Snp,
33 Tdx,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum VmmType {
40 OpenVmm,
42 HyperV,
44}
45
46#[derive(Debug, Clone)]
48pub struct VmHostInfo {
49 pub vbs_supported: bool,
51 pub snp_status: bool,
53 pub tdx_status: bool,
55}
56
57#[derive(Debug, Clone)]
59pub struct HostContext {
60 pub vm_host_info: Option<VmHostInfo>,
62 pub vendor: Vendor,
64 pub execution_environment: ExecutionEnvironment,
66 pub vpci_supported: bool,
68}
69
70impl HostContext {
71 pub async fn new() -> Self {
73 let is_nested = {
74 #[cfg(target_arch = "x86_64")]
76 {
77 let result = safe_intrinsics::cpuid(
78 hvdef::HV_CPUID_FUNCTION_MS_HV_ENLIGHTENMENT_INFORMATION,
79 0,
80 );
81 hvdef::HvEnlightenmentInformation::from(
82 result.eax as u128
83 | (result.ebx as u128) << 32
84 | (result.ecx as u128) << 64
85 | (result.edx as u128) << 96,
86 )
87 .nested()
88 }
89 #[cfg(not(target_arch = "x86_64"))]
91 {
92 false
93 }
94 };
95
96 let vendor = {
97 #[cfg(target_arch = "x86_64")]
99 {
100 let result = safe_intrinsics::cpuid(
101 x86defs::cpuid::CpuidFunction::VendorAndMaxFunction.0,
102 0,
103 );
104 if x86defs::cpuid::Vendor::from_ebx_ecx_edx(result.ebx, result.ecx, result.edx)
105 .is_amd_compatible()
106 {
107 Vendor::Amd
108 } else {
109 assert!(
110 x86defs::cpuid::Vendor::from_ebx_ecx_edx(
111 result.ebx, result.ecx, result.edx
112 )
113 .is_intel_compatible()
114 );
115 Vendor::Intel
116 }
117 }
118 #[cfg(not(target_arch = "x86_64"))]
120 {
121 Vendor::Arm
122 }
123 };
124
125 let vm_host_info = {
126 #[cfg(windows)]
127 {
128 crate::vm::hyperv::powershell::run_get_vm_host()
129 .await
130 .ok()
131 .map(|info| VmHostInfo {
132 vbs_supported: info.guest_isolation_types.contains(
133 &crate::vm::hyperv::powershell::HyperVGuestStateIsolationType::Vbs,
134 ),
135 snp_status: info.snp_status,
136 tdx_status: info.tdx_status,
137 })
138 }
139 #[cfg(not(windows))]
140 {
141 None
142 }
143 };
144
145 let vpci_supported = cfg!(windows);
147
148 Self {
149 vm_host_info,
150 vendor,
151 execution_environment: if is_nested {
152 ExecutionEnvironment::Nested
153 } else {
154 ExecutionEnvironment::Baremetal
155 },
156 vpci_supported,
157 }
158 }
159}
160
161pub enum TestRequirement {
163 ExecutionEnvironment(ExecutionEnvironment),
165 Vendor(Vendor),
167 Isolation(IsolationType),
169 VpciSupport,
172 And(Box<TestRequirement>, Box<TestRequirement>),
174 Or(Box<TestRequirement>, Box<TestRequirement>),
176 Not(Box<TestRequirement>),
178 Any,
180}
181
182impl TestRequirement {
183 pub fn and(self, other: TestRequirement) -> TestRequirement {
185 TestRequirement::And(Box::new(self), Box::new(other))
186 }
187
188 pub fn or(self, other: TestRequirement) -> TestRequirement {
190 TestRequirement::Or(Box::new(self), Box::new(other))
191 }
192
193 #[expect(clippy::should_implement_trait)]
195 pub fn not(self) -> TestRequirement {
196 TestRequirement::Not(Box::new(self))
197 }
198
199 pub fn is_satisfied(&self, context: &HostContext) -> bool {
201 match self {
202 TestRequirement::ExecutionEnvironment(env) => context.execution_environment == *env,
203 TestRequirement::Vendor(vendor) => context.vendor == *vendor,
204 TestRequirement::Isolation(isolation_type) => {
205 if let Some(vm_host_info) = &context.vm_host_info {
206 match isolation_type {
207 IsolationType::Vbs => vm_host_info.vbs_supported,
208 IsolationType::Snp => vm_host_info.snp_status,
209 IsolationType::Tdx => vm_host_info.tdx_status,
210 }
211 } else {
212 false
213 }
214 }
215 TestRequirement::VpciSupport => context.vpci_supported,
216 TestRequirement::And(req1, req2) => {
217 req1.is_satisfied(context) && req2.is_satisfied(context)
218 }
219 TestRequirement::Or(req1, req2) => {
220 req1.is_satisfied(context) || req2.is_satisfied(context)
221 }
222 TestRequirement::Not(req) => !req.is_satisfied(context),
223 TestRequirement::Any => true,
224 }
225 }
226}
227
228#[derive(Debug, Clone)]
230pub struct TestEvaluationResult {
231 pub test_name: String,
233 pub can_run: bool,
235}
236
237impl TestEvaluationResult {
238 pub fn new(test_name: &str) -> Self {
240 Self {
241 test_name: test_name.to_string(),
242 can_run: true,
243 }
244 }
245}
246
247pub struct TestCaseRequirements {
249 requirements: TestRequirement,
250}
251
252impl TestCaseRequirements {
253 pub fn new(requirements: TestRequirement) -> Self {
255 Self { requirements }
256 }
257}
258
259pub fn can_run_test_with_context(
261 config: Option<&TestCaseRequirements>,
262 context: &HostContext,
263) -> bool {
264 if let Some(config) = config {
265 config.requirements.is_satisfied(context)
266 } else {
267 true
268 }
269}