Skip to content

uefi: Add TryFrom<&[u8]> for Time #1212

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion uefi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
- `MemoryMap::as_raw` which provides raw access to the memory map. This is for
example useful if you create your own Multiboot2 bootloader that embeds the
EFI mmap in a Multiboot2 boot information structure.
- `Mode` is now `Copy` and `Clone`
- `Mode` is now `Copy` and `Clone`.
- Added `TryFrom<&[u8]>` for `Time`.

## Changed
- `SystemTable::exit_boot_services` is now `unsafe`. See that method's
Expand Down
148 changes: 147 additions & 1 deletion uefi/src/table/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::Revision;
use crate::table::boot::MemoryDescriptor;
use crate::{CStr16, Error, Result, Status, StatusExt};
use core::fmt::{self, Debug, Display, Formatter};
use core::mem::MaybeUninit;
use core::mem::{size_of, MaybeUninit};
use core::ptr;

pub use uefi_raw::capsule::{CapsuleBlockDescriptor, CapsuleFlags, CapsuleHeader};
Expand Down Expand Up @@ -582,6 +582,69 @@ impl Display for Time {
}
}

/// Error returned from failing to convert a byte slice into a [`Time`].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TimeByteConversionError {
/// One or more fields of the converted [`Time`] is invalid.
InvalidFields(TimeError),
/// The byte slice is not large enough to hold a [`Time`].
InvalidSize,
}

impl Display for TimeByteConversionError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::InvalidFields(error) => write!(f, "{error}"),
Self::InvalidSize => write!(
f,
"the byte slice is not large enough to hold a Time struct"
),
}
}
}

impl TryFrom<&[u8]> for Time {
type Error = TimeByteConversionError;

fn try_from(bytes: &[u8]) -> core::result::Result<Self, Self::Error> {
if size_of::<Time>() <= bytes.len() {
let year = u16::from_le_bytes(bytes[0..2].try_into().unwrap());
let month = bytes[2];
let day = bytes[3];
let hour = bytes[4];
let minute = bytes[5];
let second = bytes[6];
let nanosecond = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
let time_zone = match i16::from_le_bytes(bytes[12..14].try_into().unwrap()) {
Self::UNSPECIFIED_TIMEZONE => None,
num => Some(num),
};
let daylight = Daylight::from_bits(bytes[14]).ok_or(
TimeByteConversionError::InvalidFields(TimeError {
daylight: true,
..Default::default()
}),
)?;

let time_params = TimeParams {
year,
month,
day,
hour,
minute,
second,
nanosecond,
time_zone,
daylight,
};

Time::new(time_params).map_err(TimeByteConversionError::InvalidFields)
} else {
Err(TimeByteConversionError::InvalidSize)
}
}
}

/// Unique key for a variable.
#[cfg(feature = "alloc")]
#[derive(Debug)]
Expand Down Expand Up @@ -651,3 +714,86 @@ pub struct CapsuleInfo {
/// The type of reset required for the capsule update.
pub reset_type: ResetType,
}

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

use alloc::string::ToString;
use core::{slice, usize};

unsafe fn time_as_u8_slice(p: &Time) -> &[u8] {
slice::from_raw_parts(core::ptr::addr_of!(*p).cast(), size_of::<Time>())
}

unsafe fn time_as_u8_slice_with_size(p: &Time, len: usize) -> &[u8] {
slice::from_raw_parts(core::ptr::addr_of!(*p).cast(), len)
}

#[test]
fn test_successful_time_from_bytes() {
let mut time;
let mut time_from_bytes;
let mut time_params = TimeParams {
year: 2024,
month: 6,
day: 13,
hour: 4,
minute: 29,
second: 30,
nanosecond: 123_456_789,
time_zone: None,
daylight: Daylight::empty(),
};

time = Time::new(time_params).unwrap();
unsafe {
time_from_bytes = Time::try_from(time_as_u8_slice(&time)).unwrap();
}
assert_eq!(time, time_from_bytes);

time_params.time_zone = Some(120);
time = Time::new(time_params).unwrap();
unsafe {
time_from_bytes = Time::try_from(time_as_u8_slice(&time)).unwrap();
}
assert_eq!(time.to_string(), time_from_bytes.to_string());

time_params.time_zone = Some(150);
time = Time::new(time_params).unwrap();
unsafe {
time_from_bytes = Time::try_from(time_as_u8_slice(&time)).unwrap();
}
assert_eq!(time.to_string(), time_from_bytes.to_string());
}

#[test]
fn test_invalid_fields_in_time_byte_conversion() {
let time = Time::invalid();
let time_from_bytes;
unsafe {
time_from_bytes = Time::try_from(time_as_u8_slice(&time)).unwrap_err();
}
assert_eq!(
TimeByteConversionError::InvalidFields(TimeError {
year: true,
month: true,
day: true,
..Default::default()
}),
time_from_bytes
);
}

#[test]
fn test_byte_slice_too_small_to_convert_to_time() {
let time = Time::invalid();
let time_from_bytes;
unsafe {
time_from_bytes =
Time::try_from(time_as_u8_slice_with_size(&time, size_of::<Time>() - 1))
.unwrap_err();
}
assert_eq!(TimeByteConversionError::InvalidSize, time_from_bytes);
}
}