Skip to content

Commit 7817241

Browse files
HadrienG2GabrielMajeri
authored andcommitted
Encode UEFI's peculiar string handling in the type system (#61)
* Add Unicode character types and faillible conversion from Rust characters * Add CStr-like types for UEFI strings, use them for text output
1 parent c2c1bb8 commit 7817241

File tree

9 files changed

+312
-26
lines changed

9 files changed

+312
-26
lines changed

src/data_types/chars.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//! UEFI character handling
2+
//!
3+
//! UEFI uses both Latin-1 and UCS-2 character encoding, this module implements
4+
//! support for the associated character types.
5+
6+
use core::convert::{TryFrom, TryInto};
7+
use core::fmt;
8+
9+
/// Character conversion error
10+
pub struct CharConversionError;
11+
12+
/// A Latin-1 character
13+
#[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)]
14+
#[repr(transparent)]
15+
pub struct Char8(u8);
16+
17+
impl TryFrom<char> for Char8 {
18+
type Error = CharConversionError;
19+
20+
fn try_from(value: char) -> Result<Self, Self::Error> {
21+
let code_point = value as u32;
22+
if code_point <= 0xff {
23+
Ok(Char8(code_point as u8))
24+
} else {
25+
Err(CharConversionError)
26+
}
27+
}
28+
}
29+
30+
impl Into<char> for Char8 {
31+
fn into(self) -> char {
32+
self.0 as char
33+
}
34+
}
35+
36+
impl From<u8> for Char8 {
37+
fn from(value: u8) -> Self {
38+
Char8(value)
39+
}
40+
}
41+
42+
impl Into<u8> for Char8 {
43+
fn into(self) -> u8 {
44+
self.0 as u8
45+
}
46+
}
47+
48+
impl fmt::Debug for Char8 {
49+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50+
<char as fmt::Debug>::fmt(&From::from(self.0), f)
51+
}
52+
}
53+
54+
impl fmt::Display for Char8 {
55+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
56+
<char as fmt::Display>::fmt(&From::from(self.0), f)
57+
}
58+
}
59+
60+
/// Latin-1 version of the NUL character
61+
pub const NUL_8: Char8 = Char8(0);
62+
63+
/// An UCS-2 code point
64+
#[derive(Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)]
65+
#[repr(transparent)]
66+
pub struct Char16(u16);
67+
68+
impl TryFrom<char> for Char16 {
69+
type Error = CharConversionError;
70+
71+
fn try_from(value: char) -> Result<Self, Self::Error> {
72+
let code_point = value as u32;
73+
if code_point <= 0xffff {
74+
Ok(Char16(code_point as u16))
75+
} else {
76+
Err(CharConversionError)
77+
}
78+
}
79+
}
80+
81+
impl Into<char> for Char16 {
82+
fn into(self) -> char {
83+
u32::from(self.0).try_into().unwrap()
84+
}
85+
}
86+
87+
impl TryFrom<u16> for Char16 {
88+
type Error = CharConversionError;
89+
90+
fn try_from(value: u16) -> Result<Self, Self::Error> {
91+
// We leverage char's TryFrom<u32> impl for Unicode validity checking
92+
let res: Result<char, _> = u32::from(value).try_into();
93+
if let Ok(ch) = res {
94+
ch.try_into()
95+
} else {
96+
Err(CharConversionError)
97+
}
98+
}
99+
}
100+
101+
impl Into<u16> for Char16 {
102+
fn into(self) -> u16 {
103+
self.0 as u16
104+
}
105+
}
106+
107+
impl fmt::Debug for Char16 {
108+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109+
if let Ok(c) = u32::from(self.0).try_into() {
110+
<char as fmt::Debug>::fmt(&c, f)
111+
} else {
112+
write!(f, "Char16({:?})", self.0)
113+
}
114+
}
115+
}
116+
117+
impl fmt::Display for Char16 {
118+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119+
if let Ok(c) = u32::from(self.0).try_into() {
120+
<char as fmt::Display>::fmt(&c, f)
121+
} else {
122+
write!(f, "{}", core::char::REPLACEMENT_CHARACTER)
123+
}
124+
}
125+
}
126+
127+
/// UCS-2 version of the NUL character
128+
pub const NUL_16: Char16 = Char16(0);

src/data_types/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
//! Data type definitions
2+
//!
3+
//! This module defines the basic data types that are used throughout uefi-rs
4+
15
use core::ffi::c_void;
26

37
/// Opaque handle to an UEFI entity (protocol, image...)
@@ -13,5 +17,11 @@ pub struct Event(*mut c_void);
1317
mod guid;
1418
pub use self::guid::Guid;
1519

20+
pub mod chars;
21+
pub use self::chars::{Char16, Char8};
22+
1623
#[macro_use]
1724
mod enums;
25+
26+
mod strs;
27+
pub use self::strs::{CStr16, CStr8};

