zfi/
string.rs

1use alloc::borrow::ToOwned;
2use alloc::vec::Vec;
3use core::borrow::Borrow;
4use core::fmt::{Formatter, Write};
5use core::mem::transmute;
6use core::ops::Deref;
7use core::slice::{from_raw_parts, IterMut};
8use core::str::FromStr;
9use thiserror::Error;
10
11/// A borrowed EFI string. The string is always have NUL at the end.
12///
13/// You can use [str](crate::str) macro to create a value of this type.
14#[repr(transparent)]
15#[derive(Debug, PartialEq, Eq)]
16pub struct EfiStr([u16]);
17
18impl EfiStr {
19    /// An empty string with only NUL character.
20    pub const EMPTY: &'static Self = unsafe { Self::new_unchecked(&[0]) };
21
22    /// # Safety
23    /// `data` must be:
24    ///
25    /// - NUL-terminated.
26    /// - Not have any NULs in the middle.
27    /// - Valid UCS-2 (not UTF-16).
28    pub const unsafe fn new_unchecked(data: &[u16]) -> &Self {
29        // SAFETY: This is safe because EfiStr is #[repr(transparent)].
30        &*(data as *const [u16] as *const Self)
31    }
32
33    /// # Safety
34    /// `ptr` must be a valid UCS-2 (not UTF-16) and NUL-terminated.
35    pub unsafe fn from_ptr<'a>(ptr: *const u16) -> &'a Self {
36        let mut len = 0;
37
38        while *ptr.add(len) != 0 {
39            len += 1;
40        }
41
42        Self::new_unchecked(from_raw_parts(ptr, len + 1))
43    }
44
45    /// Returnes a pointer to the first character.
46    pub const fn as_ptr(&self) -> *const u16 {
47        self.0.as_ptr()
48    }
49
50    /// Returnes length of this string without NUL, in character.
51    pub const fn len(&self) -> usize {
52        self.0.len() - 1
53    }
54
55    /// Returns `true` if this string contains only NUL character.
56    pub const fn is_empty(&self) -> bool {
57        self.len() == 0
58    }
59
60    /// Returns object that implement [`core::fmt::Display`] for safely printing string that may
61    /// contain non-Unicode data.
62    pub const fn display(&self) -> impl core::fmt::Display + '_ {
63        Display(self)
64    }
65}
66
67impl AsRef<EfiStr> for EfiStr {
68    fn as_ref(&self) -> &EfiStr {
69        self
70    }
71}
72
73impl AsRef<[u8]> for EfiStr {
74    fn as_ref(&self) -> &[u8] {
75        let ptr = self.0.as_ptr().cast();
76        let len = self.0.len() * 2;
77
78        // SAFETY: This is safe because any alignment of u16 is a valid alignment fo u8.
79        unsafe { from_raw_parts(ptr, len) }
80    }
81}
82
83impl AsRef<[u16]> for EfiStr {
84    fn as_ref(&self) -> &[u16] {
85        &self.0
86    }
87}
88
89impl ToOwned for EfiStr {
90    type Owned = EfiString;
91
92    fn to_owned(&self) -> Self::Owned {
93        EfiString(self.0.to_vec())
94    }
95}
96
97/// Provides [`core::fmt::Display`] to display [`EfiStr`] lossy.
98struct Display<'a>(&'a EfiStr);
99
100impl core::fmt::Display for Display<'_> {
101    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
102        // SAFETY: EfiStr guarantee to have NUL at the end.
103        let mut ptr = self.0.as_ptr();
104
105        while unsafe { *ptr != 0 } {
106            let c = unsafe { *ptr };
107            let c = char::from_u32(c.into()).unwrap_or(char::REPLACEMENT_CHARACTER);
108
109            f.write_char(c)?;
110
111            unsafe { ptr = ptr.add(1) };
112        }
113
114        Ok(())
115    }
116}
117
118/// An owned version of [`EfiStr`]. The string is always have NUL at the end.
119#[derive(Debug)]
120pub struct EfiString(Vec<u16>);
121
122impl EfiString {
123    pub fn push(&mut self, c: EfiChar) {
124        self.0.pop();
125        self.0.push(c.0);
126        self.0.push(0);
127    }
128
129    pub fn push_str<S: AsRef<str>>(&mut self, s: S) -> Result<(), EfiStringError> {
130        let s = s.as_ref();
131        let l = self.0.len();
132
133        self.0.pop();
134
135        for (i, c) in s.chars().enumerate() {
136            let e = match c {
137                '\0' => EfiStringError::HasNul(i),
138                '\u{10000}'.. => EfiStringError::UnsupportedChar(i, c),
139                c => {
140                    self.0.push(c.encode_utf16(&mut [0; 1])[0]);
141                    continue;
142                }
143            };
144
145            unsafe { self.0.set_len(l) };
146            self.0[l - 1] = 0;
147
148            return Err(e);
149        }
150
151        self.0.push(0);
152
153        Ok(())
154    }
155}
156
157impl FromStr for EfiString {
158    type Err = EfiStringError;
159
160    fn from_str(s: &str) -> Result<Self, Self::Err> {
161        let mut v = Self(Vec::with_capacity(s.len() + 1));
162        v.0.push(0);
163        v.push_str(s)?;
164        Ok(v)
165    }
166}
167
168impl Deref for EfiString {
169    type Target = EfiStr;
170
171    fn deref(&self) -> &Self::Target {
172        unsafe { EfiStr::new_unchecked(&self.0) }
173    }
174}
175
176impl AsRef<EfiStr> for EfiString {
177    fn as_ref(&self) -> &EfiStr {
178        self.deref()
179    }
180}
181
182impl Borrow<EfiStr> for EfiString {
183    fn borrow(&self) -> &EfiStr {
184        self.deref()
185    }
186}
187
188impl<'a> IntoIterator for &'a mut EfiString {
189    type Item = &'a mut EfiChar;
190    type IntoIter = IterMut<'a, EfiChar>;
191
192    fn into_iter(self) -> Self::IntoIter {
193        let len = self.0.len() - 1; // Exclude NUL.
194
195        // SAFETY: This is safe because EfiChar is #[repr(transparent)].
196        unsafe { transmute(self.0[..len].iter_mut()) }
197    }
198}
199
200/// A non-NUL character in the EFI string.
201#[repr(transparent)]
202pub struct EfiChar(u16);
203
204impl EfiChar {
205    pub const FULL_STOP: Self = Self(b'.' as u16);
206    pub const REVERSE_SOLIDUS: Self = Self(b'\\' as u16);
207}
208
209impl PartialEq<u8> for EfiChar {
210    fn eq(&self, other: &u8) -> bool {
211        self.0 == (*other).into()
212    }
213}
214
215/// Represents an error when an [`EfiString`] cnostruction is failed.
216#[derive(Debug, Error)]
217pub enum EfiStringError {
218    #[error("the value contains NUL character")]
219    HasNul(usize),
220
221    #[error("the value contains character outside Basic Multilingual Plane")]
222    UnsupportedChar(usize, char),
223}