diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index 546be6ea4..eeaf56ead 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -1,5 +1,9 @@ # uefi - [Unreleased] +## Added +- Implemented `PartialEq` for `Char8` and `Char16`. +- Added `CStr16::from_char16_with_nul` and `Char16::from_char16_with_nul_unchecked`. + # uefi - 0.26.0 (2023-11-12) ## Added diff --git a/uefi/src/data_types/chars.rs b/uefi/src/data_types/chars.rs index ab270a82f..96cd00813 100644 --- a/uefi/src/data_types/chars.rs +++ b/uefi/src/data_types/chars.rs @@ -66,6 +66,12 @@ impl fmt::Display for Char8 { } } +impl PartialEq for Char8 { + fn eq(&self, other: &char) -> bool { + u32::from(self.0) == u32::from(*other) + } +} + /// Latin-1 version of the NUL character pub const NUL_8: Char8 = Char8(0); @@ -150,5 +156,24 @@ impl fmt::Display for Char16 { } } +impl PartialEq for Char16 { + fn eq(&self, other: &char) -> bool { + u32::from(self.0) == u32::from(*other) + } +} + /// UCS-2 version of the NUL character pub const NUL_16: Char16 = unsafe { Char16::from_u16_unchecked(0) }; + +#[cfg(test)] +mod tests { + use super::*; + + /// Test that `Char8` and `Char16` can be directly compared with `char`. + #[test] + fn test_char_eq() { + let primitive_char: char = 'A'; + assert_eq!(Char8(0x41), primitive_char); + assert_eq!(Char16(0x41), primitive_char); + } +} diff --git a/uefi/src/data_types/strs.rs b/uefi/src/data_types/strs.rs index 9e22be51a..f9d4aabfa 100644 --- a/uefi/src/data_types/strs.rs +++ b/uefi/src/data_types/strs.rs @@ -284,6 +284,40 @@ impl CStr16 { &*(codes as *const [u16] as *const Self) } + /// Creates a `&CStr16` from a [`Char16`] slice, if the slice is + /// null-terminated and has no interior null characters. + pub fn from_char16_with_nul(chars: &[Char16]) -> Result<&Self, FromSliceWithNulError> { + // Fail early if the input is empty. + if chars.is_empty() { + return Err(FromSliceWithNulError::NotNulTerminated); + } + + // Find the index of the first null char. + if let Some(null_index) = chars.iter().position(|c| *c == NUL_16) { + // Verify the null character is at the end. + if null_index == chars.len() - 1 { + // Safety: the input is null-terminated and has no interior nulls. + Ok(unsafe { Self::from_char16_with_nul_unchecked(chars) }) + } else { + Err(FromSliceWithNulError::InteriorNul(null_index)) + } + } else { + Err(FromSliceWithNulError::NotNulTerminated) + } + } + + /// Unsafely creates a `&CStr16` from a `Char16` slice. + /// + /// # Safety + /// + /// It's the callers responsibility to ensure chars is null-terminated and + /// has no interior null characters. + #[must_use] + pub const unsafe fn from_char16_with_nul_unchecked(chars: &[Char16]) -> &Self { + let ptr: *const [Char16] = chars; + &*(ptr as *const Self) + } + /// Convert a [`&str`] to a `&CStr16`, backed by a buffer. /// /// The input string must contain only characters representable with @@ -615,6 +649,45 @@ mod tests { assert_eq!(s.num_bytes(), 8); } + #[test] + fn test_cstr16_from_char16_with_nul() { + // Invalid: empty input. + assert_eq!( + CStr16::from_char16_with_nul(&[]), + Err(FromSliceWithNulError::NotNulTerminated) + ); + + // Invalid: interior null. + assert_eq!( + CStr16::from_char16_with_nul(&[ + Char16::try_from('a').unwrap(), + NUL_16, + Char16::try_from('b').unwrap(), + NUL_16 + ]), + Err(FromSliceWithNulError::InteriorNul(1)) + ); + + // Invalid: no trailing null. + assert_eq!( + CStr16::from_char16_with_nul(&[ + Char16::try_from('a').unwrap(), + Char16::try_from('b').unwrap(), + ]), + Err(FromSliceWithNulError::NotNulTerminated) + ); + + // Valid. + assert_eq!( + CStr16::from_char16_with_nul(&[ + Char16::try_from('a').unwrap(), + Char16::try_from('b').unwrap(), + NUL_16, + ]), + Ok(cstr16!("ab")) + ); + } + #[test] fn test_cstr16_from_str_with_buf() { let mut buf = [0; 4];