Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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+
pubmod 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: *constu16) -> 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: *constu16) -> 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)

AltStyle によって変換されたページ (->オリジナル) /