1 | use super::chars::{Char16, Char8, NUL_16, NUL_8}; |
2 | use super::UnalignedSlice; |
3 | use crate::polyfill::maybe_uninit_slice_assume_init_ref; |
4 | use core::ffi::CStr; |
5 | use core::fmt; |
6 | use core::iter::Iterator; |
7 | use core::mem::MaybeUninit; |
8 | use core::result::Result; |
9 | use core::slice; |
10 | |
11 | #[cfg (feature = "alloc" )] |
12 | use super::CString16; |
13 | |
14 | /// Errors which can occur during checked `[uN]` -> `CStrN` conversions |
15 | #[derive (Clone, Copy, Debug, Eq, PartialEq)] |
16 | pub enum FromSliceWithNulError { |
17 | /// An invalid character was encountered before the end of the slice |
18 | InvalidChar(usize), |
19 | |
20 | /// A null character was encountered before the end of the slice |
21 | InteriorNul(usize), |
22 | |
23 | /// The slice was not null-terminated |
24 | NotNulTerminated, |
25 | } |
26 | |
27 | /// Error returned by [`CStr16::from_unaligned_slice`]. |
28 | #[derive (Clone, Copy, Debug, Eq, PartialEq)] |
29 | pub enum UnalignedCStr16Error { |
30 | /// An invalid character was encountered. |
31 | InvalidChar(usize), |
32 | |
33 | /// A null character was encountered before the end of the data. |
34 | InteriorNul(usize), |
35 | |
36 | /// The data was not null-terminated. |
37 | NotNulTerminated, |
38 | |
39 | /// The buffer is not big enough to hold the entire string and |
40 | /// trailing null character. |
41 | BufferTooSmall, |
42 | } |
43 | |
44 | /// Error returned by [`CStr16::from_str_with_buf`]. |
45 | #[derive (Clone, Copy, Debug, Eq, PartialEq)] |
46 | pub enum FromStrWithBufError { |
47 | /// An invalid character was encountered before the end of the string |
48 | InvalidChar(usize), |
49 | |
50 | /// A null character was encountered in the string |
51 | InteriorNul(usize), |
52 | |
53 | /// The buffer is not big enough to hold the entire string and |
54 | /// trailing null character |
55 | BufferTooSmall, |
56 | } |
57 | |
58 | /// A null-terminated Latin-1 string. |
59 | /// |
60 | /// This type is largely inspired by [`core::ffi::CStr`] with the exception that all characters are |
61 | /// guaranteed to be 8 bit long. |
62 | /// |
63 | /// A [`CStr8`] can be constructed from a [`core::ffi::CStr`] via a `try_from` call: |
64 | /// ```ignore |
65 | /// let cstr8: &CStr8 = TryFrom::try_from(cstr).unwrap(); |
66 | /// ``` |
67 | /// |
68 | /// For convenience, a [`CStr8`] is comparable with [`core::str`] and |
69 | /// `alloc::string::String` from the standard library through the trait [`EqStrUntilNul`]. |
70 | #[repr (transparent)] |
71 | #[derive (Eq, PartialEq)] |
72 | pub struct CStr8([Char8]); |
73 | |
74 | impl CStr8 { |
75 | /// Takes a raw pointer to a null-terminated Latin-1 string and wraps it in a CStr8 reference. |
76 | /// |
77 | /// # Safety |
78 | /// |
79 | /// The function will start accessing memory from `ptr` until the first |
80 | /// null byte. It's the callers responsibility to ensure `ptr` points to |
81 | /// a valid null-terminated string in accessible memory. |
82 | #[must_use ] |
83 | pub unsafe fn from_ptr<'ptr>(ptr: *const Char8) -> &'ptr Self { |
84 | let mut len = 0; |
85 | while *ptr.add(len) != NUL_8 { |
86 | len += 1 |
87 | } |
88 | let ptr = ptr.cast::<u8>(); |
89 | Self::from_bytes_with_nul_unchecked(slice::from_raw_parts(ptr, len + 1)) |
90 | } |
91 | |
92 | /// Creates a CStr8 reference from bytes. |
93 | pub fn from_bytes_with_nul(chars: &[u8]) -> Result<&Self, FromSliceWithNulError> { |
94 | let nul_pos = chars.iter().position(|&c| c == 0); |
95 | if let Some(nul_pos) = nul_pos { |
96 | if nul_pos + 1 != chars.len() { |
97 | return Err(FromSliceWithNulError::InteriorNul(nul_pos)); |
98 | } |
99 | Ok(unsafe { Self::from_bytes_with_nul_unchecked(chars) }) |
100 | } else { |
101 | Err(FromSliceWithNulError::NotNulTerminated) |
102 | } |
103 | } |
104 | |
105 | /// Unsafely creates a CStr8 reference from bytes. |
106 | /// |
107 | /// # Safety |
108 | /// |
109 | /// It's the callers responsibility to ensure chars is a valid Latin-1 |
110 | /// null-terminated string, with no interior null bytes. |
111 | #[must_use ] |
112 | pub const unsafe fn from_bytes_with_nul_unchecked(chars: &[u8]) -> &Self { |
113 | &*(chars as *const [u8] as *const Self) |
114 | } |
115 | |
116 | /// Returns the inner pointer to this CStr8. |
117 | #[must_use ] |
118 | pub const fn as_ptr(&self) -> *const Char8 { |
119 | self.0.as_ptr() |
120 | } |
121 | |
122 | /// Converts this CStr8 to a slice of bytes without the terminating null byte. |
123 | #[must_use ] |
124 | pub fn to_bytes(&self) -> &[u8] { |
125 | let chars = self.to_bytes_with_nul(); |
126 | &chars[..chars.len() - 1] |
127 | } |
128 | |
129 | /// Converts this CStr8 to a slice of bytes containing the trailing null byte. |
130 | #[must_use ] |
131 | pub const fn to_bytes_with_nul(&self) -> &[u8] { |
132 | unsafe { &*(&self.0 as *const [Char8] as *const [u8]) } |
133 | } |
134 | } |
135 | |
136 | impl fmt::Debug for CStr8 { |
137 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
138 | write!(f, "CStr8( {:?})" , &self.0) |
139 | } |
140 | } |
141 | |
142 | impl fmt::Display for CStr8 { |
143 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
144 | for c: &Char8 in self.0.iter() { |
145 | <Char8 as fmt::Display>::fmt(self:c, f)?; |
146 | } |
147 | Ok(()) |
148 | } |
149 | } |
150 | |
151 | impl<StrType: AsRef<str> + ?Sized> EqStrUntilNul<StrType> for CStr8 { |
152 | fn eq_str_until_nul(&self, other: &StrType) -> bool { |
153 | let other: &str = other.as_ref(); |
154 | |
155 | // TODO: CStr16 has .iter() implemented, CStr8 not yet |
156 | let any_not_equal: bool = self |
157 | .0 |
158 | .iter() |
159 | .copied() |
160 | .map(char::from) |
161 | .zip(other.chars()) |
162 | // This only works as CStr8 is guaranteed to have a fixed character length |
163 | // (unlike UTF-8). |
164 | .take_while(|(l: &char, r: &char)| *l != ' \0' && *r != ' \0' ) |
165 | .any(|(l: char, r: char)| l != r); |
166 | |
167 | !any_not_equal |
168 | } |
169 | } |
170 | |
171 | impl<'a> TryFrom<&'a CStr> for &'a CStr8 { |
172 | type Error = FromSliceWithNulError; |
173 | |
174 | fn try_from(cstr: &'a CStr) -> Result<Self, Self::Error> { |
175 | CStr8::from_bytes_with_nul(chars:cstr.to_bytes_with_nul()) |
176 | } |
177 | } |
178 | |
179 | /// An UCS-2 null-terminated string. |
180 | /// |
181 | /// This type is largely inspired by [`core::ffi::CStr`] with the exception that all characters are |
182 | /// guaranteed to be 16 bit long. |
183 | /// |
184 | /// For convenience, a [`CStr16`] is comparable with [`core::str`] and |
185 | /// `alloc::string::String` from the standard library through the trait [`EqStrUntilNul`]. |
186 | #[derive (Eq, PartialEq)] |
187 | #[repr (transparent)] |
188 | pub struct CStr16([Char16]); |
189 | |
190 | impl CStr16 { |
191 | /// Wraps a raw UEFI string with a safe C string wrapper |
192 | /// |
193 | /// # Safety |
194 | /// |
195 | /// The function will start accessing memory from `ptr` until the first |
196 | /// null byte. It's the callers responsibility to ensure `ptr` points to |
197 | /// a valid string, in accessible memory. |
198 | #[must_use ] |
199 | pub unsafe fn from_ptr<'ptr>(ptr: *const Char16) -> &'ptr Self { |
200 | let mut len = 0; |
201 | while *ptr.add(len) != NUL_16 { |
202 | len += 1 |
203 | } |
204 | let ptr = ptr.cast::<u16>(); |
205 | Self::from_u16_with_nul_unchecked(slice::from_raw_parts(ptr, len + 1)) |
206 | } |
207 | |
208 | /// Creates a C string wrapper from a u16 slice |
209 | /// |
210 | /// Since not every u16 value is a valid UCS-2 code point, this function |
211 | /// must do a bit more validity checking than CStr::from_bytes_with_nul |
212 | pub fn from_u16_with_nul(codes: &[u16]) -> Result<&Self, FromSliceWithNulError> { |
213 | for (pos, &code) in codes.iter().enumerate() { |
214 | match code.try_into() { |
215 | Ok(NUL_16) => { |
216 | if pos != codes.len() - 1 { |
217 | return Err(FromSliceWithNulError::InteriorNul(pos)); |
218 | } else { |
219 | return Ok(unsafe { Self::from_u16_with_nul_unchecked(codes) }); |
220 | } |
221 | } |
222 | Err(_) => { |
223 | return Err(FromSliceWithNulError::InvalidChar(pos)); |
224 | } |
225 | _ => {} |
226 | } |
227 | } |
228 | Err(FromSliceWithNulError::NotNulTerminated) |
229 | } |
230 | |
231 | /// Unsafely creates a C string wrapper from a u16 slice. |
232 | /// |
233 | /// # Safety |
234 | /// |
235 | /// It's the callers responsibility to ensure chars is a valid UCS-2 |
236 | /// null-terminated string, with no interior null bytes. |
237 | #[must_use ] |
238 | pub const unsafe fn from_u16_with_nul_unchecked(codes: &[u16]) -> &Self { |
239 | &*(codes as *const [u16] as *const Self) |
240 | } |
241 | |
242 | /// Convert a [`&str`] to a `&CStr16`, backed by a buffer. |
243 | /// |
244 | /// The input string must contain only characters representable with |
245 | /// UCS-2, and must not contain any null characters (even at the end of |
246 | /// the input). |
247 | /// |
248 | /// The backing buffer must be big enough to hold the converted string as |
249 | /// well as a trailing null character. |
250 | /// |
251 | /// # Examples |
252 | /// |
253 | /// Convert the UTF-8 string "ABC" to a `&CStr16`: |
254 | /// |
255 | /// ``` |
256 | /// use uefi::CStr16; |
257 | /// |
258 | /// let mut buf = [0; 4]; |
259 | /// CStr16::from_str_with_buf("ABC" , &mut buf).unwrap(); |
260 | /// ``` |
261 | pub fn from_str_with_buf<'a>( |
262 | input: &str, |
263 | buf: &'a mut [u16], |
264 | ) -> Result<&'a Self, FromStrWithBufError> { |
265 | let mut index = 0; |
266 | |
267 | // Convert to UTF-16. |
268 | for c in input.encode_utf16() { |
269 | *buf.get_mut(index) |
270 | .ok_or(FromStrWithBufError::BufferTooSmall)? = c; |
271 | index += 1; |
272 | } |
273 | |
274 | // Add trailing null character. |
275 | *buf.get_mut(index) |
276 | .ok_or(FromStrWithBufError::BufferTooSmall)? = 0; |
277 | |
278 | // Convert from u16 to Char16. This checks for invalid UCS-2 chars and |
279 | // interior nulls. The NotNulTerminated case is unreachable because we |
280 | // just added a trailing null character. |
281 | Self::from_u16_with_nul(&buf[..index + 1]).map_err(|err| match err { |
282 | FromSliceWithNulError::InvalidChar(p) => FromStrWithBufError::InvalidChar(p), |
283 | FromSliceWithNulError::InteriorNul(p) => FromStrWithBufError::InteriorNul(p), |
284 | FromSliceWithNulError::NotNulTerminated => unreachable!(), |
285 | }) |
286 | } |
287 | |
288 | /// Create a [`CStr16`] from an [`UnalignedSlice`] using an aligned |
289 | /// buffer for storage. The lifetime of the output is tied to `buf`, |
290 | /// not `src`. |
291 | pub fn from_unaligned_slice<'buf>( |
292 | src: &UnalignedSlice<'_, u16>, |
293 | buf: &'buf mut [MaybeUninit<u16>], |
294 | ) -> Result<&'buf CStr16, UnalignedCStr16Error> { |
295 | // The input `buf` might be longer than needed, so get a |
296 | // subslice of the required length. |
297 | let buf = buf |
298 | .get_mut(..src.len()) |
299 | .ok_or(UnalignedCStr16Error::BufferTooSmall)?; |
300 | |
301 | src.copy_to_maybe_uninit(buf); |
302 | let buf = unsafe { |
303 | // Safety: `copy_buf` fully initializes the slice. |
304 | maybe_uninit_slice_assume_init_ref(buf) |
305 | }; |
306 | CStr16::from_u16_with_nul(buf).map_err(|e| match e { |
307 | FromSliceWithNulError::InvalidChar(v) => UnalignedCStr16Error::InvalidChar(v), |
308 | FromSliceWithNulError::InteriorNul(v) => UnalignedCStr16Error::InteriorNul(v), |
309 | FromSliceWithNulError::NotNulTerminated => UnalignedCStr16Error::NotNulTerminated, |
310 | }) |
311 | } |
312 | |
313 | /// Returns the inner pointer to this C string |
314 | #[must_use ] |
315 | pub const fn as_ptr(&self) -> *const Char16 { |
316 | self.0.as_ptr() |
317 | } |
318 | |
319 | /// Get the underlying [`Char16`] slice, including the trailing null. |
320 | #[must_use ] |
321 | pub const fn as_slice_with_nul(&self) -> &[Char16] { |
322 | &self.0 |
323 | } |
324 | |
325 | /// Converts this C string to a u16 slice |
326 | #[must_use ] |
327 | pub fn to_u16_slice(&self) -> &[u16] { |
328 | let chars = self.to_u16_slice_with_nul(); |
329 | &chars[..chars.len() - 1] |
330 | } |
331 | |
332 | /// Converts this C string to a u16 slice containing the trailing 0 char |
333 | #[must_use ] |
334 | pub const fn to_u16_slice_with_nul(&self) -> &[u16] { |
335 | unsafe { &*(&self.0 as *const [Char16] as *const [u16]) } |
336 | } |
337 | |
338 | /// Returns an iterator over this C string |
339 | #[must_use ] |
340 | pub const fn iter(&self) -> CStr16Iter { |
341 | CStr16Iter { |
342 | inner: self, |
343 | pos: 0, |
344 | } |
345 | } |
346 | |
347 | /// Get the number of bytes in the string (including the trailing null character). |
348 | #[must_use ] |
349 | pub const fn num_bytes(&self) -> usize { |
350 | self.0.len() * 2 |
351 | } |
352 | |
353 | /// Writes each [`Char16`] as a [`char`] (4 bytes long in Rust language) into the buffer. |
354 | /// It is up to the implementer of [`core::fmt::Write`] to convert the char to a string |
355 | /// with proper encoding/charset. For example, in the case of [`alloc::string::String`] |
356 | /// all Rust chars (UTF-32) get converted to UTF-8. |
357 | /// |
358 | /// ## Example |
359 | /// |
360 | /// ```ignore |
361 | /// let firmware_vendor_c16_str: CStr16 = ...; |
362 | /// // crate "arrayvec" uses stack-allocated arrays for Strings => no heap allocations |
363 | /// let mut buf = arrayvec::ArrayString::<128>::new(); |
364 | /// firmware_vendor_c16_str.as_str_in_buf(&mut buf); |
365 | /// log::info!("as rust str: {}" , buf.as_str()); |
366 | /// ``` |
367 | /// |
368 | /// [`alloc::string::String`]: https://doc.rust-lang.org/nightly/alloc/string/struct.String.html |
369 | pub fn as_str_in_buf(&self, buf: &mut dyn core::fmt::Write) -> core::fmt::Result { |
370 | for c16 in self.iter() { |
371 | buf.write_char(char::from(*c16))?; |
372 | } |
373 | Ok(()) |
374 | } |
375 | } |
376 | |
377 | impl<StrType: AsRef<str> + ?Sized> EqStrUntilNul<StrType> for CStr16 { |
378 | fn eq_str_until_nul(&self, other: &StrType) -> bool { |
379 | let other: &str = other.as_ref(); |
380 | |
381 | let any_not_equal: bool = self |
382 | .iter() |
383 | .copied() |
384 | .map(char::from) |
385 | .zip(other.chars()) |
386 | // This only works as CStr16 is guaranteed to have a fixed character length |
387 | // (unlike UTF-8 or UTF-16). |
388 | .take_while(|(l: &char, r: &char)| *l != ' \0' && *r != ' \0' ) |
389 | .any(|(l: char, r: char)| l != r); |
390 | |
391 | !any_not_equal |
392 | } |
393 | } |
394 | |
395 | /// An iterator over `CStr16`. |
396 | #[derive (Debug)] |
397 | pub struct CStr16Iter<'a> { |
398 | inner: &'a CStr16, |
399 | pos: usize, |
400 | } |
401 | |
402 | impl<'a> Iterator for CStr16Iter<'a> { |
403 | type Item = &'a Char16; |
404 | |
405 | fn next(&mut self) -> Option<Self::Item> { |
406 | if self.pos >= self.inner.0.len() - 1 { |
407 | None |
408 | } else { |
409 | self.pos += 1; |
410 | self.inner.0.get(self.pos - 1) |
411 | } |
412 | } |
413 | } |
414 | |
415 | impl fmt::Debug for CStr16 { |
416 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
417 | write!(f, "CStr16( {:?})" , &self.0) |
418 | } |
419 | } |
420 | |
421 | impl fmt::Display for CStr16 { |
422 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
423 | for c: &Char16 in self.iter() { |
424 | <Char16 as fmt::Display>::fmt(self:c, f)?; |
425 | } |
426 | Ok(()) |
427 | } |
428 | } |
429 | |
430 | #[cfg (feature = "alloc" )] |
431 | impl PartialEq<CString16> for &CStr16 { |
432 | fn eq(&self, other: &CString16) -> bool { |
433 | PartialEq::eq(*self, other.as_ref()) |
434 | } |
435 | } |
436 | |
437 | impl<'a> UnalignedSlice<'a, u16> { |
438 | /// Create a [`CStr16`] from an [`UnalignedSlice`] using an aligned |
439 | /// buffer for storage. The lifetime of the output is tied to `buf`, |
440 | /// not `self`. |
441 | pub fn to_cstr16<'buf>( |
442 | &self, |
443 | buf: &'buf mut [MaybeUninit<u16>], |
444 | ) -> Result<&'buf CStr16, UnalignedCStr16Error> { |
445 | CStr16::from_unaligned_slice(self, buf) |
446 | } |
447 | } |
448 | |
449 | /// The EqStrUntilNul trait helps to compare Rust strings against UEFI string types (UCS-2 strings). |
450 | /// The given generic implementation of this trait enables us that we only have to |
451 | /// implement one direction (`left.eq_str_until_nul(&right)`) for each UEFI string type and we |
452 | /// get the other direction (`right.eq_str_until_nul(&left)`) for free. Hence, the relation is |
453 | /// reflexive. |
454 | pub trait EqStrUntilNul<StrType: ?Sized> { |
455 | /// Checks if the provided Rust string `StrType` is equal to [Self] until the first null-byte |
456 | /// is found. An exception is the terminating null-byte of [Self] which is ignored. |
457 | /// |
458 | /// As soon as the first null byte in either `&self` or `other` is found, this method returns. |
459 | /// Note that Rust strings are allowed to contain null-bytes that do not terminate the string. |
460 | /// Although this is rather unusual, you can compare `"foo\0bar"` with an instance of [Self]. |
461 | /// In that case, only `foo"` is compared against [Self] (if [Self] is long enough). |
462 | fn eq_str_until_nul(&self, other: &StrType) -> bool; |
463 | } |
464 | |
465 | // magic implementation which transforms an existing `left.eq_str_until_nul(&right)` implementation |
466 | // into an additional working `right.eq_str_until_nul(&left)` implementation. |
467 | impl<StrType, C16StrType> EqStrUntilNul<C16StrType> for StrType |
468 | where |
469 | StrType: AsRef<str>, |
470 | C16StrType: EqStrUntilNul<StrType> + ?Sized, |
471 | { |
472 | fn eq_str_until_nul(&self, other: &C16StrType) -> bool { |
473 | // reuse the existing implementation |
474 | other.eq_str_until_nul(self) |
475 | } |
476 | } |
477 | |
478 | #[cfg (test)] |
479 | mod tests { |
480 | use super::*; |
481 | use alloc::string::String; |
482 | use uefi_macros::{cstr16, cstr8}; |
483 | |
484 | // Tests if our CStr8 type can be constructed from a valid core::ffi::CStr |
485 | #[test ] |
486 | fn test_cstr8_from_cstr() { |
487 | let msg = "hello world \0" ; |
488 | let cstr = unsafe { CStr::from_ptr(msg.as_ptr().cast()) }; |
489 | let cstr8: &CStr8 = TryFrom::try_from(cstr).unwrap(); |
490 | assert!(cstr8.eq_str_until_nul(msg)); |
491 | assert!(msg.eq_str_until_nul(cstr8)); |
492 | } |
493 | |
494 | #[test ] |
495 | fn test_cstr16_num_bytes() { |
496 | let s = CStr16::from_u16_with_nul(&[65, 66, 67, 0]).unwrap(); |
497 | assert_eq!(s.num_bytes(), 8); |
498 | } |
499 | |
500 | #[test ] |
501 | fn test_cstr16_from_str_with_buf() { |
502 | let mut buf = [0; 4]; |
503 | |
504 | // OK: buf is exactly the right size. |
505 | let s = CStr16::from_str_with_buf("ABC" , &mut buf).unwrap(); |
506 | assert_eq!(s.to_u16_slice_with_nul(), [65, 66, 67, 0]); |
507 | |
508 | // OK: buf is bigger than needed. |
509 | let s = CStr16::from_str_with_buf("A" , &mut buf).unwrap(); |
510 | assert_eq!(s.to_u16_slice_with_nul(), [65, 0]); |
511 | |
512 | // Error: buf is too small. |
513 | assert_eq!( |
514 | CStr16::from_str_with_buf("ABCD" , &mut buf).unwrap_err(), |
515 | FromStrWithBufError::BufferTooSmall |
516 | ); |
517 | |
518 | // Error: invalid character. |
519 | assert_eq!( |
520 | CStr16::from_str_with_buf("a😀" , &mut buf).unwrap_err(), |
521 | FromStrWithBufError::InvalidChar(1), |
522 | ); |
523 | |
524 | // Error: interior null. |
525 | assert_eq!( |
526 | CStr16::from_str_with_buf("a \0b" , &mut buf).unwrap_err(), |
527 | FromStrWithBufError::InteriorNul(1), |
528 | ); |
529 | } |
530 | |
531 | #[test ] |
532 | fn test_cstr16_macro() { |
533 | // Just a sanity check to make sure it's spitting out the right characters |
534 | assert_eq!( |
535 | crate::prelude::cstr16!("ABC" ).to_u16_slice_with_nul(), |
536 | [65, 66, 67, 0] |
537 | ) |
538 | } |
539 | |
540 | #[test ] |
541 | fn test_unaligned_cstr16() { |
542 | let mut buf = [0u16; 6]; |
543 | let us = unsafe { |
544 | let ptr = buf.as_mut_ptr() as *mut u8; |
545 | // Intentionally create an unaligned u16 pointer. This |
546 | // leaves room for five u16 characters. |
547 | let ptr = ptr.add(1) as *mut u16; |
548 | // Write out the "test" string. |
549 | ptr.add(0).write_unaligned(b't' .into()); |
550 | ptr.add(1).write_unaligned(b'e' .into()); |
551 | ptr.add(2).write_unaligned(b's' .into()); |
552 | ptr.add(3).write_unaligned(b't' .into()); |
553 | ptr.add(4).write_unaligned(b' \0' .into()); |
554 | |
555 | // Create the `UnalignedSlice`. |
556 | UnalignedSlice::new(ptr, 5) |
557 | }; |
558 | |
559 | // Test `to_cstr16()` with too small of a buffer. |
560 | let mut buf = [MaybeUninit::new(0); 4]; |
561 | assert_eq!( |
562 | us.to_cstr16(&mut buf).unwrap_err(), |
563 | UnalignedCStr16Error::BufferTooSmall |
564 | ); |
565 | // Test with a big enough buffer. |
566 | let mut buf = [MaybeUninit::new(0); 5]; |
567 | assert_eq!( |
568 | us.to_cstr16(&mut buf).unwrap(), |
569 | CString16::try_from("test" ).unwrap() |
570 | ); |
571 | |
572 | // Test `to_cstring16()`. |
573 | assert_eq!( |
574 | us.to_cstring16().unwrap(), |
575 | CString16::try_from("test" ).unwrap() |
576 | ); |
577 | } |
578 | |
579 | // Code generation helper for the compare tests of our CStrX types against "str" and "String" |
580 | // from the standard library. |
581 | #[allow (non_snake_case)] |
582 | macro_rules! test_compare_cstrX { |
583 | ($input:ident) => { |
584 | assert!($input.eq_str_until_nul(&"test" )); |
585 | assert!($input.eq_str_until_nul(&String::from("test" ))); |
586 | |
587 | // now other direction |
588 | assert!(String::from("test" ).eq_str_until_nul($input)); |
589 | assert!("test" .eq_str_until_nul($input)); |
590 | |
591 | // some more tests |
592 | // this is fine: compare until the first null |
593 | assert!($input.eq_str_until_nul(&"te \0st" )); |
594 | // this is fine |
595 | assert!($input.eq_str_until_nul(&"test \0" )); |
596 | assert!(!$input.eq_str_until_nul(&"hello" )); |
597 | }; |
598 | } |
599 | |
600 | #[test ] |
601 | fn test_compare_cstr8() { |
602 | // test various comparisons with different order (left, right) |
603 | let input: &CStr8 = cstr8!("test" ); |
604 | test_compare_cstrX!(input); |
605 | } |
606 | |
607 | #[test ] |
608 | fn test_compare_cstr16() { |
609 | let input: &CStr16 = cstr16!("test" ); |
610 | test_compare_cstrX!(input); |
611 | } |
612 | |
613 | /// Test that the `cstr16!` macro can be used in a `const` context. |
614 | #[test ] |
615 | fn test_cstr16_macro_const() { |
616 | const S: &CStr16 = cstr16!("ABC" ); |
617 | assert_eq!(S.to_u16_slice_with_nul(), [65, 66, 67, 0]); |
618 | } |
619 | |
620 | /// Tests the trait implementation of trait [`EqStrUntilNul]` for [`CStr8`]. |
621 | /// |
622 | /// This tests that `String` and `str` from the standard library can be |
623 | /// checked for equality against a [`CStr8`]. It checks both directions, |
624 | /// i.e., the equality is reflexive. |
625 | #[test ] |
626 | fn test_cstr8_eq_std_str() { |
627 | let input: &CStr8 = cstr8!("test" ); |
628 | |
629 | // test various comparisons with different order (left, right) |
630 | assert!(input.eq_str_until_nul("test" )); // requires ?Sized constraint |
631 | assert!(input.eq_str_until_nul(&"test" )); |
632 | assert!(input.eq_str_until_nul(&String::from("test" ))); |
633 | |
634 | // now other direction |
635 | assert!(String::from("test" ).eq_str_until_nul(input)); |
636 | assert!("test" .eq_str_until_nul(input)); |
637 | } |
638 | |
639 | /// Tests the trait implementation of trait [`EqStrUntilNul]` for [`CStr16`]. |
640 | /// |
641 | /// This tests that `String` and `str` from the standard library can be |
642 | /// checked for equality against a [`CStr16`]. It checks both directions, |
643 | /// i.e., the equality is reflexive. |
644 | #[test ] |
645 | fn test_cstr16_eq_std_str() { |
646 | let input: &CStr16 = cstr16!("test" ); |
647 | |
648 | assert!(input.eq_str_until_nul("test" )); // requires ?Sized constraint |
649 | assert!(input.eq_str_until_nul(&"test" )); |
650 | assert!(input.eq_str_until_nul(&String::from("test" ))); |
651 | |
652 | // now other direction |
653 | assert!(String::from("test" ).eq_str_until_nul(input)); |
654 | assert!("test" .eq_str_until_nul(input)); |
655 | } |
656 | } |
657 | |