diff --git a/CHANGELOG.md b/CHANGELOG.md index e41fab760..3dfc319df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,20 @@ - There is a new `fs` module that provides a high-level API for file-system access. The API is close to the `std::fs` module. +- Multiple convenience methods for `CString16` and `CStr16`, including: + - `CStr16::as_slice()` + - `CStr16::len()` + - `CStr16::is_empty()` + - `CStr16::char_replace_all_in_place()` + - `CString16::new()` + - `CString16::is_empty()` + - `CString16::len()` + - `CString16::char_replace_all_in_place()` + - `CString16::push()` + - `CString16::push_str()` + - `From<&CStr16>` for `CString16` + - `From<&CStr16>` for `String` + - `From<&CString16>` for `String` ### Changed @@ -16,6 +30,7 @@ - `Error::new` and `Error::from` now panic if the status is `SUCCESS`. - `Image::get_image_file_system` now returns a `fs::FileSystem` instead of the protocol. +- `CString16::default` now always contains a null byte. ## uefi-macros - [Unreleased] diff --git a/uefi/src/data_types/chars.rs b/uefi/src/data_types/chars.rs index 30f4beac1..e3a6275e7 100644 --- a/uefi/src/data_types/chars.rs +++ b/uefi/src/data_types/chars.rs @@ -65,6 +65,17 @@ pub const NUL_8: Char8 = Char8(0); #[repr(transparent)] pub struct Char16(u16); +impl Char16 { + /// Creates a UCS-2 character from a Rust character without checks. + /// + /// # Safety + /// The caller must be sure that the character is valid. + #[must_use] + pub const unsafe fn from_u16_unchecked(val: u16) -> Self { + Self(val) + } +} + impl TryFrom for Char16 { type Error = CharConversionError; @@ -125,4 +136,4 @@ impl fmt::Display for Char16 { } /// UCS-2 version of the NUL character -pub const NUL_16: Char16 = Char16(0); +pub const NUL_16: Char16 = unsafe { Char16::from_u16_unchecked(0) }; diff --git a/uefi/src/data_types/owned_strs.rs b/uefi/src/data_types/owned_strs.rs index a0b7dd4a9..b847ea9ab 100644 --- a/uefi/src/data_types/owned_strs.rs +++ b/uefi/src/data_types/owned_strs.rs @@ -4,7 +4,10 @@ use crate::data_types::strs::EqStrUntilNul; use crate::data_types::UnalignedSlice; use crate::polyfill::vec_into_raw_parts; use alloc::borrow::{Borrow, ToOwned}; +use alloc::string::String; +use alloc::vec; use alloc::vec::Vec; +use core::borrow::BorrowMut; use core::{fmt, ops}; /// Error returned by [`CString16::try_from::<&str>`]. @@ -47,9 +50,66 @@ impl core::error::Error for FromStrError {} /// let s = CString16::try_from("abc").unwrap(); /// assert_eq!(s.to_string(), "abc"); /// ``` -#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct CString16(Vec); +impl CString16 { + /// Creates a new empty string with a null-byte. + #[must_use] + pub fn new() -> Self { + Self(vec![NUL_16]) + } + + /// Pushes a character to the end of the string. + /// + /// # Panics + /// Panics if the char is a null byte. + pub fn push(&mut self, char: Char16) { + assert_ne!(char, NUL_16, "Pushing a null-byte is illegal"); + let last_elem = self + .0 + .last_mut() + .expect("There should be at least a null byte"); + *last_elem = char; + self.0.push(NUL_16); + } + + /// Pushes a string to the end of the string. + /// + /// # Panics + /// Panics if the char is a null byte. + pub fn push_str(&mut self, str: &CStr16) { + str.as_slice() + .iter() + .copied() + .for_each(|char| self.push(char)); + } + + /// Replaces all chars in the string with the replace value in-place. + pub fn char_replace_all_in_place(&mut self, search: Char16, replace: Char16) { + let slice: &mut CStr16 = self.as_mut(); + slice.char_replace_all_in_place(search, replace); + } + + /// Returns the number of characters without the trailing null. + #[must_use] + pub fn len(&self) -> usize { + self.0.len() - 1 + } + + /// Returns if the string is empty. This ignores the null byte. + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Default for CString16 { + fn default() -> Self { + CString16::new() + } +} + impl TryFrom<&str> for CString16 { type Error = FromStrError; @@ -112,6 +172,20 @@ impl<'a> TryFrom<&UnalignedSlice<'a, u16>> for CString16 { } } +impl From<&CStr16> for CString16 { + fn from(value: &CStr16) -> Self { + let vec = value.as_slice_with_nul().to_vec(); + Self(vec) + } +} + +impl From<&CString16> for String { + fn from(value: &CString16) -> Self { + let slice: &CStr16 = value.as_ref(); + String::from(slice) + } +} + impl<'a> UnalignedSlice<'a, u16> { /// Copies `self` to a new [`CString16`]. pub fn to_cstring16(&self) -> Result { @@ -127,6 +201,12 @@ impl ops::Deref for CString16 { } } +impl ops::DerefMut for CString16 { + fn deref_mut(&mut self) -> &mut CStr16 { + unsafe { &mut *(self.0.as_mut_slice() as *mut [Char16] as *mut CStr16) } + } +} + impl AsRef for CString16 { fn as_ref(&self) -> &CStr16 { self @@ -139,6 +219,18 @@ impl Borrow for CString16 { } } +impl AsMut for CString16 { + fn as_mut(&mut self) -> &mut CStr16 { + self + } +} + +impl BorrowMut for CString16 { + fn borrow_mut(&mut self) -> &mut CStr16 { + self + } +} + impl ToOwned for CStr16 { type Owned = CString16; @@ -256,4 +348,45 @@ mod tests { ] ); } + + /// This tests the following UCS-2 string functions: + /// - runtime constructor + /// - len() + /// - push() / push_str() + /// - to rust string + #[test] + fn test_push_str() { + let mut str1 = CString16::new(); + assert_eq!(str1.num_bytes(), 2, "Should have null-byte"); + assert_eq!(str1.len(), 0); + str1.push(Char16::try_from('h').unwrap()); + str1.push(Char16::try_from('i').unwrap()); + assert_eq!(str1.len(), 2); + + let mut str2 = CString16::new(); + str2.push(Char16::try_from('!').unwrap()); + + str2.push_str(str1.as_ref()); + assert_eq!(str2.len(), 3); + + let rust_str = String::from(&str2); + assert_eq!(rust_str, "!hi"); + } + + #[test] + #[should_panic] + fn test_push_str_panic() { + CString16::new().push(NUL_16); + } + + #[test] + fn test_char_replace_all_in_place() { + let mut input = CString16::try_from("foo/bar/foobar//").unwrap(); + let search = Char16::try_from('/').unwrap(); + let replace = Char16::try_from('\\').unwrap(); + input.char_replace_all_in_place(search, replace); + + let input = String::from(&input); + assert_eq!(input, "foo\\bar\\foobar\\\\") + } } diff --git a/uefi/src/data_types/strs.rs b/uefi/src/data_types/strs.rs index c7da2f470..d5953a0a3 100644 --- a/uefi/src/data_types/strs.rs +++ b/uefi/src/data_types/strs.rs @@ -309,26 +309,47 @@ impl CStr16 { }) } - /// Returns the inner pointer to this C string + /// Returns the inner pointer to this C16 string. #[must_use] pub const fn as_ptr(&self) -> *const Char16 { self.0.as_ptr() } - /// Get the underlying [`Char16`] slice, including the trailing null. + /// Get the underlying [`Char16`]s as slice without the trailing null. + #[must_use] + pub fn as_slice(&self) -> &[Char16] { + &self.0[..self.len()] + } + + /// Get the underlying [`Char16`]s as mut slice without the trailing null. + #[must_use] + fn as_slice_mut(&mut self) -> &mut [Char16] { + let len = self.len(); + &mut self.0[..len] + } + + /// Replaces all chars in the string with the replace value. + pub fn char_replace_all_in_place(&mut self, search: Char16, replace: Char16) { + self.as_slice_mut() + .iter_mut() + .filter(|char| **char == search) + .for_each(|char| *char = replace); + } + + /// Get the underlying [`Char16`]s as slice including the trailing null. #[must_use] pub const fn as_slice_with_nul(&self) -> &[Char16] { &self.0 } - /// Converts this C string to a u16 slice + /// Converts this C string to a u16 slice without the trailing null. #[must_use] pub fn to_u16_slice(&self) -> &[u16] { let chars = self.to_u16_slice_with_nul(); &chars[..chars.len() - 1] } - /// Converts this C string to a u16 slice containing the trailing 0 char + /// Converts this C string to a u16 slice containing the trailing null. #[must_use] pub const fn to_u16_slice_with_nul(&self) -> &[u16] { unsafe { &*(&self.0 as *const [Char16] as *const [u16]) } @@ -343,7 +364,19 @@ impl CStr16 { } } - /// Get the number of bytes in the string (including the trailing null character). + /// Returns the number of characters without the trailing null. + #[must_use] + pub const fn len(&self) -> usize { + self.0.len() - 1 + } + + /// Returns if the string is empty. This ignores the null byte. + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Get the number of bytes in the string (including the trailing null). #[must_use] pub const fn num_bytes(&self) -> usize { self.0.len() * 2 @@ -373,6 +406,20 @@ impl CStr16 { } } +#[cfg(feature = "alloc")] +impl From<&CStr16> for alloc::string::String { + fn from(value: &CStr16) -> Self { + value + .as_slice() + .iter() + .copied() + .map(u16::from) + .map(|int| int as u32) + .map(|int| char::from_u32(int).expect("Should be encodable as UTF-8")) + .collect::() + } +} + impl + ?Sized> EqStrUntilNul for CStr16 { fn eq_str_until_nul(&self, other: &StrType) -> bool { let other = other.as_ref(); @@ -391,7 +438,7 @@ impl + ?Sized> EqStrUntilNul for CStr16 { } } -/// An iterator over `CStr16`. +/// An iterator over the [`Char16`]s in a [`CStr16`]. #[derive(Debug)] pub struct CStr16Iter<'a> { inner: &'a CStr16, @@ -575,6 +622,16 @@ mod tests { ); } + #[test] + fn test_cstr16_as_slice() { + let string: &CStr16 = cstr16!("a"); + assert_eq!(string.as_slice(), &[Char16::try_from('a').unwrap()]); + assert_eq!( + string.as_slice_with_nul(), + &[Char16::try_from('a').unwrap(), NUL_16] + ); + } + // Code generation helper for the compare tests of our CStrX types against "str" and "String" // from the standard library. #[allow(non_snake_case)]