Skip to content

Commit 09f7b40

Browse files
Merge pull request #1915 from rust-osdev/var-to-cstr16
runtime: add `CStr16::from_bytes_with_nul` for UCS-2 strings in UEFI Variables
2 parents 3244fd0 + 2be7c63 commit 09f7b40

3 files changed

Lines changed: 131 additions & 1 deletion

File tree

uefi-test-runner/src/runtime/vars.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ fn test_variables() {
6666
);
6767
// Variable is no longer present in the `variable_keys` iterator.
6868
assert!(!find_by_key());
69+
70+
// Test `get_variable` plus conversion to UEFI string
71+
let value_cstr16 = cstr16!("Hello World");
72+
runtime::set_variable(NAME, VENDOR, ATTRS, value_cstr16.as_bytes()).unwrap();
73+
let (data, attrs) = runtime::get_variable_boxed(NAME, VENDOR).expect("failed to get variable");
74+
let retrieved = CStr16::from_bytes_with_nul(&data).expect("should be valid UCS2 string");
75+
assert_eq!(value_cstr16, retrieved);
76+
assert_eq!(attrs, ATTRS);
6977
}
7078

7179
fn test_variable_info() {

uefi/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
common use-case of querying more information about a handle.
2121
- Added `fs::path::Path::join()`.
2222
- Added `Serial::read_exact()` and `Serial::write_exact()`
23+
- `CStr16::from_bytes_with_nul()`: This is especially useful to transform the
24+
retrieved value from a UEFI variable into a UCS2 (CStr16) string.
2325

2426
## Changed
2527
- export all `text::{input, output}::*` types

uefi/src/data_types/strs.rs

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ pub enum FromSliceWithNulError {
5151

5252
/// The slice was not null-terminated
5353
NotNulTerminated,
54+
55+
/// Slice is not aligned to a 16-bit boundary.
56+
NotAligned,
5457
}
5558

5659
impl Display for FromSliceWithNulError {
@@ -59,6 +62,7 @@ impl Display for FromSliceWithNulError {
5962
Self::InvalidChar(usize) => write!(f, "invalid character at index {usize}"),
6063
Self::InteriorNul(usize) => write!(f, "interior null character at index {usize}"),
6164
Self::NotNulTerminated => write!(f, "not null-terminated"),
65+
Self::NotAligned => write!(f, "slice is not aligned to a 16-bit boundary"),
6266
}
6367
}
6468
}
@@ -505,7 +509,7 @@ impl CStr16 {
505509
Self::from_u16_with_nul(&buf[..index + 1]).map_err(|err| match err {
506510
FromSliceWithNulError::InvalidChar(p) => FromStrWithBufError::InvalidChar(p),
507511
FromSliceWithNulError::InteriorNul(p) => FromStrWithBufError::InteriorNul(p),
508-
FromSliceWithNulError::NotNulTerminated => {
512+
FromSliceWithNulError::NotNulTerminated | FromSliceWithNulError::NotAligned => {
509513
unreachable!()
510514
}
511515
})
@@ -533,9 +537,61 @@ impl CStr16 {
533537
FromSliceWithNulError::InvalidChar(v) => UnalignedCStr16Error::InvalidChar(v),
534538
FromSliceWithNulError::InteriorNul(v) => UnalignedCStr16Error::InteriorNul(v),
535539
FromSliceWithNulError::NotNulTerminated => UnalignedCStr16Error::NotNulTerminated,
540+
// input is aligned
541+
FromSliceWithNulError::NotAligned => unreachable!(),
536542
})
537543
}
538544

