• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use super::chars::{Char16, NUL_16};
2 use super::strs::{CStr16, FromSliceWithNulError};
3 use crate::data_types::strs::EqStrUntilNul;
4 use crate::data_types::UnalignedSlice;
5 use crate::polyfill::vec_into_raw_parts;
6 use alloc::borrow::{Borrow, ToOwned};
7 use alloc::string::String;
8 use alloc::vec;
9 use alloc::vec::Vec;
10 use core::fmt::{self, Display, Formatter};
11 use core::ops;
12 
13 /// Error returned by [`CString16::try_from::<&str>`].
14 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
15 pub enum FromStrError {
16     /// Character conversion error.
17     InvalidChar,
18     /// Nul character found in the input.
19     InteriorNul,
20 }
21 
22 impl Display for FromStrError {
fmt(&self, f: &mut Formatter<'_>) -> fmt::Result23     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
24         write!(
25             f,
26             "UCS-2 Conversion Error: {}",
27             match self {
28                 Self::InvalidChar => "Invalid character",
29                 Self::InteriorNul => "Interior null terminator",
30             }
31         )
32     }
33 }
34 
35 #[cfg(feature = "unstable")]
36 impl core::error::Error for FromStrError {}
37 
38 /// An owned UCS-2 null-terminated string.
39 ///
40 /// For convenience, a [`CString16`] is comparable with `&str` and `String` from
41 /// the standard library through the trait [`EqStrUntilNul`].
42 ///
43 /// # Examples
44 ///
45 /// Round-trip conversion from a [`&str`] to a `CString16` and back:
46 ///
47 /// ```
48 /// use uefi::CString16;
49 ///
50 /// let s = CString16::try_from("abc").unwrap();
51 /// assert_eq!(s.to_string(), "abc");
52 /// ```
53 #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
54 pub struct CString16(Vec<Char16>);
55 
56 impl CString16 {
57     /// Creates a new empty string with a terminating null character.
58     #[must_use]
new() -> Self59     pub fn new() -> Self {
60         Self(vec![NUL_16])
61     }
62 
63     /// Inserts a character at the end of the string, right before the null
64     /// character.
65     ///
66     /// # Panics
67     /// Panics if the char is a null character.
push(&mut self, char: Char16)68     pub fn push(&mut self, char: Char16) {
69         assert_ne!(char, NUL_16, "Pushing a null-character is illegal");
70         let last_elem = self
71             .0
72             .last_mut()
73             .expect("There should be at least a null character");
74         *last_elem = char;
75         self.0.push(NUL_16);
76     }
77 
78     /// Extends the string with the given [`CStr16`]. The null character is
79     /// automatically kept at the end.
push_str(&mut self, str: &CStr16)80     pub fn push_str(&mut self, str: &CStr16) {
81         str.as_slice()
82             .iter()
83             .copied()
84             .for_each(|char| self.push(char));
85     }
86 
87     /// Replaces all chars in the string with the replace value in-place.
replace_char(&mut self, search: Char16, replace: Char16)88     pub fn replace_char(&mut self, search: Char16, replace: Char16) {
89         assert_ne!(search, NUL_16, "Replacing a null character is illegal");
90         assert_ne!(
91             replace, NUL_16,
92             "Replacing with a null character is illegal"
93         );
94         self.0
95             .as_mut_slice()
96             .iter_mut()
97             .filter(|char| **char == search)
98             .for_each(|char| *char = replace);
99     }
100 
101     /// Returns the number of characters without the trailing null character.
102     #[must_use]
num_chars(&self) -> usize103     pub fn num_chars(&self) -> usize {
104         self.0.len() - 1
105     }
106 
107     /// Returns if the string is empty. This ignores the null character.
108     #[must_use]
is_empty(&self) -> bool109     pub fn is_empty(&self) -> bool {
110         self.num_chars() == 0
111     }
112 }
113 
114 impl Default for CString16 {
default() -> Self115     fn default() -> Self {
116         Self::new()
117     }
118 }
119 
120 impl TryFrom<&str> for CString16 {
121     type Error = FromStrError;
122 
try_from(input: &str) -> Result<Self, Self::Error>123     fn try_from(input: &str) -> Result<Self, Self::Error> {
124         // Initially allocate one Char16 for each byte of the input, plus
125         // one for the null character. This should be a good guess for ASCII-ish
126         // input.
127         let mut output = Vec::with_capacity(input.len() + 1);
128 
129         // Convert to UTF-16, then convert to UCS-2.
130         for c in input.encode_utf16() {
131             let c = Char16::try_from(c).map_err(|_| FromStrError::InvalidChar)?;
132 
133             // Check for interior nul chars.
134             if c == NUL_16 {
135                 return Err(FromStrError::InteriorNul);
136             }
137 
138             output.push(c);
139         }
140 
141         // Add trailing nul.
142         output.push(NUL_16);
143 
144         Ok(Self(output))
145     }
146 }
147 
148 impl TryFrom<Vec<u16>> for CString16 {
149     type Error = FromSliceWithNulError;
150 
try_from(input: Vec<u16>) -> Result<Self, Self::Error>151     fn try_from(input: Vec<u16>) -> Result<Self, Self::Error> {
152         // Try creating a CStr16 from the input. We throw away the
153         // result if successful, but it takes care of all the necessary
154         // validity checks (valid UCS-2, ends in null, contains no
155         // interior nulls).
156         CStr16::from_u16_with_nul(&input)?;
157 
158         // Convert the input vector from `u16` to `Char16`.
159         //
160         // Safety: `Char16` is a transparent struct wrapping `u16`, so
161         // the types are compatible. The pattern used here matches the
162         // example in the docs for `into_raw_parts`.
163         let (ptr, len, cap) = vec_into_raw_parts(input);
164         let rebuilt = unsafe {
165             let ptr = ptr.cast::<Char16>();
166             Vec::from_raw_parts(ptr, len, cap)
167         };
168 
169         Ok(Self(rebuilt))
170     }
171 }
172 
173 impl<'a> TryFrom<&UnalignedSlice<'a, u16>> for CString16 {
174     type Error = FromSliceWithNulError;
175 
try_from(input: &UnalignedSlice<u16>) -> Result<Self, Self::Error>176     fn try_from(input: &UnalignedSlice<u16>) -> Result<Self, Self::Error> {
177         let v = input.to_vec();
178         Self::try_from(v)
179     }
180 }
181 
182 impl From<&CStr16> for CString16 {
from(value: &CStr16) -> Self183     fn from(value: &CStr16) -> Self {
184         let vec = value.as_slice_with_nul().to_vec();
185         Self(vec)
186     }
187 }
188 
189 impl From<&CString16> for String {
from(value: &CString16) -> Self190     fn from(value: &CString16) -> Self {
191         let slice: &CStr16 = value.as_ref();
192         Self::from(slice)
193     }
194 }
195 
196 impl<'a> UnalignedSlice<'a, u16> {
197     /// Copies `self` to a new [`CString16`].
to_cstring16(&self) -> Result<CString16, FromSliceWithNulError>198     pub fn to_cstring16(&self) -> Result<CString16, FromSliceWithNulError> {
199         CString16::try_from(self)
200     }
201 }
202 
203 impl ops::Deref for CString16 {
204     type Target = CStr16;
205 
deref(&self) -> &CStr16206     fn deref(&self) -> &CStr16 {
207         unsafe { &*(self.0.as_slice() as *const [Char16] as *const CStr16) }
208     }
209 }
210 
211 impl AsRef<CStr16> for CString16 {
as_ref(&self) -> &CStr16212     fn as_ref(&self) -> &CStr16 {
213         self
214     }
215 }
216 
217 impl Borrow<CStr16> for CString16 {
borrow(&self) -> &CStr16218     fn borrow(&self) -> &CStr16 {
219         self
220     }
221 }
222 
223 impl ToOwned for CStr16 {
224     type Owned = CString16;
225 
to_owned(&self) -> CString16226     fn to_owned(&self) -> CString16 {
227         CString16(self.as_slice_with_nul().to_vec())
228     }
229 }
230 
231 impl fmt::Display for CString16 {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result232     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
233         self.as_ref().fmt(f)
234     }
235 }
236 
237 impl PartialEq<&CStr16> for CString16 {
eq(&self, other: &&CStr16) -> bool238     fn eq(&self, other: &&CStr16) -> bool {
239         PartialEq::eq(self.as_ref(), other)
240     }
241 }
242 
243 impl<StrType: AsRef<str> + ?Sized> EqStrUntilNul<StrType> for CString16 {
eq_str_until_nul(&self, other: &StrType) -> bool244     fn eq_str_until_nul(&self, other: &StrType) -> bool {
245         let this = self.as_ref();
246         this.eq_str_until_nul(other)
247     }
248 }
249 
250 #[cfg(test)]
251 mod tests {
252     use super::*;
253     use crate::cstr16;
254     use alloc::string::String;
255     use alloc::vec;
256 
257     #[test]
test_cstring16_from_str()258     fn test_cstring16_from_str() {
259         assert_eq!(
260             CString16::try_from("x").unwrap(),
261             CString16(vec![Char16::try_from('x').unwrap(), NUL_16])
262         );
263 
264         assert_eq!(CString16::try_from("��"), Err(FromStrError::InvalidChar));
265 
266         assert_eq!(CString16::try_from("x\0"), Err(FromStrError::InteriorNul));
267     }
268 
269     #[test]
test_cstring16_from_u16_vec()270     fn test_cstring16_from_u16_vec() {
271         // Test that invalid inputs are caught.
272         assert_eq!(
273             CString16::try_from(vec![]),
274             Err(FromSliceWithNulError::NotNulTerminated)
275         );
276         assert_eq!(
277             CString16::try_from(vec![b'a'.into(), 0, b'b'.into(), 0]),
278             Err(FromSliceWithNulError::InteriorNul(1))
279         );
280         assert_eq!(
281             CString16::try_from(vec![0xd800, 0]),
282             Err(FromSliceWithNulError::InvalidChar(0))
283         );
284 
285         // Test valid input.
286         assert_eq!(
287             CString16::try_from(vec![b'x'.into(), 0]).unwrap(),
288             CString16::try_from("x").unwrap()
289         );
290     }
291 
292     /// Test `CString16 == &CStr16` and `&CStr16 == CString16`.
293     #[test]
test_cstring16_cstr16_eq()294     fn test_cstring16_cstr16_eq() {
295         assert_eq!(
296             crate::prelude::cstr16!("abc"),
297             CString16::try_from("abc").unwrap()
298         );
299 
300         assert_eq!(
301             CString16::try_from("abc").unwrap(),
302             crate::prelude::cstr16!("abc")
303         );
304     }
305 
306     /// Tests the trait implementation of trait [`EqStrUntilNul]` for [`CString16`].
307     ///
308     /// This tests that `String` and `str` from the standard library can be
309     /// checked for equality against a [`CString16`]. It checks both directions,
310     /// i.e., the equality is reflexive.
311     #[test]
test_cstring16_eq_std_str()312     fn test_cstring16_eq_std_str() {
313         let input = CString16::try_from("test").unwrap();
314 
315         assert!(input.eq_str_until_nul("test")); // requires ?Sized constraint
316         assert!(input.eq_str_until_nul(&"test"));
317         assert!(input.eq_str_until_nul(&String::from("test")));
318 
319         // now other direction
320         assert!(String::from("test").eq_str_until_nul(&input));
321         assert!("test".eq_str_until_nul(&input));
322     }
323 
324     /// Test the `Borrow` and `ToOwned` impls.
325     #[test]
test_borrow_and_to_owned()326     fn test_borrow_and_to_owned() {
327         let s1: &CStr16 = cstr16!("ab");
328         let owned: CString16 = s1.to_owned();
329         let s2: &CStr16 = owned.borrow();
330         assert_eq!(s1, s2);
331         assert_eq!(
332             owned.0,
333             [
334                 Char16::try_from('a').unwrap(),
335                 Char16::try_from('b').unwrap(),
336                 NUL_16
337             ]
338         );
339     }
340 
341     /// This tests the following UCS-2 string functions:
342     /// - runtime constructor
343     /// - len()
344     /// - push() / push_str()
345     /// - to rust string
346     #[test]
test_push_str()347     fn test_push_str() {
348         let mut str1 = CString16::new();
349         assert_eq!(str1.num_bytes(), 2, "Should have null character");
350         assert_eq!(str1.num_chars(), 0);
351         str1.push(Char16::try_from('h').unwrap());
352         str1.push(Char16::try_from('i').unwrap());
353         assert_eq!(str1.num_chars(), 2);
354 
355         let mut str2 = CString16::new();
356         str2.push(Char16::try_from('!').unwrap());
357 
358         str2.push_str(str1.as_ref());
359         assert_eq!(str2.num_chars(), 3);
360 
361         let rust_str = String::from(&str2);
362         assert_eq!(rust_str, "!hi");
363     }
364 
365     #[test]
366     #[should_panic]
test_push_str_panic()367     fn test_push_str_panic() {
368         CString16::new().push(NUL_16);
369     }
370 
371     #[test]
test_char_replace_all_in_place()372     fn test_char_replace_all_in_place() {
373         let mut input = CString16::try_from("foo/bar/foobar//").unwrap();
374         let search = Char16::try_from('/').unwrap();
375         let replace = Char16::try_from('\\').unwrap();
376         input.replace_char(search, replace);
377 
378         let input = String::from(&input);
379         assert_eq!(input, "foo\\bar\\foobar\\\\")
380     }
381 }
382