uefi 0.19.0

Safe and easy-to-use wrapper for building UEFI apps.
Documentation
use super::{Error, Result};
use core::fmt::Debug;

/// Bit indicating that an UEFI status code is an error
const ERROR_BIT: usize = 1 << (core::mem::size_of::<usize>() * 8 - 1);

newtype_enum! {
/// UEFI uses status codes in order to report successes, errors, and warnings.
///
/// Unfortunately, the spec allows and encourages implementation-specific
/// non-portable status codes. Therefore, these cannot be modeled as a Rust
/// enum, as injecting an unknown value in a Rust enum is undefined behaviour.
///
/// For lack of a better option, we therefore model them as a newtype of usize.
///
/// For a convenient integration into the Rust ecosystem, there are multiple
/// factory methods to convert a Status into a [`uefi::Result`]:
/// - [`Status::into_with`]
/// - [`Status::into_with_val`]
/// - [`Status::into_with_err`]
#[must_use]
pub enum Status: usize => {
    /// The operation completed successfully.
    SUCCESS                 =  0,

    /// The string contained characters that could not be rendered and were skipped.
    WARN_UNKNOWN_GLYPH      =  1,
    /// The handle was closed, but the file was not deleted.
    WARN_DELETE_FAILURE     =  2,
    /// The handle was closed, but the data to the file was not flushed properly.
    WARN_WRITE_FAILURE      =  3,
    /// The resulting buffer was too small, and the data was truncated.
    WARN_BUFFER_TOO_SMALL   =  4,
    /// The data has not been updated within the timeframe set by local policy.
    WARN_STALE_DATA         =  5,
    /// The resulting buffer contains UEFI-compliant file system.
    WARN_FILE_SYSTEM        =  6,
    /// The operation will be processed across a system reset.
    WARN_RESET_REQUIRED     =  7,

    /// The image failed to load.
    LOAD_ERROR              = ERROR_BIT |  1,
    /// A parameter was incorrect.
    INVALID_PARAMETER       = ERROR_BIT |  2,
    /// The operation is not supported.
    UNSUPPORTED             = ERROR_BIT |  3,
    /// The buffer was not the proper size for the request.
    BAD_BUFFER_SIZE         = ERROR_BIT |  4,
    /// The buffer is not large enough to hold the requested data.
    /// The required buffer size is returned in the appropriate parameter.
    BUFFER_TOO_SMALL        = ERROR_BIT |  5,
    /// There is no data pending upon return.
    NOT_READY               = ERROR_BIT |  6,
    /// The physical device reported an error while attempting the operation.
    DEVICE_ERROR            = ERROR_BIT |  7,
    /// The device cannot be written to.
    WRITE_PROTECTED         = ERROR_BIT |  8,
    /// A resource has run out.
    OUT_OF_RESOURCES        = ERROR_BIT |  9,
    /// An inconstency was detected on the file system.
    VOLUME_CORRUPTED        = ERROR_BIT | 10,
    /// There is no more space on the file system.
    VOLUME_FULL             = ERROR_BIT | 11,
    /// The device does not contain any medium to perform the operation.
    NO_MEDIA                = ERROR_BIT | 12,
    /// The medium in the device has changed since the last access.
    MEDIA_CHANGED           = ERROR_BIT | 13,
    /// The item was not found.
    NOT_FOUND               = ERROR_BIT | 14,
    /// Access was denied.
    ACCESS_DENIED           = ERROR_BIT | 15,
    /// The server was not found or did not respond to the request.
    NO_RESPONSE             = ERROR_BIT | 16,
    /// A mapping to a device does not exist.
    NO_MAPPING              = ERROR_BIT | 17,
    /// The timeout time expired.
    TIMEOUT                 = ERROR_BIT | 18,
    /// The protocol has not been started.
    NOT_STARTED             = ERROR_BIT | 19,
    /// The protocol has already been started.
    ALREADY_STARTED         = ERROR_BIT | 20,
    /// The operation was aborted.
    ABORTED                 = ERROR_BIT | 21,
    /// An ICMP error occurred during the network operation.
    ICMP_ERROR              = ERROR_BIT | 22,
    /// A TFTP error occurred during the network operation.
    TFTP_ERROR              = ERROR_BIT | 23,
    /// A protocol error occurred during the network operation.
    PROTOCOL_ERROR          = ERROR_BIT | 24,
    /// The function encountered an internal version that was
    /// incompatible with a version requested by the caller.
    INCOMPATIBLE_VERSION    = ERROR_BIT | 25,
    /// The function was not performed due to a security violation.
    SECURITY_VIOLATION      = ERROR_BIT | 26,
    /// A CRC error was detected.
    CRC_ERROR               = ERROR_BIT | 27,
    /// Beginning or end of media was reached
    END_OF_MEDIA            = ERROR_BIT | 28,
    /// The end of the file was reached.
    END_OF_FILE             = ERROR_BIT | 31,
    /// The language specified was invalid.
    INVALID_LANGUAGE        = ERROR_BIT | 32,
    /// The security status of the data is unknown or compromised and
    /// the data must be updated or replaced to restore a valid security status.
    COMPROMISED_DATA        = ERROR_BIT | 33,
    /// There is an address conflict address allocation
    IP_ADDRESS_CONFLICT     = ERROR_BIT | 34,
    /// A HTTP error occurred during the network operation.
    HTTP_ERROR              = ERROR_BIT | 35,
}}

