Skip to content

Commit

Permalink
uefi: Add CStr16 conversions from slices with interior nuls
Browse files Browse the repository at this point in the history
This is modeled after `CStr::from_bytes_until_nul`, which was added in Rust
1.69. It's often the case that you have a slice of data containing a string, but
the buffer size is larger than the string; these new methods make that case easy
to handle.

[1]: https://doc.rust-lang.org/std/ffi/struct.CStr.html#method.from_bytes_until_nul
  • Loading branch information
nicholasbishop committed Oct 1, 2024
1 parent 321040e commit 565c3c6
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 1 deletion.
1 change: 1 addition & 0 deletions uefi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ details of the deprecated items that were removed in this release.

## Added
- Impl `PartialEq` and `Eq` for `GptPartitionEntry`.
- Added `CStr16::from_u16_until_nul` and `CStr16::from_char16_until_nul`.

## Changed
- **Breaking:** Deleted the deprecated `BootServices`, `RuntimeServices`, and
Expand Down
128 changes: 127 additions & 1 deletion uefi/src/data_types/strs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,31 @@ use core::slice;
#[cfg(feature = "alloc")]
use super::CString16;

/// Errors which can occur during checked `[uN]` -> `CStrN` conversions
/// Error converting from a slice (which can contain interior nuls) to a string
/// type.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FromSliceUntilNulError {
/// An invalid character was encountered before the end of the slice.
InvalidChar(usize),

/// The does not contain a nul character.
NoNul,
}

impl Display for FromSliceUntilNulError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidChar(usize) => write!(f, "invalid character at index {}", usize),
Self::NoNul => write!(f, "no nul character"),
}
}
}

#[cfg(feature = "unstable")]
impl core::error::Error for FromSliceWithNulError {}

/// Error converting from a slice (which cannot contain interior nuls) to a
/// string type.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FromSliceWithNulError {
/// An invalid character was encountered before the end of the slice
Expand Down Expand Up @@ -337,6 +361,23 @@ impl CStr16 {
Self::from_u16_with_nul_unchecked(slice::from_raw_parts(ptr, len + 1))
}

/// Creates a `&CStr16` from a u16 slice, stopping at the first nul character.
///
/// # Errors
///
/// An error is returned if the slice contains invalid UCS-2 characters, or
/// if the slice does not contain any nul character.
pub fn from_u16_until_nul(codes: &[u16]) -> Result<&Self, FromSliceUntilNulError> {
for (pos, &code) in codes.iter().enumerate() {
let chr =
Char16::try_from(code).map_err(|_| FromSliceUntilNulError::InvalidChar(pos))?;
if chr == NUL_16 {
return Ok(unsafe { Self::from_u16_with_nul_unchecked(&codes[..=pos]) });
}
}
Err(FromSliceUntilNulError::NoNul)
}

/// Creates a `&CStr16` from a u16 slice, if the slice contains exactly
/// one terminating null-byte and all chars are valid UCS-2 chars.
pub fn from_u16_with_nul(codes: &[u16]) -> Result<&Self, FromSliceWithNulError> {
Expand Down Expand Up @@ -369,6 +410,22 @@ impl CStr16 {
&*(codes as *const [u16] as *const Self)
}

/// Creates a `&CStr16` from a [`Char16`] slice, stopping at the first nul character.
///
/// # Errors
///
/// An error is returned if the slice does not contain any nul character.
pub fn from_char16_until_nul(chars: &[Char16]) -> Result<&Self, FromSliceUntilNulError> {
// Find the index of the first null char.
let end = chars
.iter()
.position(|c| *c == NUL_16)
.ok_or(FromSliceUntilNulError::NoNul)?;

// Safety: the input is nul-terminated.
unsafe { Ok(Self::from_char16_with_nul_unchecked(&chars[..=end])) }
}

/// 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> {
Expand Down Expand Up @@ -734,6 +791,75 @@ mod tests {
assert_eq!(s.num_bytes(), 8);
}

#[test]
fn test_cstr16_from_u16_until_nul() {
// Invalid: empty input.
assert_eq!(
CStr16::from_u16_until_nul(&[]),
Err(FromSliceUntilNulError::NoNul)
);

// Invalid: no nul.
assert_eq!(
CStr16::from_u16_until_nul(&[65, 66]),
Err(FromSliceUntilNulError::NoNul)
);

// Invalid: not UCS-2.
assert_eq!(
CStr16::from_u16_until_nul(&[65, 0xde01, 0]),
Err(FromSliceUntilNulError::InvalidChar(1))
);

// Valid: trailing nul.
assert_eq!(CStr16::from_u16_until_nul(&[97, 98, 0,]), Ok(cstr16!("ab")));

// Valid: interior nul.
assert_eq!(
CStr16::from_u16_until_nul(&[97, 0, 98, 0,]),
Ok(cstr16!("a"))
);
}

#[test]
fn test_cstr16_from_char16_until_nul() {
// Invalid: empty input.
assert_eq!(
CStr16::from_char16_until_nul(&[]),
Err(FromSliceUntilNulError::NoNul)
);

// Invalid: no nul character.
assert_eq!(
CStr16::from_char16_until_nul(&[
Char16::try_from('a').unwrap(),
Char16::try_from('b').unwrap(),
]),
Err(FromSliceUntilNulError::NoNul)
);

// Valid: trailing nul.
assert_eq!(
CStr16::from_char16_until_nul(&[
Char16::try_from('a').unwrap(),
Char16::try_from('b').unwrap(),
NUL_16,
]),
Ok(cstr16!("ab"))
);

// Valid: interior nul.
assert_eq!(
CStr16::from_char16_until_nul(&[
Char16::try_from('a').unwrap(),
NUL_16,
Char16::try_from('b').unwrap(),
NUL_16
]),
Ok(cstr16!("a"))
);
}

#[test]
fn test_cstr16_from_char16_with_nul() {
// Invalid: empty input.
Expand Down

0 comments on commit 565c3c6

Please sign in to comment.