545+
/// Creates a `&CStr16` from a byte slice.
546+
///
547+
/// The byte slice is reinterpreted as a `u16` slice and validated as a
548+
/// null-terminated UCS-2 string. The slice must satisfy the following
549+
/// requirements:
550+
///
551+
/// - Its length must be even (every `u16` is two bytes).
552+
/// - Its starting address must be 2-byte aligned.
553+
/// - It must be null-terminated with no interior null characters.
554+
/// - All `u16` values must be valid UCS-2 characters.
555+
///
556+
/// # Errors
557+
///
558+
/// See [`FromSliceWithNulError`].
559+
///
560+
/// # Examples
561+
///
562+
/// ```
563+
/// use uefi::CStr16;
564+
///
565+
/// let mut aligned_buf = [0u16; 3];
566+
/// // "AB\0" as native-endian u16 bytes
567+
/// aligned_buf[0] = b'A' as u16;
568+
/// aligned_buf[1] = b'B' as u16;
569+
/// aligned_buf[2] = 0;
570+
///
571+
/// let bytes: &[u8] = unsafe {
572+
/// core::slice::from_raw_parts(aligned_buf.as_ptr().cast::<u8>(), 6)
573+
/// };
574+
///
575+
/// let s = CStr16::from_bytes_with_nul(bytes).unwrap();
576+
/// assert_eq!(s.to_u16_slice_with_nul(), &[b'A' as u16, b'B' as u16, 0]);
577+
/// ```
578+
pub fn from_bytes_with_nul(bytes: &[u8]) -> Result<&Self, FromSliceWithNulError> {
579+
if !bytes.len().is_multiple_of(size_of::<Char16>()) || !bytes.ends_with(&[0, 0]) {
580+
return Err(FromSliceWithNulError::NotNulTerminated);
581+
}
582+
583+
// Unlikely: Most UEFI buffers are 8 byte aligned
584+
if bytes.as_ptr().align_offset(2) != 0 {
585+
return Err(FromSliceWithNulError::NotAligned);
586+
}
587+
588+
// Safety: length is even and pointer is 2-byte aligned.
589+
let u16_slice =
590+
unsafe { slice::from_raw_parts(bytes.as_ptr().cast::<u16>(), bytes.len() / 2) };
591+
592+
Self::from_u16_with_nul(u16_slice)
593+
}
594+
539595
/// Returns the inner pointer to this C16 string.
540596
#[must_use]
541597
pub const fn as_ptr(&self) -> *const Char16 {
@@ -1124,4 +1180,68 @@ mod tests {
11241180
assert!(String::from("test").eq_str_until_nul(input));
11251181
assert!("test".eq_str_until_nul(input));
11261182
}
1183+
1184+
#[test]
1185+
fn test_cstr16_from_bytes_with_nul() {
1186+
// Valid: "AB\0"
1187+
let aligned: &[u16] = &[b'A' as u16, b'B' as u16, 0];
1188+
let bytes = unsafe { slice::from_raw_parts(aligned.as_ptr().cast::<u8>(), 6) };
1189+
let s = CStr16::from_bytes_with_nul(bytes).unwrap();
1190+
assert_eq!(s.to_u16_slice_with_nul(), &[65, 66, 0]);
1191+
1192+
// Invalid: odd number of bytes.
1193+
let aligned: &[u16] = &[b'A' as u16, 0];
1194+
let bytes = unsafe { slice::from_raw_parts(aligned.as_ptr().cast::<u8>(), 3) };
1195+
assert_eq!(
1196+
CStr16::from_bytes_with_nul(bytes),
1197+
Err(FromSliceWithNulError::NotNulTerminated)
1198+
);
1199+
1200+
// Invalid: not null-terminated (last u16 is not NUL).
1201+
let aligned: &[u16] = &[b'A' as u16, b'B' as u16];
1202+
let bytes = unsafe { slice::from_raw_parts(aligned.as_ptr().cast::<u8>(), 4) };
1203+
assert_eq!(
1204+
CStr16::from_bytes_with_nul(bytes),
1205+
Err(FromSliceWithNulError::NotNulTerminated)
1206+
);
1207+
1208+
// Invalid: only the high byte of the terminator is zero (half-null).
1209+
let aligned: &[u16] = &[b'A' as u16, 0x0100];
1210+
let bytes = unsafe { slice::from_raw_parts(aligned.as_ptr().cast::<u8>(), 4) };
1211+
assert_eq!(
1212+
CStr16::from_bytes_with_nul(bytes),
1213+
Err(FromSliceWithNulError::NotNulTerminated)
1214+
);
1215+
1216+
// Invalid: interior null.
1217+
let aligned: &[u16] = &[b'A' as u16, 0, b'B' as u16, 0];
1218+
let bytes = unsafe { slice::from_raw_parts(aligned.as_ptr().cast::<u8>(), 8) };
1219+
assert_eq!(
1220+
CStr16::from_bytes_with_nul(bytes),
1221+
Err(FromSliceWithNulError::InteriorNul(1))
1222+
);
1223+
1224+
// Invalid: invalid UCS-2 character.
1225+
let aligned: &[u16] = &[0xd800, 0];
1226+
let bytes = unsafe { slice::from_raw_parts(aligned.as_ptr().cast::<u8>(), 4) };
1227+
assert_eq!(
1228+
CStr16::from_bytes_with_nul(bytes),
1229+
Err(FromSliceWithNulError::InvalidChar(0))
1230+
);
1231+
1232+
// Invalid: misaligned pointer. We simulate this by taking a byte slice
1233+
// starting one byte into an aligned u16 buffer.
1234+
let aligned: &[u16] = &[b'A' as u16, 0, 0];
1235+
let bytes = unsafe { slice::from_raw_parts(aligned.as_ptr().cast::<u8>().add(1), 4) };
1236+
assert_eq!(
1237+
CStr16::from_bytes_with_nul(bytes),
1238+
Err(FromSliceWithNulError::NotAligned)
1239+
);
1240+
1241+
// Edge case: empty string (just a null terminator).
1242+
let aligned: &[u16] = &[0];
1243+
let bytes = unsafe { slice::from_raw_parts(aligned.as_ptr().cast::<u8>(), 2) };
1244+
let s = CStr16::from_bytes_with_nul(bytes).unwrap();
1245+
assert!(s.is_empty());
1246+
}
11271247
}

0 commit comments

Comments
 (0)