src/data_types/strs.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use super::chars::{Char16, Char8, NUL_16, NUL_8};
2+
use core::convert::TryInto;
3+
use core::result::Result;
4+
use core::slice;
5+
6+
/// Errors which can occur during checked [uN] -> CStrN conversions
7+
pub enum FromSliceWithNulError {
8+
/// An invalid character was encountered before the end of the slice
9+
InvalidChar(usize),
10+
11+
/// A null character was encountered before the end of the slice
12+
InteriorNul(usize),
13+
14+
/// The slice was not null-terminated
15+
NotNulTerminated,
16+
}
17+
18+
/// A Latin-1 null-terminated string
19+
///
20+
/// This type is largely inspired by std::ffi::CStr, see documentation of CStr
21+
/// for more details on its semantics.
22+
///
23+
#[repr(transparent)]
24+
pub struct CStr8([Char8]);
25+
26+
impl CStr8 {
27+
/// Wraps a raw UEFI string with a safe C string wrapper
28+
pub unsafe fn from_ptr<'a>(ptr: *const Char8) -> &'a Self {
29+
let mut len = 0;
30+
while *ptr.add(len) != NUL_8 {
31+
len += 1
32+
}
33+
let ptr = ptr as *const u8;
34+
Self::from_bytes_with_nul_unchecked(slice::from_raw_parts(ptr, len + 1))
35+
}
36+
37+
/// Creates a C string wrapper from bytes
38+
pub fn from_bytes_with_nul(chars: &[u8]) -> Result<&Self, FromSliceWithNulError> {
39+
let nul_pos = chars.iter().position(|&c| c == 0);
40+
if let Some(nul_pos) = nul_pos {
41+
if nul_pos + 1 != chars.len() {
42+
return Err(FromSliceWithNulError::InteriorNul(nul_pos));
43+
}
44+
Ok(unsafe { Self::from_bytes_with_nul_unchecked(chars) })
45+
} else {
46+
Err(FromSliceWithNulError::NotNulTerminated)
47+
}
48+
}
49+
50+
/// Unsafely creates a C string wrapper from bytes
51+
pub unsafe fn from_bytes_with_nul_unchecked(chars: &[u8]) -> &Self {
52+
&*(chars as *const [u8] as *const Self)
53+
}
54+
55+
/// Returns the inner pointer to this C string
56+
pub fn as_ptr(&self) -> *const Char8 {
57+
self.0.as_ptr()
58+
}
59+
60+
/// Converts this C string to a slice of bytes
61+
pub fn to_bytes(&self) -> &[u8] {
62+
let chars = self.to_bytes_with_nul();
63+
&chars[..chars.len() - 1]
64+
}
65+
66+
/// Converts this C string to a slice of bytes containing the trailing 0 char
67+
pub fn to_bytes_with_nul(&self) -> &[u8] {
68+
unsafe { &*(&self.0 as *const [Char8] as *const [u8]) }
69+
}
70+
}
71+
72+
/// An UCS-2 null-terminated string
73+
///
74+
/// This type is largely inspired by std::ffi::CStr, see documentation of CStr
75+
/// for more details on its semantics.
76+
///
77+
#[repr(transparent)]
78+
pub struct CStr16([Char16]);
79+
80+
impl CStr16 {
81+
/// Wraps a raw UEFI string with a safe C string wrapper
82+
pub unsafe fn from_ptr<'a>(ptr: *const Char16) -> &'a Self {
83+
let mut len = 0;
84+
while *ptr.add(len) != NUL_16 {
85+
len += 1
86+
}
87+
let ptr = ptr as *const u16;
88+
Self::from_u16_with_nul_unchecked(slice::from_raw_parts(ptr, len + 1))
89+
}
90+
91+
/// Creates a C string wrapper from a u16 slice
92+
///
93+
/// Since not every u16 value is a valid UCS-2 code point, this function
94+
/// must do a bit more validity checking than CStr::from_bytes_with_nul
95+
pub fn from_u16_with_nul(codes: &[u16]) -> Result<&Self, FromSliceWithNulError> {
96+
for (pos, &code) in codes.iter().enumerate() {
97+
match code.try_into() {
98+
Ok(NUL_16) => {
99+
if pos != codes.len() - 1 {
100+
return Err(FromSliceWithNulError::InteriorNul(pos));
101+
} else {
102+
return Ok(unsafe { Self::from_u16_with_nul_unchecked(codes) });
103+
}
104+
}
105+
Err(_) => {
106+
return Err(FromSliceWithNulError::InvalidChar(pos));
107+
}
108+
_ => {}
109+
}
110+
}
111+
Err(FromSliceWithNulError::NotNulTerminated)
112+
}
113+
114+
/// Unsafely creates a C string wrapper from a u16 slice.
115+
pub unsafe fn from_u16_with_nul_unchecked(codes: &[u16]) -> &Self {
116+
&*(codes as *const [u16] as *const Self)
117+
}
118+
119+
/// Returns the inner pointer to this C string
120+
pub fn as_ptr(&self) -> *const Char16 {
121+
self.0.as_ptr()
122+
}
123+
124+
/// Converts this C string to a u16 slice
125+
pub fn to_u16_slice(&self) -> &[u16] {
126+
let chars = self.to_u16_slice_with_nul();
127+
&chars[..chars.len() - 1]
128+
}
129+
130+
/// Converts this C string to a u16 slice containing the trailing 0 char
131+
pub fn to_u16_slice_with_nul(&self) -> &[u16] {
132+
unsafe { &*(&self.0 as *const [Char16] as *const [u16]) }
133+
}
134+
}

