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