// Copyright 2022, The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Error types used in libavb. //! //! There are a few advantages of providing these custom types rather than exposing the raw bindgen //! enums directly: //! * More idiomatic error handling //! * C code defines a "status" enum that can contain either OK or an error, whereas Rust prefers //! error-only enums to use with `Result<>` e.g. `Result<(), IoError>`. An "OK" status doesn't //! make sense when used with `Result<>`. //! * Better naming e.g. `IoError::Oom` vs the redundant `AvbIoResult::AVB_IO_RESULT_ERROR_OOM` //! * We can implement traits such as `Display` for added convenience. // The naming scheme can be a bit confusing due to the re-use of "result" in a few places: // * `Avb*Result`: raw libavb enums generated by bindgen, containing errors and "OK". Internal-only; // library users should never have to use these types. // * `*Error`: `Avb*Result` wrappers which only contain error conditions, not "OK". Should be // wrapped in a Rust `Result<>` in public API. // * `Result`: top-level `Result<>` type used in this library's public API. use crate::SlotVerifyData; use avb_bindgen::{AvbIOResult, AvbSlotVerifyResult, AvbVBMetaVerifyResult}; use core::{fmt, str::Utf8Error}; /// `AvbSlotVerifyResult` error wrapper. #[derive(Debug, PartialEq, Eq)] pub enum SlotVerifyError<'a> { /// `AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT` InvalidArgument, /// `AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA` InvalidMetadata, /// `AVB_SLOT_VERIFY_RESULT_ERROR_IO` Io, /// `AVB_SLOT_VERIFY_RESULT_ERROR_OOM` Oom, /// `AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED` PublicKeyRejected, /// `AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX` RollbackIndex, /// `AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION` UnsupportedVersion, /// `AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION` /// /// This verification error can contain the resulting `SlotVerifyData` if the /// `AllowVerificationError` flag was passed into `slot_verify()`. Verification(Option>), /// Unexpected internal error. This does not have a corresponding libavb error code. Internal, } /// `Result` type for `SlotVerifyError` errors. pub type SlotVerifyResult<'a, T> = Result>; /// `Result` type for `SlotVerifyError` errors without any `SlotVerifyData`. /// /// If the contained error will never hold a `SlotVerifyData`, this is easier to work with compared /// to `SlotVerifyResult` due to the static lifetime bound. pub type SlotVerifyNoDataResult = SlotVerifyResult<'static, T>; impl<'a> SlotVerifyError<'a> { /// Returns a copy of this error without any contained `SlotVerifyData`. /// /// This can simplify usage if the user doesn't care about the `SlotVerifyData` by turning the /// current lifetime bound into `'static`. pub fn without_verify_data(&self) -> SlotVerifyError<'static> { match self { Self::InvalidArgument => SlotVerifyError::InvalidArgument, Self::InvalidMetadata => SlotVerifyError::InvalidMetadata, Self::Io => SlotVerifyError::Io, Self::Oom => SlotVerifyError::Oom, Self::PublicKeyRejected => SlotVerifyError::PublicKeyRejected, Self::RollbackIndex => SlotVerifyError::RollbackIndex, Self::UnsupportedVersion => SlotVerifyError::UnsupportedVersion, Self::Verification(_) => SlotVerifyError::Verification(None), Self::Internal => SlotVerifyError::Internal, } } } impl<'a> fmt::Display for SlotVerifyError<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::InvalidArgument => write!(f, "Invalid parameters"), Self::InvalidMetadata => write!(f, "Invalid metadata"), Self::Io => write!(f, "I/O error"), Self::Oom => write!(f, "Unable to allocate memory"), Self::PublicKeyRejected => write!(f, "Public key rejected or data not signed"), Self::RollbackIndex => write!(f, "Rollback index violation"), Self::UnsupportedVersion => write!(f, "Unsupported vbmeta version"), Self::Verification(_) => write!(f, "Verification failure"), Self::Internal => write!(f, "Internal error"), } } } /// Converts a bindgen `AvbSlotVerifyResult` enum to a `SlotVerifyNoDataResult<>`, mapping /// `AVB_SLOT_VERIFY_RESULT_OK` to the Rust equivalent `Ok(())` and errors to the corresponding /// `Err(SlotVerifyError)`. /// /// A `Verification` error returned here will always have a `None` `SlotVerifyData`; the data should /// be added in later if it exists. /// /// This function is also important to serve as a compile-time check that we're handling all the /// libavb enums; if a new one is added to (or removed from) the C code, this will fail to compile /// until it is updated to match. pub(crate) fn slot_verify_enum_to_result( result: AvbSlotVerifyResult, ) -> SlotVerifyNoDataResult<()> { match result { AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_OK => Ok(()), AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT => { Err(SlotVerifyError::InvalidArgument) } AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA => { Err(SlotVerifyError::InvalidMetadata) } AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_IO => Err(SlotVerifyError::Io), AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_OOM => Err(SlotVerifyError::Oom), AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED => { Err(SlotVerifyError::PublicKeyRejected) } AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX => { Err(SlotVerifyError::RollbackIndex) } AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION => { Err(SlotVerifyError::UnsupportedVersion) } AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION => { Err(SlotVerifyError::Verification(None)) } } } /// `AvbIOResult` error wrapper. #[derive(Clone, Debug, PartialEq, Eq)] pub enum IoError { /// `AVB_IO_RESULT_ERROR_OOM` Oom, /// `AVB_IO_RESULT_ERROR_IO` Io, /// `AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION` NoSuchPartition, /// `AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION` RangeOutsidePartition, /// `AVB_IO_RESULT_ERROR_NO_SUCH_VALUE` NoSuchValue, /// `AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE` InvalidValueSize, /// `AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE`. Also contains the space that would be required. InsufficientSpace(usize), /// Custom error code to indicate that an optional callback method has not been implemented. /// If this is returned from a required callback method, it will bubble up as an `Io` error. NotImplemented, } /// `Result` type for `IoError` errors. pub type IoResult = Result; impl fmt::Display for IoError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Oom => write!(f, "Unable to allocate memory"), Self::Io => write!(f, "I/O error"), Self::NoSuchPartition => write!(f, "No such partition exists"), Self::RangeOutsidePartition => write!(f, "Range is outside the partition"), Self::NoSuchValue => write!(f, "No such named persistent value"), Self::InvalidValueSize => write!(f, "Invalid named persistent value size"), Self::InsufficientSpace(size) => write!(f, "Buffer is too small (requires {})", size), Self::NotImplemented => write!(f, "Function not implemented"), } } } impl From for IoError { fn from(_: Utf8Error) -> Self { Self::Io } } // Converts our `IoError` to the bindgen `AvbIOResult` enum. // // Unlike `SlotVerifyError` which gets generated by libavb and passed to the caller, `IoError` is // created by the user callbacks and passed back into libavb so we need to be able to convert in // this direction as well. impl From for AvbIOResult { fn from(error: IoError) -> Self { match error { IoError::Oom => AvbIOResult::AVB_IO_RESULT_ERROR_OOM, IoError::Io => AvbIOResult::AVB_IO_RESULT_ERROR_IO, IoError::NoSuchPartition => AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION, IoError::RangeOutsidePartition => { AvbIOResult::AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION } IoError::NoSuchValue => AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_VALUE, IoError::InvalidValueSize => AvbIOResult::AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE, IoError::InsufficientSpace(_) => AvbIOResult::AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE, // `NotImplemented` is internal to this library and doesn't have a libavb equivalent, // convert it to the default I/O error. IoError::NotImplemented => AvbIOResult::AVB_IO_RESULT_ERROR_IO, } } } /// Converts an `IoResult<>` to the bindgen `AvbIOResult` enum. pub(crate) fn result_to_io_enum(result: IoResult<()>) -> AvbIOResult { result.map_or_else(|e| e.into(), |_| AvbIOResult::AVB_IO_RESULT_OK) } /// Converts a bindgen `AvbIOResult` enum to an `IoResult<>`, mapping `AVB_IO_RESULT_OK` to the Rust /// equivalent `Ok(())` and errors to the corresponding `Err(IoError)`. /// /// This function is also important to serve as a compile-time check that we're handling all the /// libavb enums; if a new one is added to (or removed from) the C code, this will fail to compile /// until it is updated to match. pub(crate) fn io_enum_to_result(result: AvbIOResult) -> IoResult<()> { match result { AvbIOResult::AVB_IO_RESULT_OK => Ok(()), AvbIOResult::AVB_IO_RESULT_ERROR_OOM => Err(IoError::Oom), AvbIOResult::AVB_IO_RESULT_ERROR_IO => Err(IoError::Io), AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION => Err(IoError::NoSuchPartition), AvbIOResult::AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION => { Err(IoError::RangeOutsidePartition) } AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_VALUE => Err(IoError::NoSuchValue), AvbIOResult::AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE => Err(IoError::InvalidValueSize), AvbIOResult::AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE => Err(IoError::InsufficientSpace(0)), } } /// `AvbVBMetaVerifyResult` error wrapper. #[derive(Clone, Debug, PartialEq, Eq)] pub enum VbmetaVerifyError { /// `AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED` NotSigned, /// `AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER` InvalidVbmetaHeader, /// `AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION` UnsupportedVersion, /// `AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH` HashMismatch, /// `AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH` SignatureMismatch, } /// `Result` type for `VbmetaVerifyError` errors. pub type VbmetaVerifyResult = Result; impl fmt::Display for VbmetaVerifyError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::NotSigned => write!(f, "vbmeta is unsigned"), Self::InvalidVbmetaHeader => write!(f, "invalid vbmeta header"), Self::UnsupportedVersion => write!(f, "unsupported vbmeta version"), Self::HashMismatch => write!(f, "vbmeta hash mismatch"), Self::SignatureMismatch => write!(f, "vbmeta signature mismatch"), } } } // Converts a bindgen `AvbVBMetaVerifyResult` enum to a `VbmetaVerifyResult<>`, mapping // `AVB_VBMETA_VERIFY_RESULT_OK` to the Rust equivalent `Ok(())` and errors to the corresponding // `Err(SlotVerifyError)`. // // This function is also important to serve as a compile-time check that we're handling all the // libavb enums; if a new one is added to (or removed from) the C code, this will fail to compile // until it is updated to match. pub fn vbmeta_verify_enum_to_result(result: AvbVBMetaVerifyResult) -> VbmetaVerifyResult<()> { match result { AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK => Ok(()), AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED => { Err(VbmetaVerifyError::NotSigned) } AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER => { Err(VbmetaVerifyError::InvalidVbmetaHeader) } AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION => { Err(VbmetaVerifyError::UnsupportedVersion) } AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH => { Err(VbmetaVerifyError::HashMismatch) } AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH => { Err(VbmetaVerifyError::SignatureMismatch) } } } #[cfg(test)] mod tests { use super::*; #[test] fn display_slot_verify_error() { // The actual error message can change as needed, the point of the test is just to make sure // the fmt::Display trait is properly implemented. assert_eq!( format!("{}", SlotVerifyError::Verification(None)), "Verification failure" ); } #[test] fn convert_slot_verify_enum_to_result() { assert!(matches!( slot_verify_enum_to_result(AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_OK), Ok(()) )); assert!(matches!( slot_verify_enum_to_result(AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_IO), Err(SlotVerifyError::Io) )); } #[test] fn display_io_error() { // The actual error message can change as needed, the point of the test is just to make sure // the fmt::Display trait is properly implemented. assert_eq!( format!("{}", IoError::NoSuchPartition), "No such partition exists" ); } #[test] fn convert_io_enum_to_result() { // This is a compile-time check that we handle all the `AvbIOResult` enum values. If any // enums are added or removed this will break, indicating we need to update `IoError` to // match. assert_eq!(io_enum_to_result(AvbIOResult::AVB_IO_RESULT_OK), Ok(())); assert_eq!( io_enum_to_result(AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION), Err(IoError::NoSuchPartition) ); } #[test] fn convert_io_result_to_enum() { assert_eq!(result_to_io_enum(Ok(())), AvbIOResult::AVB_IO_RESULT_OK); assert_eq!( result_to_io_enum(Err(IoError::Io)), AvbIOResult::AVB_IO_RESULT_ERROR_IO ); } #[test] fn display_vmbeta_verify_error() { // The actual error message can change as needed, the point of the test is just to make sure // the fmt::Display trait is properly implemented. assert_eq!( format!("{}", VbmetaVerifyError::NotSigned), "vbmeta is unsigned" ); } #[test] fn convert_vbmeta_verify_enum_to_result() { assert_eq!( vbmeta_verify_enum_to_result(AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK), Ok(()) ); assert_eq!( vbmeta_verify_enum_to_result( AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH ), Err(VbmetaVerifyError::HashMismatch) ); } }