1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use super::NvramError;
use super::NvramResult;
use super::NvramSpecServices;
use guid::Guid;
use ucs2::Ucs2LeSlice;
use uefi_nvram_storage::InspectableNvramStorage;
use uefi_specs::uefi::common::EfiStatus;

/// Extension trait around `NvramServices` that makes it easier to use the API
/// outside the context of the UEFI device.
///
/// This trait provides various helper methods that make it easier to get/set
/// nvram variables without worrying about the nitty-gritty details of UCS-2
/// string encoding, pointer sizes/nullness, etc...
#[async_trait::async_trait]
pub trait NvramServicesExt {
    /// Get a variable identified by `name` (as a Rust string) + `vendor`,
    /// returning the variable's attributes and data.
    #[allow(dead_code)] // Useful for debugging
    async fn get_variable(
        &mut self,
        vendor: Guid,
        name: &str,
    ) -> Result<(u32, Vec<u8>), (EfiStatus, Option<NvramError>)>;

    /// Get a variable identified by `name` (as a UCS-2 string) + `vendor`,
    /// returning the variable's attributes and data.
    #[allow(dead_code)] // Useful for debugging
    async fn get_variable_ucs2(
        &mut self,
        vendor: Guid,
        name: &Ucs2LeSlice,
    ) -> Result<(u32, Vec<u8>), (EfiStatus, Option<NvramError>)>;

    /// Set a variable identified by `name` (as a Rust string) + `vendor` with
    /// the specified `attr` and `data`.
    async fn set_variable(
        &mut self,
        vendor: Guid,
        name: &str,
        attr: u32,
        data: Vec<u8>,
    ) -> Result<(), (EfiStatus, Option<NvramError>)>;

    /// Set a variable identified by `name` (as a UCS-2 string) + `vendor` with
    /// the specified `attr` and `data`.
    async fn set_variable_ucs2(
        &mut self,
        vendor: Guid,
        name: &Ucs2LeSlice,
        attr: u32,
        data: Vec<u8>,
    ) -> Result<(), (EfiStatus, Option<NvramError>)>;
}

#[async_trait::async_trait]
impl<S: InspectableNvramStorage> NvramServicesExt for NvramSpecServices<S> {
    async fn get_variable(
        &mut self,
        vendor: Guid,
        name: &str,
    ) -> Result<(u32, Vec<u8>), (EfiStatus, Option<NvramError>)> {
        let name = ucs2::Ucs2LeVec::from(name);
        self.get_variable_ucs2(vendor, &name).await
    }

    async fn get_variable_ucs2(
        &mut self,
        vendor: Guid,
        name: &Ucs2LeSlice,
    ) -> Result<(u32, Vec<u8>), (EfiStatus, Option<NvramError>)> {
        let mut attr = 0;
        // the low level UEFI APIs includes the `in_out_data_size` parameter so
        // that it can perform validation logic to ensure the user-provided
        // buffer is large enough to store the variable data.
        //
        // this validation logic isn't relevant to the high level API, as
        // consumers can simply use the returned Rust reference directly
        // (without having to copy it into another buffer).
        let mut in_out_data_size = u32::MAX;
        let NvramResult(data, status, err) = self
            .uefi_get_variable(
                Some(name.as_bytes()),
                vendor,
                &mut attr,
                &mut in_out_data_size,
                false,
            )
            .await;

        if matches!(status, EfiStatus::SUCCESS) {
            Ok((attr, data.expect("data will not be None on EFI_SUCCESS")))
        } else {
            Err((status, err))
        }
    }

    async fn set_variable(
        &mut self,
        vendor: Guid,
        name: &str,
        attr: u32,
        data: Vec<u8>,
    ) -> Result<(), (EfiStatus, Option<NvramError>)> {
        let name = ucs2::Ucs2LeVec::from(name);
        self.set_variable_ucs2(vendor, &name, attr, data).await
    }

    async fn set_variable_ucs2(
        &mut self,
        vendor: Guid,
        name: &Ucs2LeSlice,
        attr: u32,
        data: Vec<u8>,
    ) -> Result<(), (EfiStatus, Option<NvramError>)> {
        let NvramResult((), status, err) = self
            .uefi_set_variable(
                Some(name.as_bytes()),
                vendor,
                attr,
                data.len() as u32,
                Some(data),
            )
            .await;

        if matches!(status, EfiStatus::SUCCESS) {
            Ok(())
        } else {
            Err((status, err))
        }
    }
}