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 authored and nicholasbishop committed Apr 2, 2023
1 parent 739bd8e commit cfa9d8f
Show file tree
Hide file tree
Showing 4 changed files with 224 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) };
135 changes: 134 additions & 1 deletion uefi/src/data_types/owned_strs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>`].
Expand Down Expand Up @@ -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<Char16>);

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;

Expand Down Expand Up @@ -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<CString16, FromSliceWithNulError> {
Expand All @@ -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<CStr16> for CString16 {
fn as_ref(&self) -> &CStr16 {
self
Expand All @@ -139,6 +219,18 @@ impl Borrow<CStr16> for CString16 {
}
}

impl AsMut<CStr16> for CString16 {
fn as_mut(&mut self) -> &mut CStr16 {
self
}
}

impl BorrowMut<CStr16> for CString16 {
fn borrow_mut(&mut self) -> &mut CStr16 {
self
}
}

impl ToOwned for CStr16 {
type Owned = CString16;

Expand Down Expand Up @@ -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\\\\")
}
}
69 changes: 63 additions & 6 deletions uefi/src/data_types/strs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]) }
Expand All @@ -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
Expand Down Expand Up @@ -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::<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 +438,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 +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)]
Expand Down

0 comments on commit cfa9d8f

Please sign in to comment.