src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,16 @@
2424
//! therefore all the network protocols will be unavailable.
2525
2626
#![feature(optin_builtin_traits)]
27+
#![feature(try_from)]
2728
#![feature(try_trait)]
2829
#![no_std]
2930
// Enable some additional warnings and lints.
3031
#![warn(missing_docs, unused)]
3132
#![deny(clippy::all)]
3233

3334
#[macro_use]
34-
mod data_types;
35-
pub use self::data_types::{Event, Guid, Handle};
35+
pub mod data_types;
36+
pub use self::data_types::{CStr16, CStr8, Char16, Char8, Event, Guid, Handle};
3637

3738
mod error;
3839
pub use self::error::{Completion, Result, ResultExt, Status};

src/proto/console/text/input.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use core::mem;
2-
use crate::{Event, Result, Status};
2+
use crate::{Char16, Event, Result, Status};
33

44
/// Interface for text-based input devices.
55
#[repr(C)]
@@ -54,7 +54,7 @@ pub struct Key {
5454
pub scan_code: ScanCode,
5555
/// Associated Unicode character,
5656
/// or 0 if not printable.
57-
pub unicode_char: u16,
57+
pub unicode_char: Char16,
5858
}
5959

6060
newtype_enum! {

src/proto/console/text/output.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use core::fmt;
22
use crate::prelude::*;
3-
use crate::{Completion, Result, Status};
3+
use crate::{CStr16, Char16, Completion, Result, Status};
44

55
/// Interface for text-based output devices.
66
///
@@ -9,8 +9,8 @@ use crate::{Completion, Result, Status};
99
#[repr(C)]
1010
pub struct Output {
1111
reset: extern "win64" fn(this: &Output, extended: bool) -> Status,
12-
output_string: extern "win64" fn(this: &Output, string: *const u16) -> Status,
13-
test_string: extern "win64" fn(this: &Output, string: *const u16) -> Status,
12+
output_string: extern "win64" fn(this: &Output, string: *const Char16) -> Status,
13+
test_string: extern "win64" fn(this: &Output, string: *const Char16) -> Status,
1414
query_mode: extern "win64" fn(this: &Output, mode: i32, columns: &mut usize, rows: &mut usize)
1515
-> Status,
1616
set_mode: extern "win64" fn(this: &mut Output, mode: i32) -> Status,
@@ -36,17 +36,17 @@ impl Output {
3636
}
3737

3838
/// Writes a string to the output device.
39-
pub fn output_string(&mut self, string: *const u16) -> Result<()> {
40-
(self.output_string)(self, string).into()
39+
pub fn output_string(&mut self, string: &CStr16) -> Result<()> {
40+
(self.output_string)(self, string.as_ptr()).into()
4141
}
4242

4343
/// Checks if a string contains only supported characters.
4444
/// True indicates success.
4545
///
4646
/// UEFI applications are encouraged to try to print a string even if it contains
4747
/// some unsupported characters.
48-
pub fn test_string(&mut self, string: *const u16) -> bool {
49-
match (self.test_string)(self, string) {
48+
pub fn test_string(&mut self, string: &CStr16) -> bool {
49+
match (self.test_string)(self, string.as_ptr()) {
5050
Status::SUCCESS => true,
5151
_ => false,
5252
}
@@ -146,9 +146,12 @@ impl fmt::Write for Output {
146146
// This closure writes the local buffer to the output and resets the buffer.
147147
let mut flush_buffer = |buf: &mut [u16], i: &mut usize| {
148148
buf[*i] = 0;
149+
let codes = &buf[..=*i];
149150
*i = 0;
150151

151-
self.output_string(buf.as_ptr())
152+
let text = CStr16::from_u16_with_nul(codes).map_err(|_| fmt::Error)?;
153+
154+
self.output_string(text)
152155
.warning_as_error()
153156
.map_err(|_| fmt::Error)
154157
};

0 commit comments

Comments
 (0)