Skip to content

Commit 957b31d

Browse files
committed
CStr8 cleanup and enhancements
- CStr8 can now be constructed from core::ffi::CStr - CStr8 now also implements EqStrUntilNul
1 parent 65418c7 commit 957b31d

File tree

2 files changed

+116
-32
lines changed

2 files changed

+116
-32
lines changed

CHANGELOG.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
- Implemented `core::fmt::Write` for the `Serial` protocol.
1111
- Added the `MemoryProtection` protocol.
1212
- Added `BootServices::get_handle_for_protocol`.
13-
- Added trait `EqStrUntilNul` and implemented it for `CStr16` and `CString16`.
14-
Now you can compare everything that is `AsRef<str>` (such as `String` and `str`
15-
from the standard library) to uefi strings. Please head to the documentation of
16-
`EqStrUntilNul` to find out limitations and further information.
13+
- Added trait `EqStrUntilNul` and implemented it for `CStr8`, `CStr16`, and `CString16`
14+
(CString8 doesn't exist yet). Now you can compare everything that is `AsRef<str>`
15+
(such as `String` and `str` from the standard library) to UEFI strings. Please head to the
16+
documentation of `EqStrUntilNul` to find out limitations and further information.
1717
- Added `BootServices::image_handle` to get the handle of the executing
1818
image. The image is set automatically by the `#[entry]` macro; if a
1919
program does not use that macro then it should call
@@ -27,6 +27,7 @@
2727
- Added `DiskIo` and `DiskIo2` protocols.
2828
- Added `HardDriveMediaDevicePath` and related types.
2929
- Added `PartialOrd` and `Ord` to the traits derived by `Guid`.
30+
- Added `TryFrom&lt;core::ffi::CStr&gt;` implementation for `CStr8`.
3031

3132
### Fixed
3233

src/data_types/strs.rs

+111-28
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::chars::{Char16, Char8, NUL_16, NUL_8};
2+
use core::ffi::CStr;
23
use core::fmt;
34
use core::iter::Iterator;
45
use core::marker::PhantomData;
@@ -52,21 +53,30 @@ pub enum FromStrWithBufError {
5253
BufferTooSmall,
5354
}
5455

55-
/// A Latin-1 null-terminated string
56+
/// A Latin-1 null-terminated string.
5657
///
57-
/// This type is largely inspired by `std::ffi::CStr`, see the documentation of
58-
/// `CStr` for more details on its semantics.
58+
/// This type is largely inspired by [core::ffi::CStr] with the exception that all characters are
59+
/// guaranteed to be 8 bit long. A [CStr8] can be constructed from a [core::ffi::CStr] via a
60+
/// `try_from` call:
61+
///
62+
/// ```ignore
63+
/// let cstr8: &CStr8 = TryFrom::try_from(cstr).unwrap();
64+
/// ```
65+
///
66+
/// For convenience, a [CStr8] is comparable with `&str` and `String` from the standard library
67+
/// through the trait [EqStrUntilNul].
5968
#[repr(transparent)]
69+
#[derive(Eq, PartialEq)]
6070
pub struct CStr8([Char8]);
6171

6272
impl CStr8 {
63-
/// Wraps a raw UEFI string with a safe C string wrapper
73+
/// Wraps a raw null-terminated 8 bit ASCII (Latin 1) with a CStr8 reference.
6474
///
6575
/// # Safety
6676
///
6777
/// The function will start accessing memory from `ptr` until the first
68-
/// null byte. It's the callers responsability to ensure `ptr` points to
69-
/// a valid string, in accessible memory.
78+
/// null byte. It's the callers responsibility to ensure `ptr` points to
79+
/// a valid null-terminated string in accessible memory.
7080
pub unsafe fn from_ptr<'ptr>(ptr: *const Char8) -> &'ptr Self {
7181
let mut len = 0;
7282
while *ptr.add(len) != NUL_8 {
@@ -76,7 +86,7 @@ impl CStr8 {
7686
Self::from_bytes_with_nul_unchecked(slice::from_raw_parts(ptr, len + 1))
7787
}
7888

79-
/// Creates a C string wrapper from bytes
89+
/// Creates a CStr8 reference from bytes.
8090
pub fn from_bytes_with_nul(chars: &[u8]) -> Result<&Self, FromSliceWithNulError> {
8191
let nul_pos = chars.iter().position(|&c| c == 0);
8292
if let Some(nul_pos) = nul_pos {
@@ -89,7 +99,7 @@ impl CStr8 {
8999
}
90100
}
91101

92-
/// Unsafely creates a C string wrapper from bytes
102+
/// Unsafely creates a CStr8 reference from bytes.
93103
///
94104
/// # Safety
95105
///
@@ -99,23 +109,65 @@ impl CStr8 {
99109
&*(chars as *const [u8] as *const Self)
100110
}
101111

102-
/// Returns the inner pointer to this C string
112+
/// Returns the inner pointer to this CStr8.
103113
pub fn as_ptr(&self) -> *const Char8 {
104114
self.0.as_ptr()
105115
}
106116

107-
/// Converts this C string to a slice of bytes
117+
/// Converts this CStr8 to a slice of bytes without the terminating null byte.
108118
pub fn to_bytes(&self) -> &[u8] {
109119
let chars = self.to_bytes_with_nul();
110120
&chars[..chars.len() - 1]
111121
}
112122

113-
/// Converts this C string to a slice of bytes containing the trailing 0 char
123+
/// Converts this CStr8 to a slice of bytes containing the trailing null byte.
114124
pub fn to_bytes_with_nul(&self) -> &[u8] {
115125
unsafe { &*(&self.0 as *const [Char8] as *const [u8]) }
116126
}
117127
}
118128

129+
impl fmt::Debug for CStr8 {
130+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131+
write!(f, "CStr8({:?})", &self.0)
132+
}
133+
}
134+
135+
impl fmt::Display for CStr8 {
136+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
137+
for c in self.0.iter() {
138+
<Char8 as fmt::Display>::fmt(c, f)?;
139+
}
140+
Ok(())
141+
}
142+
}
143+
144+
impl<StrType: AsRef<str>> EqStrUntilNul<StrType> for CStr8 {
145+
fn eq_str_until_nul(&self, other: &StrType) -> bool {
146+
let other = other.as_ref();
147+
148+
// TODO: CStr16 has .iter() implemented, CStr8 not yet
149+
let any_not_equal = self
150+
.0
151+
.iter()
152+
.copied()
153+
.map(char::from)
154+
.zip(other.chars())
155+
// this only works as CStr8 is guaranteed to have a fixed character length
156+
.take_while(|(l, r)| *l != '\0' && *r != '\0')
157+
.any(|(l, r)| l != r);
158+
159+
!any_not_equal
160+
}
161+
}
162+
163+
impl<'a> TryFrom<&'a CStr> for &'a CStr8 {
164+
type Error = FromSliceWithNulError;
165+
166+
fn try_from(cstr: &'a CStr) -> Result<Self, Self::Error> {
167+
CStr8::from_bytes_with_nul(cstr.to_bytes_with_nul())
168+
}
169+
}
170+
119171
/// An UCS-2 null-terminated string
120172
///
121173
/// This type is largely inspired by `std::ffi::CStr`, see the documentation of
@@ -290,6 +342,7 @@ impl<StrType: AsRef<str>> EqStrUntilNul<StrType> for CStr16 {
290342
.copied()
291343
.map(char::from)
292344
.zip(other.chars())
345+
// this only works as CStr16 is guaranteed to have a fixed character length
293346
.take_while(|(l, r)| *l != '\0' && *r != '\0')
294347
.any(|(l, r)| l != r);
295348

@@ -478,7 +531,17 @@ where
478531
mod tests {
479532
use super::*;
480533
use crate::alloc_api::string::String;
481-
use uefi_macros::cstr16;
534+
use uefi_macros::{cstr16, cstr8};
535+
536+
// Tests if our CStr8 type can be constructed from a valid core::ffi::CStr
537+
#[test]
538+
fn test_cstr8_from_cstr() {
539+
let msg = "hello world\0";
540+
let cstr = unsafe { CStr::from_ptr(msg.as_ptr().cast()) };
541+
let cstr8: &CStr8 = TryFrom::try_from(cstr).unwrap();
542+
assert!(cstr8.eq_str_until_nul(&msg));
543+
assert!(msg.eq_str_until_nul(cstr8));
544+
}
482545

483546
#[test]
484547
fn test_cstr16_num_bytes() {
@@ -567,21 +630,41 @@ mod tests {
567630

568631
#[test]
569632
fn test_compare() {
570-
let input: &CStr16 = cstr16!("test");
571-
572-
// test various comparisons with different order (left, right)
573-
assert!(input.eq_str_until_nul(&"test"));
574-
assert!(input.eq_str_until_nul(&String::from("test")));
575-
576-
// now other direction
577-
assert!(String::from("test").eq_str_until_nul(input));
578-
assert!("test".eq_str_until_nul(input));
579-
580-
// some more tests
581-
// this is fine: compare until the first null
582-
assert!(input.eq_str_until_nul(&"te\0st"));
583-
// this is fine
584-
assert!(input.eq_str_until_nul(&"test\0"));
585-
assert!(!input.eq_str_until_nul(&"hello"));
633+
// twice the same test: once for CStr8, once for CStr16
634+
{
635+
// test various comparisons with different order (left, right)
636+
let input: &CStr8 = cstr8!("test");
637+
assert!(input.eq_str_until_nul(&"test"));
638+
assert!(input.eq_str_until_nul(&String::from("test")));
639+
640+
// now other direction
641+
assert!(String::from("test").eq_str_until_nul(input));
642+
assert!("test".eq_str_until_nul(input));
643+
644+
// some more tests
645+
// this is fine: compare until the first null
646+
assert!(input.eq_str_until_nul(&"te\0st"));
647+
// this is fine
648+
assert!(input.eq_str_until_nul(&"test\0"));
649+
assert!(!input.eq_str_until_nul(&"hello"));
650+
}
651+
{
652+
let input: &CStr16 = cstr16!("test");
653+
654+
// test various comparisons with different order (left, right)
655+
assert!(input.eq_str_until_nul(&"test"));
656+
assert!(input.eq_str_until_nul(&String::from("test")));
657+
658+
// now other direction
659+
assert!(String::from("test").eq_str_until_nul(input));
660+
assert!("test".eq_str_until_nul(input));
661+
662+
// some more tests
663+
// this is fine: compare until the first null
664+
assert!(input.eq_str_until_nul(&"te\0st"));
665+
// this is fine
666+
assert!(input.eq_str_until_nul(&"test\0"));
667+
assert!(!input.eq_str_until_nul(&"hello"));
668+
}
586669
}
587670
}

0 commit comments

Comments
 (0)