diff --git a/CHANGELOG.md b/CHANGELOG.md index 44cea02fe..76d2a1372 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - Added `PhysicalAddress` and `VirtualAddress` type aliases. - Added `Guid::from_bytes` and `Guid::to_bytes`. +- Added `UnalignedSlice` for representing a reference to an unaligned + slice. ### Changed @@ -22,6 +24,12 @@ - The `Revision` type now implements `Display` with correct formatting for all UEFI versions. The custom `Debug` impl has been removed and replaced with a derived `Debug` impl. + +### Removed + +- Removed `UnalignedCStr16`; use `UnalignedSlice` instead. An + `UnalignedSlice` can be converted to a string with `to_cstr16` or + `to_cstring16`. ## uefi-macros - [Unreleased] diff --git a/src/data_types/mod.rs b/src/data_types/mod.rs index 987d7e95b..622a40304 100644 --- a/src/data_types/mod.rs +++ b/src/data_types/mod.rs @@ -129,8 +129,7 @@ mod enums; mod strs; pub use self::strs::{ - CStr16, CStr8, EqStrUntilNul, FromSliceWithNulError, FromStrWithBufError, UnalignedCStr16, - UnalignedCStr16Error, + CStr16, CStr8, EqStrUntilNul, FromSliceWithNulError, FromStrWithBufError, UnalignedCStr16Error, }; #[cfg(feature = "exts")] @@ -138,6 +137,9 @@ mod owned_strs; #[cfg(feature = "exts")] pub use self::owned_strs::{CString16, FromStrError}; +mod unaligned_slice; +pub use unaligned_slice::UnalignedSlice; + #[cfg(test)] mod tests { use super::*; diff --git a/src/data_types/owned_strs.rs b/src/data_types/owned_strs.rs index 048fabf69..292ed9f47 100644 --- a/src/data_types/owned_strs.rs +++ b/src/data_types/owned_strs.rs @@ -2,6 +2,7 @@ use super::chars::{Char16, NUL_16}; use super::strs::{CStr16, FromSliceWithNulError}; use crate::alloc_api::vec::Vec; use crate::data_types::strs::EqStrUntilNul; +use crate::data_types::UnalignedSlice; use core::fmt; use core::ops; @@ -85,6 +86,22 @@ impl TryFrom> for CString16 { } } +impl<'a> TryFrom<&UnalignedSlice<'a, u16>> for CString16 { + type Error = FromSliceWithNulError; + + fn try_from(input: &UnalignedSlice) -> Result { + let v = input.to_vec(); + CString16::try_from(v) + } +} + +impl<'a> UnalignedSlice<'a, u16> { + /// Copies `self` to a new [`CString16`]. + pub fn to_cstring16(&self) -> Result { + CString16::try_from(self) + } +} + impl ops::Deref for CString16 { type Target = CStr16; diff --git a/src/data_types/strs.rs b/src/data_types/strs.rs index 888d0b97a..e93a99539 100644 --- a/src/data_types/strs.rs +++ b/src/data_types/strs.rs @@ -1,12 +1,13 @@ use super::chars::{Char16, Char8, NUL_16, NUL_8}; +use super::UnalignedSlice; use core::fmt; use core::iter::Iterator; -use core::marker::PhantomData; use core::mem::MaybeUninit; use core::result::Result; use core::slice; + #[cfg(feature = "exts")] -use {super::CString16, crate::alloc_api::vec::Vec}; +use super::CString16; /// Errors which can occur during checked `[uN]` -> `CStrN` conversions #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -21,7 +22,7 @@ pub enum FromSliceWithNulError { NotNulTerminated, } -/// Error returned by [`UnalignedCStr16::to_cstr16`]. +/// Error returned by [`CStr16::from_unaligned_slice`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum UnalignedCStr16Error { /// An invalid character was encountered. @@ -261,6 +262,31 @@ impl CStr16 { }) } + /// Create a [`CStr16`] from an [`UnalignedSlice`] using an aligned + /// buffer for storage. The lifetime of the output is tied to `buf`, + /// not `src`. + pub fn from_unaligned_slice<'buf>( + src: &UnalignedSlice<'_, u16>, + buf: &'buf mut [MaybeUninit], + ) -> Result<&'buf CStr16, UnalignedCStr16Error> { + // The input `buf` might be longer than needed, so get a + // subslice of the required length. + let buf = buf + .get_mut(..src.len()) + .ok_or(UnalignedCStr16Error::BufferTooSmall)?; + + src.copy_to_maybe_uninit(buf); + let buf = unsafe { + // Safety: `copy_buf` fully initializes the slice. + MaybeUninit::slice_assume_init_ref(buf) + }; + CStr16::from_u16_with_nul(buf).map_err(|e| match e { + FromSliceWithNulError::InvalidChar(v) => UnalignedCStr16Error::InvalidChar(v), + FromSliceWithNulError::InteriorNul(v) => UnalignedCStr16Error::InteriorNul(v), + FromSliceWithNulError::NotNulTerminated => UnalignedCStr16Error::NotNulTerminated, + }) + } + /// Returns the inner pointer to this C string pub fn as_ptr(&self) -> *const Char16 { self.0.as_ptr() @@ -378,110 +404,15 @@ impl PartialEq for &CStr16 { } } -/// An unaligned UCS-2 null-terminated string. -/// -/// This wrapper type can be used with UEFI strings that are inside a -/// [`repr(packed)`] struct. Creating a reference to a packed field is -/// not allowed because it might not be properly aligned, so a -/// [`CStr16`] can't be directly constructed. `UnalignedCStr16` instead -/// takes a pointer to the unaligned field, which is allowed. The -/// resulting unaligned string cannot be used directly, but must be -/// converted to an aligned form first with [`to_cstr16`] or -/// [`to_cstring16`]. -/// -/// [`repr(packed)`]: https://doc.rust-lang.org/nomicon/other-reprs.html#reprpacked -/// [`to_cstr16`]: Self::to_cstr16 -/// [`to_cstring16`]: Self::to_cstring16 -#[derive(Debug)] -pub struct UnalignedCStr16<'a> { - data: *const u16, - len: usize, - _phantom_lifetime: PhantomData<&'a ()>, -} - -// While it's not unsafe to have an empty `UnalignedCStr16`, there's not -// much point either since the string wouldn't be valid without a null -// terminator. So skip adding an `is_empty` method. -#[allow(clippy::len_without_is_empty)] -impl<'a> UnalignedCStr16<'a> { - /// Create an `UnalignedCStr16` from a `*const u16` pointer. The - /// pointer must be valid but can be unaligned. The `len` parameter - /// is the number of `u16` characters in the string (not the number - /// of bytes), including the trailing null. - /// - /// The `_lifetime` parameter is used to make it easy to set an - /// appropriate lifetime for `'a`. The caller should pass in a - /// reference to something tied to the lifetime of `data`. (The - /// `data` parameter cannot itself be a reference because the - /// pointer is allowed to be unaligned.) - /// - /// # Safety - /// - /// The `data` pointer cannot be dangling, and must remain valid for - /// the lifetime of `'a`. There must be at least `len` `u16` - /// elements starting with the the first character pointed to by - /// `data`. These restrictions allow all other methods on - /// `UnalignedCStr16` to be safe. - pub unsafe fn new(_lifetime: &'a T, data: *const u16, len: usize) -> Self { - Self { - data, - len, - _phantom_lifetime: PhantomData, - } - } - - /// Number of `u16` elements in the string, including the trailing null. - pub fn len(&self) -> usize { - self.len - } - - /// Copy the data to an aligned buffer. Panics if the length of - /// `dst` is not exactly the same as `self.len()`. Otherwise the - /// function always succeeds, and initializes all elements of `dst`. - pub fn copy_to(&self, dst: &mut [MaybeUninit]) { - if dst.len() != self.len { - panic!("incorrect buffer length"); - } - - for (i, elem) in dst.iter_mut().enumerate() { - unsafe { elem.write(self.data.add(i).read_unaligned()) }; - } - } - - /// Convert to a [`CStr16`] using an aligned buffer for storage. The - /// lifetime of the output is tied to `buf`, not `self`. +impl<'a> UnalignedSlice<'a, u16> { + /// Create a [`CStr16`] from an [`UnalignedSlice`] using an aligned + /// buffer for storage. The lifetime of the output is tied to `buf`, + /// not `self`. pub fn to_cstr16<'buf>( &self, buf: &'buf mut [MaybeUninit], ) -> Result<&'buf CStr16, UnalignedCStr16Error> { - // The input `buf` might be longer than needed, so get a - // subslice of the required length. - let buf = buf - .get_mut(..self.len()) - .ok_or(UnalignedCStr16Error::BufferTooSmall)?; - - self.copy_to(buf); - let buf = unsafe { - // Safety: `copy_buf` fully initializes the slice. - MaybeUninit::slice_assume_init_ref(buf) - }; - CStr16::from_u16_with_nul(buf).map_err(|e| match e { - FromSliceWithNulError::InvalidChar(v) => UnalignedCStr16Error::InvalidChar(v), - FromSliceWithNulError::InteriorNul(v) => UnalignedCStr16Error::InteriorNul(v), - FromSliceWithNulError::NotNulTerminated => UnalignedCStr16Error::NotNulTerminated, - }) - } - - /// Convert to a [`CString16`]. Requires the `exts` feature. - #[cfg(feature = "exts")] - pub fn to_cstring16(&self) -> Result { - let len = self.len(); - let mut v = Vec::with_capacity(len); - unsafe { - self.copy_to(v.spare_capacity_mut()); - v.set_len(len); - } - CString16::try_from(v) + CStr16::from_unaligned_slice(self, buf) } } @@ -580,8 +511,8 @@ mod tests { ptr.add(3).write_unaligned(b't'.into()); ptr.add(4).write_unaligned(b'\0'.into()); - // Create the `UnalignedCStr16`. - UnalignedCStr16::new(&buf, ptr, 5) + // Create the `UnalignedSlice`. + UnalignedSlice::new(ptr, 5) }; // Test `to_cstr16()` with too small of a buffer. diff --git a/src/data_types/unaligned_slice.rs b/src/data_types/unaligned_slice.rs new file mode 100644 index 000000000..9728f8fa0 --- /dev/null +++ b/src/data_types/unaligned_slice.rs @@ -0,0 +1,234 @@ +use core::marker::PhantomData; +use core::mem::MaybeUninit; + +#[cfg(feature = "exts")] +use crate::alloc_api::vec::Vec; + +/// Slice backed by a potentially-unaligned pointer. +/// +/// This wrapper can be used to safely expose slices that are inside a +/// [`repr(packed)`] struct. The element type must be [`Copy`]. +/// +/// [`repr(packed)`]: https://doc.rust-lang.org/nomicon/other-reprs.html#reprpacked +#[derive(Debug)] +pub struct UnalignedSlice<'a, T: Copy> { + data: *const T, + len: usize, + _phantom_lifetime: PhantomData<&'a T>, +} + +impl<'a, T: Copy> UnalignedSlice<'a, T> { + /// Create an `UnalignedSlice` from a raw pointer. The pointer must + /// not be dangling but can be unaligned. The `len` parameter is the + /// number of elements in the slice (not the number of bytes). + /// + /// # Safety + /// + /// The `data` pointer must point to a packed array of at least + /// `len` elements of type `T`. The pointer must remain valid for as + /// long as the `'a` lifetime. + pub unsafe fn new(data: *const T, len: usize) -> Self { + Self { + data, + len, + _phantom_lifetime: PhantomData::default(), + } + } + + /// Returns true if the slice has a length of 0. + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Returns the number of elements in the slice. + pub fn len(&self) -> usize { + self.len + } + + /// Returns the element at `index`, or `None` if the `index` is out + /// of bounds. + pub fn get(&self, index: usize) -> Option { + if index < self.len { + Some(unsafe { self.data.add(index).read_unaligned() }) + } else { + None + } + } + + /// Returns an iterator over the slice. + /// + /// The iterator yields all items from start to end. + pub fn iter(&'a self) -> UnalignedSliceIter<'a, T> { + UnalignedSliceIter { + slice: self, + index: 0, + } + } + + /// Copy the data to an aligned buffer. + /// + /// The length of `dest` must be the same as `self`. + /// + /// # Panics + /// + /// This function will panic if the two slices have different lengths. + pub fn copy_to(&self, dest: &mut [T]) { + if dest.len() != self.len { + panic!( + "source slice length ({}) does not match destination slice length ({})", + self.len(), + dest.len(), + ); + } + + for (i, elem) in dest.iter_mut().enumerate() { + *elem = unsafe { self.data.add(i).read_unaligned() }; + } + } + + /// Copy the data to an aligned [`MaybeUninit`] buffer. + /// + /// The length of `dest` must be the same as `self`. + /// + /// This function fully initializes the `dest` slice. + /// + /// # Panics + /// + /// This function will panic if the two slices have different lengths. + pub fn copy_to_maybe_uninit(&self, dest: &mut [MaybeUninit]) { + if dest.len() != self.len { + panic!( + "source slice length ({}) does not match destination slice length ({})", + self.len(), + dest.len(), + ); + } + + for (i, elem) in dest.iter_mut().enumerate() { + unsafe { elem.write(self.data.add(i).read_unaligned()) }; + } + } + + /// Copies `self` into a new `Vec`. + #[cfg(feature = "exts")] + pub fn to_vec(&self) -> Vec { + let len = self.len(); + let mut v = Vec::with_capacity(len); + unsafe { + self.copy_to_maybe_uninit(v.spare_capacity_mut()); + v.set_len(len); + } + v + } +} + +#[cfg(feature = "exts")] +impl<'a, T: Copy> From> for Vec { + fn from(input: UnalignedSlice<'a, T>) -> Self { + input.to_vec() + } +} + +impl<'a, T: Copy> IntoIterator for UnalignedSlice<'a, T> { + type Item = T; + type IntoIter = UnalignedSliceIntoIter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + UnalignedSliceIntoIter { + slice: self, + index: 0, + } + } +} + +impl<'a, T: Copy> IntoIterator for &'a UnalignedSlice<'a, T> { + type Item = T; + type IntoIter = UnalignedSliceIter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// Iterator for a [`UnalignedSlice`]. +pub struct UnalignedSliceIntoIter<'a, T: Copy> { + slice: UnalignedSlice<'a, T>, + index: usize, +} + +impl<'a, T: Copy> Iterator for UnalignedSliceIntoIter<'a, T> { + type Item = T; + + fn next(&mut self) -> Option { + let output = self.slice.get(self.index)?; + self.index += 1; + Some(output) + } +} + +/// Iterator for a [`UnalignedSlice`] reference. +pub struct UnalignedSliceIter<'a, T: Copy> { + slice: &'a UnalignedSlice<'a, T>, + index: usize, +} + +impl<'a, T: Copy> Iterator for UnalignedSliceIter<'a, T> { + type Item = T; + + fn next(&mut self) -> Option { + let output = self.slice.get(self.index)?; + self.index += 1; + Some(output) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc_api::vec::Vec; + + #[test] + fn test_unaligned_slice() { + #[rustfmt::skip] + let bytes: [u8; 13] = [ + // Extra byte to make the rest of the data unaligned. + 0, + // First element. + 0x10, 0x11, 0x12, 0x13, + // Second element. + 0x20, 0x21, 0x22, 0x23, + // Third element. + 0x30, 0x31, 0x32, 0x33, + ]; + + // Skip past the first byte and create an unaligned `*const u32` pointer. + let bytes = &bytes[1..]; + let slice_ptr: *const u32 = bytes.as_ptr().cast(); + + let slice: UnalignedSlice = unsafe { UnalignedSlice::new(slice_ptr, 0) }; + assert!(slice.is_empty()); + + let slice: UnalignedSlice = unsafe { UnalignedSlice::new(slice_ptr, 3) }; + assert!(!slice.is_empty()); + assert_eq!(slice.len(), 3); + + assert_eq!(slice.get(0), Some(0x13121110)); + assert_eq!(slice.get(1), Some(0x23222120)); + assert_eq!(slice.get(2), Some(0x33323130)); + assert_eq!(slice.get(3), None); + + let mut copy = [0; 3]; + slice.copy_to(&mut copy); + assert_eq!(copy, [0x13121110, 0x23222120, 0x33323130]); + + assert_eq!( + slice.iter().collect::>(), + [0x13121110, 0x23222120, 0x33323130] + ); + + assert_eq!( + slice.into_iter().collect::>(), + [0x13121110, 0x23222120, 0x33323130] + ); + } +} diff --git a/src/proto/device_path/mod.rs b/src/proto/device_path/mod.rs index f2e9e36be..12aede513 100644 --- a/src/proto/device_path/mod.rs +++ b/src/proto/device_path/mod.rs @@ -68,7 +68,7 @@ pub mod text; -use crate::data_types::UnalignedCStr16; +use crate::data_types::UnalignedSlice; use crate::proto::{Protocol, ProtocolPointer}; use crate::{unsafe_guid, Guid}; use core::ffi::c_void; @@ -578,10 +578,10 @@ pub struct FilePathMediaDevicePath { } impl FilePathMediaDevicePath { - /// Get the path. An [`UnalignedCStr16`] is returned since this is a + /// Get the path. An [`UnalignedSlice`] is returned since this is a /// packed struct. - pub fn path_name(&self) -> UnalignedCStr16<'_> { - // Safety: creating this `UnalignedCStr16` is safe because the + pub fn path_name(&self) -> UnalignedSlice { + // Safety: creating this `UnalignedSlice` is safe because the // `path_name` pointer is valid (although potentially // unaligned), and the lifetime of the output is tied to `self`, // so there's no possibility of use-after-free. @@ -589,7 +589,7 @@ impl FilePathMediaDevicePath { // Use `addr_of` to avoid creating an unaligned reference. let ptr: *const [u16] = ptr::addr_of!(self.path_name); let (ptr, len): (*const (), usize) = ptr.to_raw_parts(); - UnalignedCStr16::new(self, ptr.cast::(), len) + UnalignedSlice::new(ptr.cast::(), len) } } }