Skip to content

Commit

Permalink
cstr[ing]16: convenience functions
Browse files Browse the repository at this point in the history
  • Loading branch information
phip1611 committed Apr 3, 2023
1 parent 739bd8e commit e93168b
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 8 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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]

Expand Down
13 changes: 12 additions & 1 deletion uefi/src/data_types/chars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<char> for Char16 {
type Error = CharConversionError;

Expand Down Expand Up @@ -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) };
123 changes: 122 additions & 1 deletion uefi/src/data_types/owned_strs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ 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::{fmt, ops};

Expand Down Expand Up @@ -47,9 +49,73 @@ 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<Char16>);

impl CString16 {
/// Creates a new empty string with a null-byte.
#[must_use]
pub fn new() -> Self {
Self(vec![NUL_16])
}

/// Inserts a character at the end of the string, right before the null
/// character.
///
/// # Panics
/// Panics if the char is a null character.
pub fn push(&mut self, char: Char16) {
assert_ne!(char, NUL_16, "Pushing a null-character is illegal");
let last_elem = self
.0
.last_mut()
.expect("There should be at least a null character");
*last_elem = char;
self.0.push(NUL_16);
}

/// Extends the string with the given [`CStr16`]. The null character is
/// automatically kept at the end.
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 replace_char(&mut self, search: Char16, replace: Char16) {
assert_ne!(search, NUL_16, "Replacing a null character is illegal");
assert_ne!(
replace, NUL_16,
"Replacing with a null character is illegal"
);
self.0
.as_mut_slice()
.iter_mut()
.filter(|char| **char == search)
.for_each(|char| *char = replace);
}

/// Returns the number of characters without the trailing null character.
#[must_use]
pub fn num_chars(&self) -> usize {
self.0.len() - 1
}

/// Returns if the string is empty. This ignores the null character.
#[must_use]
pub fn is_empty(&self) -> bool {
self.num_chars() == 0
}
}

impl Default for CString16 {
fn default() -> Self {
CString16::new()
}
}

impl TryFrom<&str> for CString16 {
type Error = FromStrError;

Expand Down Expand Up @@ -112,6 +178,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<CString16, FromSliceWithNulError> {
Expand Down Expand Up @@ -256,4 +336,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.num_chars(), 0);
str1.push(Char16::try_from('h').unwrap());
str1.push(Char16::try_from('i').unwrap());
assert_eq!(str1.num_chars(), 2);

let mut str2 = CString16::new();
str2.push(Char16::try_from('!').unwrap());

str2.push_str(str1.as_ref());
assert_eq!(str2.num_chars(), 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.replace_char(search, replace);

let input = String::from(&input);
assert_eq!(input, "foo\\bar\\foobar\\\\")
}
}
54 changes: 48 additions & 6 deletions uefi/src/data_types/strs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,26 +309,32 @@ 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 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]) }
Expand All @@ -343,7 +349,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
Expand Down Expand Up @@ -373,6 +391,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::<alloc::string::String>()
}
}

impl<StrType: AsRef<str> + ?Sized> EqStrUntilNul<StrType> for CStr16 {
fn eq_str_until_nul(&self, other: &StrType) -> bool {
let other = other.as_ref();
Expand All @@ -391,7 +423,7 @@ impl<StrType: AsRef<str> + ?Sized> EqStrUntilNul<StrType> for CStr16 {
}
}

/// An iterator over `CStr16`.
/// An iterator over the [`Char16`]s in a [`CStr16`].
#[derive(Debug)]
pub struct CStr16Iter<'a> {
inner: &'a CStr16,
Expand Down Expand Up @@ -575,6 +607,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)]
Expand Down

0 comments on commit e93168b

Please sign in to comment.