impl Status {
    /// Returns true if status code indicates success.
    #[inline]
    #[must_use]
    pub fn is_success(self) -> bool {
        self == Status::SUCCESS
    }

    /// Returns true if status code indicates a warning.
    #[inline]
    #[must_use]
    pub fn is_warning(self) -> bool {
        (self != Status::SUCCESS) && (self.0 & ERROR_BIT == 0)
    }

    /// Returns true if the status code indicates an error.
    #[inline]
    #[must_use]
    pub const fn is_error(self) -> bool {
        self.0 & ERROR_BIT != 0
    }

    /// Converts this status code into a [`uefi::Result`] with a given `Ok` value.
    ///
    /// If the status does not indicate success, the status representing the specific error
    /// code is embedded into the `Err` variant of type [`uefi::Error`].
    #[inline]
    pub fn into_with_val<T>(self, val: impl FnOnce() -> T) -> Result<T, ()> {
        if self.is_success() {
            Ok(val())
        } else {
            Err(self.into())
        }
    }

    /// Converts this status code into a [`uefi::Result`] with a given `Err` payload.
    ///
    /// If the status does not indicate success, the status representing the specific error
    /// code is embedded into the `Err` variant of type [`uefi::Error`].
    #[inline]
    pub fn into_with_err<ErrData: Debug>(
        self,
        err: impl FnOnce(Status) -> ErrData,
    ) -> Result<(), ErrData> {
        if self.is_success() {
            Ok(())
        } else {
            Err(Error::new(self, err(self)))
        }
    }

    /// Convert this status code into a result with a given `Ok` value and `Err` payload.
    ///
    /// If the status does not indicate success, the status representing the specific error
    /// code is embedded into the `Err` variant of type [`uefi::Error`].
    #[inline]
    pub fn into_with<T, ErrData: Debug>(
        self,
        val: impl FnOnce() -> T,
        err: impl FnOnce(Status) -> ErrData,
    ) -> Result<T, ErrData> {
        if self.is_success() {
            Ok(val())
        } else {
            Err(Error::new(self, err(self)))
        }
    }
}

// An UEFI status is equivalent to a Result with no data or error payload
impl From<Status> for Result<(), ()> {
    #[inline]
    fn from(status: Status) -> Result<(), ()> {
        status.into_with(|| (), |_| ())
    }
}

impl core::fmt::Display for Status {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        Debug::fmt(self, f)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_status_to_result() {
        assert!(Result::from(Status::SUCCESS).is_ok());
        assert!(Result::from(Status::WARN_DELETE_FAILURE).is_err());
        assert!(Result::from(Status::BUFFER_TOO_SMALL).is_err());

        assert_eq!(Status::SUCCESS.into_with_val(|| 123).unwrap(), 123);
        assert!(Status::WARN_DELETE_FAILURE.into_with_val(|| 123).is_err());
        assert!(Status::BUFFER_TOO_SMALL.into_with_val(|| 123).is_err());

        assert!(Status::SUCCESS.into_with_err(|_| 123).is_ok());
        assert_eq!(
            *Status::WARN_DELETE_FAILURE
                .into_with_err(|_| 123)
                .unwrap_err()
                .data(),
            123
        );
        assert_eq!(
            *Status::BUFFER_TOO_SMALL
                .into_with_err(|_| 123)
                .unwrap_err()
                .data(),
            123
        );

        assert_eq!(Status::SUCCESS.into_with(|| 123, |_| 456).unwrap(), 123);
        assert_eq!(
            *Status::WARN_DELETE_FAILURE
                .into_with(|| 123, |_| 456)
                .unwrap_err()
                .data(),
            456
        );
        assert_eq!(
            *Status::BUFFER_TOO_SMALL
                .into_with(|| 123, |_| 456)
                .unwrap_err()
                .data(),
            456
        );
    }
}