runtime_services/
runtime_services.rs

1//! Rust-friendly UEFI Runtime Service Wrappers
2//!
3//! Provides safe and unsafe easy-to-use wrappers for UEFI runtime services, as well as additional
4//! utilities and helper functions.
5//!
6//! ```ignore
7//! pub static RUNTIME_SERVICES: StandardRuntimeServices =
8//!     StandardRuntimeServices::new(&(*runtime_services_ptr));
9//! let variable_services::VariableInfo = RUNTIME_SERVICES.query_variable_info(attributes);
10//! ```
11//!
12
13#![cfg_attr(all(not(test), not(feature = "mockall")), no_std)]
14
15extern crate alloc;
16
17/// Variable-services-specific structs and utilities
18pub mod variable_services;
19
20#[cfg(any(test, feature = "mockall"))]
21use mockall::automock;
22
23use alloc::vec::Vec;
24use core::{
25    ffi::c_void,
26    marker::PhantomData,
27    ptr,
28    sync::atomic::{AtomicPtr, Ordering},
29};
30
31use r_efi::efi;
32use variable_services::{GetVariableStatus, VariableInfo};
33
34/// The UEFI spec runtime services.
35/// It wraps an [`AtomicPtr`] around [`efi::RuntimeServices`]
36///
37/// UEFI Spec Documentation: [8. Services - RuntimeServices](https://uefi.org/specs/UEFI/2.10/08_Services_Runtime_Services.html)
38///
39#[derive(Debug)]
40pub struct StandardRuntimeServices<'a> {
41    efi_runtime_services: AtomicPtr<efi::RuntimeServices>,
42    _lifetime_marker: PhantomData<&'a efi::RuntimeServices>,
43}
44
45impl<'a> StandardRuntimeServices<'a> {
46    /// Create a new StandardRuntimeServices with the provided [efi::RuntimeServices].
47    pub const fn new(efi_runtime_services: &'a efi::RuntimeServices) -> Self {
48        // The efi::RuntimeServices is only read, that is why we use a non mutable reference.
49        Self {
50            efi_runtime_services: AtomicPtr::new(efi_runtime_services as *const _ as *mut _),
51            _lifetime_marker: PhantomData,
52        }
53    }
54
55    /// Create a new StandardRuntimeServices that is uninitialized.
56    /// The struct need to be initialize later with [Self::initialize], otherwise, subsequent call will panic.
57    pub const fn new_uninit() -> Self {
58        Self { efi_runtime_services: AtomicPtr::new(ptr::null_mut()), _lifetime_marker: PhantomData }
59    }
60
61    /// Initialize the StandardRuntimeServices with a reference to [efi::RuntimeServices].
62    /// # Debug asserts
63    /// This function will assert on debug if already initialized.
64    pub fn initialize(&'a self, efi_runtime_services: &'a efi::RuntimeServices) {
65        if self.efi_runtime_services.load(Ordering::Relaxed).is_null() {
66            // The efi::RuntimeServices is only read, that is why we use a non mutable reference.
67            self.efi_runtime_services.store(efi_runtime_services as *const _ as *mut _, Ordering::SeqCst)
68        } else {
69            debug_assert!(false, "Runtime services is already initialized.");
70        }
71    }
72
73    /// # Panics
74    /// This function will panic if it was not initialize.
75    fn efi_runtime_services(&self) -> &efi::RuntimeServices {
76        // SAFETY: This pointer is assume to be a valid efi::RuntimeServices pointer since the only way to set it was via an efi::RuntimeServices reference.
77        unsafe {
78            self.efi_runtime_services
79                .load(Ordering::SeqCst)
80                .as_ref::<'a>()
81                .expect("Runtime services is not initialized.")
82        }
83    }
84}
85
86///SAFETY: StandardRuntimeServices uses an atomic ptr to access the RuntimeServices.
87unsafe impl Sync for StandardRuntimeServices<'static> {}
88///SAFETY: When the lifetime is `'static`, the pointer is guaranteed to stay valid.
89unsafe impl Send for StandardRuntimeServices<'static> {}
90
91#[cfg_attr(any(test, feature = "mockall"), automock)]
92
93/// Interface for Rust-friendly wrappers of the UEFI Runtime Services
94pub trait RuntimeServices: Sized {
95    /// Sets a UEFI variable.
96    ///
97    /// UEFI Spec Documentation: [8.2.3. EFI_RUNTIME_SERVICES.SetVariable()](https://uefi.org/specs/UEFI/2.10/08_Services_Runtime_Services.html#setvariable)
98    ///
99    fn set_variable<T>(&self, name: &[u16], namespace: &efi::Guid, attributes: u32, data: &T) -> Result<(), efi::Status>
100    where
101        T: AsRef<[u8]> + 'static,
102    {
103        if !name.iter().position(|&c| c == 0).is_some() {
104            debug_assert!(false, "Name passed into set_variable is not null-terminated.");
105            return Err(efi::Status::INVALID_PARAMETER);
106        }
107
108        // Keep a local copy of name to unburden the caller of having to pass in a mutable slice
109        let mut name_vec = name.to_vec();
110
111        unsafe { self.set_variable_unchecked(name_vec.as_mut_slice(), namespace, attributes, data.as_ref()) }
112    }
113
114    /// Gets a UEFI variable.
115    ///
116    /// Returns a tuple of (data, attributes)
117    ///
118    /// UEFI Spec Documentation: [8.2.1. EFI_RUNTIME_SERVICES.GetVariable()](https://uefi.org/specs/UEFI/2.10/08_Services_Runtime_Services.html#getvariable)
119    ///
120    fn get_variable<T>(
121        &self,
122        name: &[u16],
123        namespace: &efi::Guid,
124        size_hint: Option<usize>,
125    ) -> Result<(T, u32), efi::Status>
126    where
127        T: TryFrom<Vec<u8>> + 'static,
128    {
129        if !name.iter().position(|&c| c == 0).is_some() {
130            debug_assert!(false, "Name passed into get_variable is not null-terminated.");
131            return Err(efi::Status::INVALID_PARAMETER);
132        }
133
134        // Keep a local copy of name to unburden the caller of having to pass in a mutable slice
135        let mut name_vec = name.to_vec();
136
137        // We can't simply allocate an empty buffer of size T because we can't assume
138        // the TryFrom representation of T will be the same as T
139        let mut data = Vec::<u8>::new();
140        if size_hint.is_some() {
141            data.resize(size_hint.unwrap(), 0);
142        }
143
144        // Do at most two calls to get_variable_unchecked.
145        //
146        // If size_hint was provided (and the size is sufficient), then only call to get_variable_unchecked is
147        // needed. Otherwise, the first check will determine the size of the buffer to allocate for the second
148        // call.
149        let mut first_attempt = true;
150        loop {
151            unsafe {
152                let status = self.get_variable_unchecked(
153                    name_vec.as_mut_slice(),
154                    namespace,
155                    if data.len() == 0 { None } else { Some(&mut data) },
156                );
157
158                match status {
159                    GetVariableStatus::Success { data_size: _, attributes } => match T::try_from(data) {
160                        Ok(d) => return Ok((d, attributes)),
161                        Err(_) => return Err(efi::Status::INVALID_PARAMETER),
162                    },
163                    GetVariableStatus::BufferTooSmall { data_size, attributes: _ } => {
164                        if first_attempt {
165                            first_attempt = false;
166                            data.resize(data_size, 10);
167                        } else {
168                            return Err(efi::Status::BUFFER_TOO_SMALL);
169                        }
170                    }
171                    GetVariableStatus::Error(e) => {
172                        return Err(e);
173                    }
174                }
175            }
176        }
177    }
178
179    /// Helper function to get a UEFI variable's size and attributes
180    fn get_variable_size_and_attributes(
181        &self,
182        name: &[u16],
183        namespace: &efi::Guid,
184    ) -> Result<(usize, u32), efi::Status> {
185        if !name.iter().position(|&c| c == 0).is_some() {
186            debug_assert!(false, "Name passed into set_variable is not null-terminated.");
187            return Err(efi::Status::INVALID_PARAMETER);
188        }
189
190        // Keep a local copy of name to unburden the caller of having to pass in a mutable slice
191        let mut name_vec = name.to_vec();
192
193        unsafe {
194            match self.get_variable_unchecked(name_vec.as_mut_slice(), namespace, None) {
195                GetVariableStatus::BufferTooSmall { data_size, attributes } => Ok((data_size, attributes)),
196                GetVariableStatus::Error(e) => Err(e),
197                GetVariableStatus::Success { data_size, attributes } => {
198                    debug_assert!(false, "GetVariable call with zero-sized buffer returned Success.");
199                    Ok((data_size, attributes))
200                }
201            }
202        }
203    }
204
205    /// Gets the name and namespace of the UEFI variable after the one provided.
206    ///
207    /// Returns a tuple of (name, namespace)
208    ///
209    /// Note: Unlike get_variable, a non-null terminated name will return INVALID_PARAMETER per UEFI spec
210    ///
211    /// UEFI Spec Documentation: [8.2.2. EFI_RUNTIME_SERVICES.GetNextVariableName()](https://uefi.org/specs/UEFI/2.10/08_Services_Runtime_Services.html#getnextvariablename)
212    ///
213    fn get_next_variable_name(
214        &self,
215        prev_name: &[u16],
216        prev_namespace: &efi::Guid,
217    ) -> Result<(Vec<u16>, efi::Guid), efi::Status> {
218        if prev_name.len() == 0 {
219            debug_assert!(false, "Zero-length name passed into get_next_variable_name.");
220            return Err(efi::Status::INVALID_PARAMETER);
221        }
222
223        let mut next_name = Vec::<u16>::new();
224        let mut next_namespace: efi::Guid = efi::Guid::from_bytes(&[0x0; 16]);
225
226        unsafe {
227            self.get_next_variable_name_unchecked(&prev_name, &prev_namespace, &mut next_name, &mut next_namespace)?;
228        };
229
230        Ok((next_name, next_namespace))
231    }
232
233    /// Queries variable information for given UEFI variable attributes.
234    ///
235    /// UEFI Spec Documentation: [8.2.4. EFI_RUNTIME_SERVICES.QueryVariableInfo()](https://uefi.org/specs/UEFI/2.10/08_Services_Runtime_Services.html#queryvariableinfo)
236    ///
237    fn query_variable_info(&self, attributes: u32) -> Result<VariableInfo, efi::Status>;
238
239    /// Set's a UEFI variable
240    ///
241    /// # Safety
242    ///
243    /// Ensure name is null-terminated
244    unsafe fn set_variable_unchecked(
245        &self,
246        name: &mut [u16],
247        namespace: &efi::Guid,
248        attributes: u32,
249        data: &[u8],
250    ) -> Result<(), efi::Status>;
251
252    /// Gets a UEFI variable
253    ///
254    /// # Safety
255    ///
256    /// Ensure name is null-terminated
257    unsafe fn get_variable_unchecked<'a>(
258        &self,
259        name: &mut [u16],
260        namespace: &efi::Guid,
261        data: Option<&'a mut [u8]>,
262    ) -> GetVariableStatus;
263
264    /// Gets the UEFI variable name after the one provided.
265    ///
266    /// Will populate next_name and next_namespace.
267    ///
268    /// # Safety
269    ///
270    /// Ensure name isn't empty. It can be an empty string,
271    /// but there must be some data.
272    ///
273    unsafe fn get_next_variable_name_unchecked(
274        &self,
275        prev_name: &[u16],
276        prev_namespace: &efi::Guid,
277        next_name: &mut Vec<u16>,
278        next_namespace: &mut efi::Guid,
279    ) -> Result<(), efi::Status>;
280}
281
282impl RuntimeServices for StandardRuntimeServices<'_> {
283    unsafe fn set_variable_unchecked(
284        &self,
285        name: &mut [u16],
286        namespace: &efi::Guid,
287        attributes: u32,
288        data: &[u8],
289    ) -> Result<(), efi::Status> {
290        let set_variable = self.efi_runtime_services().set_variable;
291        if set_variable as usize == 0 {
292            debug_assert!(false, "SetVariable has not initialized in the Runtime Services Table.");
293            return Err(efi::Status::NOT_FOUND);
294        }
295
296        let status = set_variable(
297            name.as_mut_ptr(),
298            namespace as *const _ as *mut _,
299            attributes,
300            data.len(),
301            data.as_ptr() as *mut c_void,
302        );
303
304        if status.is_error() {
305            Err(status)
306        } else {
307            Ok(())
308        }
309    }
310
311    unsafe fn get_variable_unchecked(
312        &self,
313        name: &mut [u16],
314        namespace: &efi::Guid,
315        data: Option<&mut [u8]>,
316    ) -> GetVariableStatus {
317        let get_variable = self.efi_runtime_services().get_variable;
318        if get_variable as usize == 0 {
319            debug_assert!(false, "GetVariable has not initialized in the Runtime Services Table.");
320            return GetVariableStatus::Error(efi::Status::NOT_FOUND);
321        }
322
323        let mut data_size: usize = match data {
324            Some(ref d) => d.len(),
325            None => 0,
326        };
327        let mut attributes: u32 = 0;
328
329        let status = get_variable(
330            name.as_mut_ptr(),
331            namespace as *const _ as *mut _,
332            ptr::addr_of_mut!(attributes),
333            ptr::addr_of_mut!(data_size),
334            match data {
335                Some(d) => d.as_ptr() as *mut c_void,
336                None => ptr::null_mut() as *mut c_void,
337            },
338        );
339
340        if status == efi::Status::BUFFER_TOO_SMALL {
341            return GetVariableStatus::BufferTooSmall { data_size: data_size, attributes: attributes };
342        } else if status.is_error() {
343            return GetVariableStatus::Error(status);
344        }
345
346        GetVariableStatus::Success { data_size: data_size, attributes: attributes }
347    }
348
349    unsafe fn get_next_variable_name_unchecked(
350        &self,
351        prev_name: &[u16],
352        prev_namespace: &efi::Guid,
353        next_name: &mut Vec<u16>,
354        next_namespace: &mut efi::Guid,
355    ) -> Result<(), efi::Status> {
356        let get_next_variable_name = self.efi_runtime_services().get_next_variable_name;
357        if get_next_variable_name as usize == 0 {
358            debug_assert!(false, "GetNextVariableName has not initialized in the Runtime Services Table.");
359            return Err(efi::Status::NOT_FOUND);
360        }
361
362        // Copy prev_name and namespace into next name and namespace
363        if next_name.len() < prev_name.len() {
364            next_name.resize(prev_name.len(), 0);
365        }
366        next_name[..prev_name.len()].clone_from_slice(prev_name);
367        next_namespace.clone_from(prev_namespace);
368
369        let mut next_name_size: usize = next_name.len();
370
371        // Loop at most two times. If the size of the previous name is sufficient for the next, then only
372        // one call to the EFI function will be made. Otherwise, the first call will be used to determine
373        // the appropriate size that the buffer should be resized to for the second call.
374        let mut first_try: bool = true;
375        loop {
376            let status =
377                get_next_variable_name(ptr::addr_of_mut!(next_name_size), next_name.as_mut_ptr(), next_namespace);
378
379            if status == efi::Status::BUFFER_TOO_SMALL && first_try {
380                first_try = false;
381
382                assert!(
383                    next_name_size > next_name.len(),
384                    "get_next_variable_name requested smaller buffer on BUFFER_TOO_SMALL."
385                );
386
387                // Resize name to be able to fit the size of the next name
388                next_name.resize(next_name_size, 0);
389
390                // Reset fields which may have been overwritten
391                next_name[..prev_name.len()].clone_from_slice(prev_name);
392                next_namespace.clone_from(prev_namespace);
393            } else if status.is_error() {
394                return Err(status);
395            } else {
396                return Ok(());
397            }
398        }
399    }
400
401    fn query_variable_info(&self, attributes: u32) -> Result<VariableInfo, efi::Status> {
402        let query_variable_info = self.efi_runtime_services().query_variable_info;
403        if query_variable_info as usize == 0 {
404            debug_assert!(false, "QueryVariableInfo has not initialized in the Runtime Services Table.");
405            return Err(efi::Status::NOT_FOUND);
406        }
407
408        let mut var_info = VariableInfo {
409            maximum_variable_storage_size: 0,
410            remaining_variable_storage_size: 0,
411            maximum_variable_size: 0,
412        };
413
414        let status = query_variable_info(
415            attributes,
416            ptr::addr_of_mut!(var_info.maximum_variable_storage_size),
417            ptr::addr_of_mut!(var_info.remaining_variable_storage_size),
418            ptr::addr_of_mut!(var_info.maximum_variable_size),
419        );
420
421        if status.is_error() {
422            return Err(status);
423        } else {
424            return Ok(var_info);
425        }
426    }
427}
428
429#[cfg(test)]
430pub(crate) mod test {
431    use efi;
432
433    use super::*;
434    use core::{mem, slice};
435
436    macro_rules! runtime_services {
437        ($($efi_services:ident = $efi_service_fn:ident),*) => {{
438        static RUNTIME_SERVICE: StandardRuntimeServices = StandardRuntimeServices::new_uninit();
439        let efi_runtime_services = unsafe {
440            #[allow(unused_mut)]
441            let mut rs = mem::MaybeUninit::<efi::RuntimeServices>::zeroed();
442            $(
443            rs.assume_init_mut().$efi_services = $efi_service_fn;
444            )*
445            rs.assume_init()
446        };
447        RUNTIME_SERVICE.initialize(&efi_runtime_services);
448        &RUNTIME_SERVICE
449        }};
450    }
451
452    pub(crate) use runtime_services;
453
454    #[test]
455    #[should_panic(expected = "Runtime services is not initialized.")]
456    fn test_that_accessing_uninit_runtime_services_should_panic() {
457        let rs = StandardRuntimeServices::new_uninit();
458        rs.efi_runtime_services();
459    }
460
461    #[test]
462    #[should_panic(expected = "Runtime services is already initialized.")]
463    fn test_that_initializing_runtime_services_multiple_time_should_panic() {
464        let efi_rs = unsafe { mem::MaybeUninit::<efi::RuntimeServices>::zeroed().as_ptr().as_ref().unwrap() };
465        let rs = StandardRuntimeServices::new_uninit();
466        rs.initialize(efi_rs);
467        rs.initialize(efi_rs);
468    }
469
470    pub const DUMMY_FIRST_NAME: [u16; 3] = [0x1000, 0x1020, 0x0000];
471    pub const DUMMY_NON_NULL_TERMINATED_NAME: [u16; 3] = [0x1000, 0x1020, 0x1040];
472    pub const DUMMY_EMPTY_NAME: [u16; 1] = [0x0000];
473    pub const DUMMY_ZERO_LENGTH_NAME: [u16; 0] = [];
474    pub const DUMMY_SECOND_NAME: [u16; 5] = [0x1001, 0x1022, 0x1043, 0x1064, 0x0000];
475    pub const DUMMY_UNKNOWN_NAME: [u16; 3] = [0x2000, 0x2020, 0x0000];
476
477    pub const DUMMY_NODE: [u8; 6] = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0];
478    pub const DUMMY_FIRST_NAMESPACE: efi::Guid = efi::Guid::from_fields(0, 0, 0, 0, 0, &DUMMY_NODE);
479    pub const DUMMY_SECOND_NAMESPACE: efi::Guid = efi::Guid::from_fields(1, 0, 0, 0, 0, &DUMMY_NODE);
480
481    pub const DUMMY_ATTRIBUTES: u32 = 0x1234;
482    pub const DUMMY_INVALID_ATTRIBUTES: u32 = 0x2345;
483
484    pub const DUMMY_DATA: u32 = 0xDEADBEEF;
485    pub const DUMMY_DATA_REPR_SIZE: usize = mem::size_of::<u32>();
486
487    pub const DUMMY_MAXIMUM_VARIABLE_STORAGE_SIZE: u64 = 0x11111111_11111111;
488    pub const DUMMY_REMAINING_VARIABLE_STORAGE_SIZE: u64 = 0x22222222_22222222;
489    pub const DUMMY_MAXIMUM_VARIABLE_SIZE: u64 = 0x33333333_33333333;
490
491    #[derive(Debug)]
492    pub struct DummyVariableType {
493        pub value: u32,
494    }
495
496    impl AsRef<[u8]> for DummyVariableType {
497        fn as_ref(&self) -> &[u8] {
498            unsafe { slice::from_raw_parts::<u8>(ptr::addr_of!(self.value) as *mut u8, mem::size_of::<u32>()) }
499        }
500    }
501
502    impl TryFrom<Vec<u8>> for DummyVariableType {
503        type Error = &'static str;
504
505        fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
506            assert!(value.len() == mem::size_of::<u32>());
507
508            Ok(DummyVariableType { value: u32::from_ne_bytes(value[0..4].try_into().unwrap()) })
509        }
510    }
511
512    /// Mocks GetVariable() from UEFI spec
513    ///
514    /// Expects to be passed DUMMY_FIRST_NAME, DUMMY_FIRST_NAMESPACE, and to return
515    /// DUMMY_ATTRIBUTES, and DUMMY_DATA.
516    ///
517    /// DUMMY_UNKNOWN_NAME can be passed in to test searching for non-existant variables.
518    ///
519    pub extern "efiapi" fn mock_efi_get_variable(
520        name: *mut u16,
521        namespace: *mut efi::Guid,
522        attributes: *mut u32,
523        data_size: *mut usize,
524        data: *mut c_void,
525    ) -> efi::Status {
526        unsafe {
527            if DUMMY_UNKNOWN_NAME.iter().enumerate().all(|(i, &c)| *name.offset(i as isize) == c) {
528                return efi::Status::NOT_FOUND;
529            }
530
531            // Since name isn't DUMMY_UNKNOWN_NAME, we're assuming DUMMY_FIRST_NAME was passed in.
532            // If name is not equal to DUMMY_FIRST_NAME, then something must have gone wrong.
533            assert_eq!(
534                DUMMY_FIRST_NAME.iter().enumerate().all(|(i, &c)| *name.offset(i as isize) == c),
535                true,
536                "Variable name does not match expected."
537            );
538
539            assert_eq!(*namespace, DUMMY_FIRST_NAMESPACE);
540
541            *attributes = DUMMY_ATTRIBUTES;
542
543            if *data_size < DUMMY_DATA_REPR_SIZE {
544                *data_size = DUMMY_DATA_REPR_SIZE;
545                return efi::Status::BUFFER_TOO_SMALL;
546            }
547
548            *data_size = DUMMY_DATA_REPR_SIZE;
549            *(data as *mut u32) = DUMMY_DATA;
550        }
551
552        efi::Status::SUCCESS
553    }
554
555    /// Mocks SetVariable() from UEFI spec
556    ///
557    /// Expects to be passed DUMMY_FIRST_NAME, DUMMY_FIRST_NAMESPACE, and DUMMY_DATA
558    ///
559    /// DUMMY_UNKNOWN_NAME can be passed in to test searching for non-existant variables.
560    ///
561    pub extern "efiapi" fn mock_efi_set_variable(
562        name: *mut u16,
563        namespace: *mut efi::Guid,
564        attributes: u32,
565        data_size: usize,
566        data: *mut c_void,
567    ) -> efi::Status {
568        unsafe {
569            // Invalid parameter is returned if name is empty (first character is 0)
570            if *name == 0 {
571                return efi::Status::INVALID_PARAMETER;
572            }
573
574            if DUMMY_UNKNOWN_NAME.iter().enumerate().all(|(i, &c)| *name.offset(i as isize) == c) {
575                return efi::Status::NOT_FOUND;
576            }
577
578            // Since name isn't DUMMY_UNKNOWN_NAME, we're assuming DUMMY_FIRST_NAME was passed in.
579            // If name is not equal to DUMMY_FIRST_NAME, then something must have gone wrong.
580            assert_eq!(
581                DUMMY_FIRST_NAME.iter().enumerate().all(|(i, &c)| *name.offset(i as isize) == c),
582                true,
583                "Variable name does not match expected."
584            );
585
586            assert_eq!(*namespace, DUMMY_FIRST_NAMESPACE);
587            assert_eq!(attributes, DUMMY_ATTRIBUTES);
588            assert_eq!(data_size, DUMMY_DATA_REPR_SIZE);
589            assert_eq!(*(data as *mut u32), DUMMY_DATA);
590        }
591
592        efi::Status::SUCCESS
593    }
594
595    /// Mocks GetNextVariableName() from UEFI spec
596    ///
597    /// Will mock a list of two variables:
598    ///     1. DUMMY_FIRST_NAME (under namespace DUMMY_FIRST_NAMESPACE)
599    ///     2. DUMMY_SECOND_NAME (under namespace DUMMY_SECOND_NAME)
600    ///
601    /// DUMMY_UNKNOWN_NAME can be passed in to test searching for non-existant variables.
602    ///
603    pub extern "efiapi" fn mock_efi_get_next_variable_name(
604        name_size: *mut usize,
605        name: *mut u16,
606        namespace: *mut efi::Guid,
607    ) -> efi::Status {
608        // Ensure the name and namespace are as expected
609        unsafe {
610            // Return invalid parameter if the name isn't null-terminated per UEFI spec
611            if !slice::from_raw_parts(name, *name_size).iter().position(|&c| c == 0).is_some() {
612                return efi::Status::INVALID_PARAMETER;
613            }
614
615            if DUMMY_UNKNOWN_NAME.iter().enumerate().all(|(i, &c)| *name.offset(i as isize) == c) {
616                return efi::Status::NOT_FOUND;
617            }
618
619            // If name is an empty string, return the first variable
620            if *name == 0 {
621                if *name_size < DUMMY_FIRST_NAME.len() {
622                    *name_size = DUMMY_FIRST_NAME.len();
623                    return efi::Status::BUFFER_TOO_SMALL;
624                }
625
626                *name_size = DUMMY_FIRST_NAME.len();
627                ptr::copy_nonoverlapping(DUMMY_FIRST_NAME.as_ptr(), name, DUMMY_FIRST_NAME.len());
628                *namespace = DUMMY_FIRST_NAMESPACE;
629
630                return efi::Status::SUCCESS;
631            }
632
633            // If the first variable is passed in, return the second
634            if DUMMY_FIRST_NAME.iter().enumerate().all(|(i, &c)| *name.offset(i as isize) == c) {
635                assert_eq!(*namespace, DUMMY_FIRST_NAMESPACE);
636
637                if *name_size < DUMMY_SECOND_NAME.len() {
638                    *name_size = DUMMY_SECOND_NAME.len();
639                    return efi::Status::BUFFER_TOO_SMALL;
640                }
641
642                *name_size = DUMMY_SECOND_NAME.len();
643                ptr::copy_nonoverlapping(DUMMY_SECOND_NAME.as_ptr(), name, DUMMY_SECOND_NAME.len());
644                *namespace = DUMMY_SECOND_NAMESPACE;
645
646                return efi::Status::SUCCESS;
647            }
648
649            // If the second (and last) variable is passed in, return NOT_FOUND to indicate the end of the list per
650            // UEFI spec
651            if DUMMY_SECOND_NAME.iter().enumerate().all(|(i, &c)| *name.offset(i as isize) == c) {
652                assert_eq!(*namespace, DUMMY_SECOND_NAMESPACE);
653                return efi::Status::NOT_FOUND;
654            }
655
656            // If we got here, the variable name must have gotten lost or corrupted somehow
657            assert!(false, "Variable name does not match any of expected.");
658        }
659
660        efi::Status::SUCCESS
661    }
662
663    /// Mocks QueryVariableInfo() from UEFI spec
664    ///
665    /// Expects to be passed DUMMY_ATTRIBUTES, and to return, DUMMY_MAXIMUM_VARIABLE_STORAGE_SIZE,
666    /// DUMMY_REMAINING_VARIABLE_STORAGE_SIZE, and DUMMY_MAXIMUM_VARIABLE_SIZE.
667    ///
668    /// DUMMY_INVALID_ATTRIBUTES can be passed in to test querying invalid attributes.
669    ///
670    pub extern "efiapi" fn mock_efi_query_variable_info(
671        attributes: u32,
672        maximum_variable_storage_size: *mut u64,
673        remaining_variable_storage_size: *mut u64,
674        maximum_variable_size: *mut u64,
675    ) -> efi::Status {
676        if attributes == DUMMY_INVALID_ATTRIBUTES {
677            return efi::Status::INVALID_PARAMETER;
678        }
679
680        // Since attributes isn't DUMMY_INVALID_ATTRIBUTES, we're assuming DUMMY_ATTRIBUTES was passed in.
681        // If attributes is not equal to DUMMY_ATTRIBUTES, then something must have gone wrong.
682        assert_eq!(attributes, DUMMY_ATTRIBUTES);
683
684        unsafe {
685            *maximum_variable_storage_size = DUMMY_MAXIMUM_VARIABLE_STORAGE_SIZE;
686            *remaining_variable_storage_size = DUMMY_REMAINING_VARIABLE_STORAGE_SIZE;
687            *maximum_variable_size = DUMMY_MAXIMUM_VARIABLE_SIZE;
688        }
689
690        efi::Status::SUCCESS
691    }
692
693    #[test]
694    fn test_get_variable() {
695        let rs: &StandardRuntimeServices<'_> = runtime_services!(get_variable = mock_efi_get_variable);
696
697        let status = rs.get_variable::<DummyVariableType>(&DUMMY_FIRST_NAME, &DUMMY_FIRST_NAMESPACE, None);
698
699        assert!(status.is_ok());
700        let (data, attributes) = status.unwrap();
701        assert_eq!(attributes, DUMMY_ATTRIBUTES);
702        assert_eq!(data.value, DUMMY_DATA);
703    }
704
705    #[test]
706    #[should_panic(expected = "Name passed into get_variable is not null-terminated.")]
707    fn test_get_variable_non_terminated() {
708        let rs: &StandardRuntimeServices<'_> = runtime_services!(get_variable = mock_efi_get_variable);
709
710        let _ = rs.get_variable::<DummyVariableType>(&DUMMY_NON_NULL_TERMINATED_NAME, &DUMMY_FIRST_NAMESPACE, None);
711    }
712
713    #[test]
714    fn test_get_variable_low_size_hint() {
715        let rs: &StandardRuntimeServices<'_> = runtime_services!(get_variable = mock_efi_get_variable);
716
717        let status = rs.get_variable::<DummyVariableType>(&DUMMY_FIRST_NAME, &DUMMY_FIRST_NAMESPACE, Some(1));
718
719        assert!(status.is_ok());
720        let (data, attributes) = status.unwrap();
721        assert_eq!(attributes, DUMMY_ATTRIBUTES);
722        assert_eq!(data.value, DUMMY_DATA);
723    }
724
725    #[test]
726    fn test_get_variable_not_found() {
727        let rs: &StandardRuntimeServices<'_> = runtime_services!(get_variable = mock_efi_get_variable);
728
729        let status = rs.get_variable::<DummyVariableType>(&DUMMY_UNKNOWN_NAME, &DUMMY_FIRST_NAMESPACE, Some(1));
730
731        assert!(status.is_err());
732        assert_eq!(status.unwrap_err(), efi::Status::NOT_FOUND);
733    }
734
735    #[test]
736    fn test_get_variable_size_and_attributes() {
737        let rs: &StandardRuntimeServices<'_> = runtime_services!(get_variable = mock_efi_get_variable);
738
739        let status = rs.get_variable_size_and_attributes(&DUMMY_FIRST_NAME, &DUMMY_FIRST_NAMESPACE);
740
741        assert!(status.is_ok());
742        let (size, attributes) = status.unwrap();
743        assert_eq!(size, DUMMY_DATA_REPR_SIZE);
744        assert_eq!(attributes, DUMMY_ATTRIBUTES);
745    }
746
747    #[test]
748    fn test_set_variable() {
749        let rs: &StandardRuntimeServices<'_> = runtime_services!(set_variable = mock_efi_set_variable);
750
751        let mut data = DummyVariableType { value: DUMMY_DATA };
752
753        let status = rs.set_variable::<DummyVariableType>(
754            &DUMMY_FIRST_NAME,
755            &DUMMY_FIRST_NAMESPACE,
756            DUMMY_ATTRIBUTES,
757            &mut data,
758        );
759
760        assert!(status.is_ok());
761    }
762
763    #[test]
764    #[should_panic(expected = "Name passed into set_variable is not null-terminated.")]
765    fn test_set_variable_non_terminated() {
766        let rs: &StandardRuntimeServices<'_> = runtime_services!(set_variable = mock_efi_set_variable);
767
768        let mut data = DummyVariableType { value: DUMMY_DATA };
769
770        let _ = rs.set_variable::<DummyVariableType>(
771            &DUMMY_NON_NULL_TERMINATED_NAME,
772            &DUMMY_FIRST_NAMESPACE,
773            DUMMY_ATTRIBUTES,
774            &mut data,
775        );
776    }
777
778    #[test]
779    fn test_set_variable_empty_name() {
780        let rs: &StandardRuntimeServices<'_> = runtime_services!(set_variable = mock_efi_set_variable);
781
782        let mut data = DummyVariableType { value: DUMMY_DATA };
783
784        let status = rs.set_variable::<DummyVariableType>(
785            &DUMMY_EMPTY_NAME,
786            &DUMMY_FIRST_NAMESPACE,
787            DUMMY_ATTRIBUTES,
788            &mut data,
789        );
790
791        assert!(status.is_err());
792        assert_eq!(status.unwrap_err(), efi::Status::INVALID_PARAMETER);
793    }
794
795    #[test]
796    fn test_set_variable_not_found() {
797        let rs: &StandardRuntimeServices<'_> = runtime_services!(set_variable = mock_efi_set_variable);
798
799        let mut data = DummyVariableType { value: DUMMY_DATA };
800
801        let status = rs.set_variable::<DummyVariableType>(
802            &DUMMY_UNKNOWN_NAME,
803            &DUMMY_FIRST_NAMESPACE,
804            DUMMY_ATTRIBUTES,
805            &mut data,
806        );
807
808        assert!(status.is_err());
809        assert_eq!(status.unwrap_err(), efi::Status::NOT_FOUND);
810    }
811
812    #[test]
813    fn test_get_next_variable_name() {
814        // Ensure we are testing a growing name buffer
815        assert!(DUMMY_SECOND_NAME.len() > DUMMY_FIRST_NAME.len());
816
817        let rs: &StandardRuntimeServices<'_> =
818            runtime_services!(get_next_variable_name = mock_efi_get_next_variable_name);
819
820        let status = rs.get_next_variable_name(&DUMMY_FIRST_NAME, &DUMMY_FIRST_NAMESPACE);
821
822        assert!(status.is_ok());
823
824        let (next_name, next_guid) = status.unwrap();
825
826        assert_eq!(next_name, DUMMY_SECOND_NAME);
827        assert_eq!(next_guid, DUMMY_SECOND_NAMESPACE);
828    }
829
830    #[test]
831    fn test_get_next_variable_name_non_terminated() {
832        let rs: &StandardRuntimeServices<'_> =
833            runtime_services!(get_next_variable_name = mock_efi_get_next_variable_name);
834
835        let status = rs.get_next_variable_name(&DUMMY_NON_NULL_TERMINATED_NAME, &DUMMY_FIRST_NAMESPACE);
836
837        assert!(status.is_err());
838        assert_eq!(status.unwrap_err(), efi::Status::INVALID_PARAMETER);
839    }
840
841    #[test]
842    #[should_panic(expected = "Zero-length name passed into get_next_variable_name.")]
843    fn test_get_next_variable_name_zero_length_name() {
844        let rs: &StandardRuntimeServices<'_> =
845            runtime_services!(get_next_variable_name = mock_efi_get_next_variable_name);
846
847        let _ = rs.get_next_variable_name(&DUMMY_ZERO_LENGTH_NAME, &DUMMY_FIRST_NAMESPACE);
848    }
849
850    #[test]
851    fn test_get_next_variable_name_not_found() {
852        let rs: &StandardRuntimeServices<'_> =
853            runtime_services!(get_next_variable_name = mock_efi_get_next_variable_name);
854
855        let status = rs.get_next_variable_name(&DUMMY_UNKNOWN_NAME, &DUMMY_FIRST_NAMESPACE);
856
857        assert!(status.is_err());
858        assert_eq!(status.unwrap_err(), efi::Status::NOT_FOUND);
859    }
860
861    #[test]
862    fn test_query_variable_info() {
863        let rs: &StandardRuntimeServices<'_> = runtime_services!(query_variable_info = mock_efi_query_variable_info);
864
865        let status = rs.query_variable_info(DUMMY_ATTRIBUTES);
866
867        assert!(status.is_ok());
868        let variable_info = status.unwrap();
869        assert_eq!(variable_info.maximum_variable_storage_size, DUMMY_MAXIMUM_VARIABLE_STORAGE_SIZE);
870        assert_eq!(variable_info.remaining_variable_storage_size, DUMMY_REMAINING_VARIABLE_STORAGE_SIZE);
871        assert_eq!(variable_info.maximum_variable_size, DUMMY_MAXIMUM_VARIABLE_SIZE);
872    }
873
874    #[test]
875    fn test_query_variable_info_invalid_attributes() {
876        let rs: &StandardRuntimeServices<'_> = runtime_services!(query_variable_info = mock_efi_query_variable_info);
877
878        let status = rs.query_variable_info(DUMMY_INVALID_ATTRIBUTES);
879
880        assert!(status.is_err());
881        assert_eq!(status.unwrap_err(), efi::Status::INVALID_PARAMETER);
882    }
883}