1#![allow(unused_qualifications)]
13#![allow(clippy::similar_names)]
14#![allow(unused)]
15
16#[cfg(feature = "alloc")]
17extern crate alloc;
18
19use super::bytes::{BytesError, volatile_zero};
20use core::convert::TryFrom;
21use core::ffi::CStr as StdCStr;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum CStrError {
42 CapacityExceeded(BytesError),
44 InteriorNul(usize),
46 MissingNul,
48 NullPointer,
50}
51
52impl core::fmt::Display for CStrError {
53 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54 match self {
55 Self::CapacityExceeded(e) => write!(f, "capacity exceeded: {e}"),
56 Self::InteriorNul(pos) => write!(f, "interior NUL at payload index {pos}"),
57 Self::MissingNul => write!(f, "missing terminating NUL"),
58 Self::NullPointer => write!(f, "received null pointer"),
59 }
60 }
61}
62
63impl core::error::Error for CStrError {}
64
65#[inline(always)]
66fn safe_parse_cstr_and_wipe<F>(buffer: &mut [u8], mut push_fn: F) -> Result<(), CStrError>
67where
68 F: FnMut(&[u8]) -> Result<(), CStrError>,
69{
70 let res = match buffer.iter().position(|&b| b == 0) {
71 None => Err(CStrError::MissingNul),
72 Some(pos) => push_fn(&buffer[..pos]),
73 };
74 volatile_zero(buffer);
75 res
77}
78
79pub trait CStr:
99 ::core::ops::Deref<Target = StdCStr> + ::core::fmt::Display + ::core::fmt::Debug + Send + Sync
100{
101 fn as_cstr(&self) -> &StdCStr;
107
108 #[inline(always)]
114 fn as_bytes(&self) -> &[u8] {
115 self.as_cstr().to_bytes()
116 }
117
118 #[inline(always)]
124 fn as_bytes_with_nul(&self) -> &[u8] {
125 self.as_cstr().to_bytes_with_nul()
126 }
127
128 #[inline(always)]
137 fn as_ptr(&self) -> *const core::ffi::c_char {
138 self.as_cstr().as_ptr()
139 }
140
141 #[cfg(feature = "alloc")]
147 fn dyn_clone(&self) -> alloc::boxed::Box<dyn CStr>;
148
149 #[inline(always)]
155 fn len(&self) -> usize {
156 self.as_cstr().to_bytes().len()
157 }
158
159 #[inline(always)]
165 fn is_empty(&self) -> bool {
166 self.as_cstr().to_bytes().is_empty()
167 }
168}
169
170pub struct StackCStr<const N: usize> {
188 len: usize,
189 data: [u8; N],
190}
191
192#[cfg(feature = "alloc")]
210pub struct HeapCStr<const N: usize> {
211 len: usize,
212 data: alloc::boxed::Box<[u8; N]>,
213}
214
215impl<const N: usize> StackCStr<N> {
216 pub const CAPACITY: usize = N;
222
223 #[inline(always)]
233 pub const fn new() -> Self {
234 assert!(N > 0, "StackCStr capacity N must include the NUL (>= 1)");
235 Self {
236 len: 0,
237 data: [0u8; N],
238 }
239 }
240}
241
242#[cfg(feature = "alloc")]
243impl<const N: usize> HeapCStr<N> {
244 pub const CAPACITY: usize = N;
250
251 #[inline(always)]
261 pub fn new() -> Self {
262 assert!(N > 0, "HeapCStr capacity N must include the NUL (>= 1)");
263 let data = unsafe { alloc::boxed::Box::<[u8; N]>::new_zeroed().assume_init() };
265 Self { len: 0, data }
266 }
267}
268
269macro_rules! impl_secure_cstr_base {
270 ($name:ident) => {
271 impl<const N: usize> $name<N> {
272 #[inline(always)]
273 fn try_push_bytes(&mut self, payload: &[u8]) -> Result<(), CStrError> {
274 if let Some(pos) = payload.iter().position(|&b| b == 0) {
275 return Err(CStrError::InteriorNul(pos));
276 }
277 let required = self
278 .len
279 .checked_add(payload.len())
280 .and_then(|v| v.checked_add(1))
281 .ok_or(CStrError::CapacityExceeded(BytesError::CapacityExceeded {
282 requested: usize::MAX,
283 max: N,
284 }))?;
285 if required > N {
286 return Err(CStrError::CapacityExceeded(BytesError::CapacityExceeded {
287 requested: required,
288 max: N,
289 }));
290 }
291 let cur = self.len;
292 self.data[cur..cur + payload.len()].copy_from_slice(payload);
293 self.data[cur + payload.len()] = 0;
294 self.len = cur + payload.len();
295 Ok(())
296 }
297
298 pub unsafe fn from_raw_parts_mut(ptr: *mut u8, len: usize) -> Result<Self, CStrError> {
315 if ptr.is_null() {
316 return Err(CStrError::NullPointer);
317 }
318 if len == 0 {
319 return Err(CStrError::MissingNul);
320 }
321
322 let slice = unsafe { ::core::slice::from_raw_parts_mut(ptr, len) };
324
325 let mut out = Self::new();
326 let pos = slice.iter().position(|&b| b == 0);
327 let res: Result<(), CStrError> = match pos {
328 None => Err(CStrError::MissingNul),
329 Some(end) => out.try_push_bytes(&slice[..end]),
330 };
331 volatile_zero(slice);
332 res?;
333 Ok(out)
334 }
335
336 #[inline]
363 pub fn try_grow<const M: usize>(self) -> Result<$name<M>, CStrError> {
364 if self.len + 1 > M {
365 return Err(CStrError::CapacityExceeded(BytesError::CapacityExceeded {
366 requested: self.len + 1,
367 max: M,
368 }));
369 }
370 let mut larger = $name::<M>::new();
371 larger
372 .try_push_bytes(self.as_bytes())
373 .expect("Capacity already checked");
374 Ok(larger)
375 }
376
377 #[inline]
407 pub fn push_bytes_into<const M: usize>(
408 self,
409 suffix: &[u8],
410 ) -> Result<$name<M>, CStrError> {
411 if let Some(rel) = suffix.iter().position(|&b| b == 0) {
412 return Err(CStrError::InteriorNul(self.len + rel));
413 }
414 let required = self
415 .len
416 .checked_add(suffix.len())
417 .and_then(|v| v.checked_add(1))
418 .ok_or(CStrError::CapacityExceeded(BytesError::CapacityExceeded {
419 requested: usize::MAX,
420 max: M,
421 }))?;
422 if required > M {
423 return Err(CStrError::CapacityExceeded(BytesError::CapacityExceeded {
424 requested: required,
425 max: M,
426 }));
427 }
428 let required = self.len + suffix.len() + 1;
429 if required > M {
430 return Err(CStrError::CapacityExceeded(BytesError::CapacityExceeded {
431 requested: required,
432 max: M,
433 }));
434 }
435 let mut larger = $name::<M>::new();
436 larger
437 .try_push_bytes(self.as_bytes())
438 .expect("Capacity already checked");
439 larger
440 .try_push_bytes(suffix)
441 .expect("Capacity already checked");
442 Ok(larger)
443 }
444
445 pub fn concat_into<const M: usize>(
473 self,
474 rhs: impl ::core::convert::AsRef<[u8]>,
475 ) -> Result<$name<M>, CStrError> {
476 self.push_bytes_into(rhs.as_ref())
477 }
478 }
479
480 impl<const N: usize> ::core::default::Default for $name<N> {
481 #[inline(always)]
482 fn default() -> Self {
483 Self::new()
484 }
485 }
486
487 impl<const N: usize> ::core::ops::Drop for $name<N> {
488 #[inline(always)]
489 fn drop(&mut self) {
490 volatile_zero(&mut self.data[..]);
491 }
492 }
493
494 impl<const N: usize> CStr for $name<N> {
495 #[inline(always)]
496 fn as_cstr(&self) -> &StdCStr {
497 unsafe { StdCStr::from_bytes_with_nul_unchecked(&self.data[..self.len + 1]) }
500 }
501
502 #[cfg(feature = "alloc")]
503 #[inline(always)]
504 fn dyn_clone(&self) -> alloc::boxed::Box<dyn CStr> {
505 alloc::boxed::Box::new(self.clone())
506 }
507 }
508
509 impl<const N: usize> ::core::clone::Clone for $name<N> {
510 #[inline(always)]
511 fn clone(&self) -> Self {
512 let mut out = Self::new();
513 let _ = out.try_push_bytes(self.as_bytes());
514 out
515 }
516 }
517 };
518}
519
520macro_rules! impl_try_from_traits {
521 ($name:ident) => {
522 impl<const N: usize> TryFrom<&StdCStr> for $name<N> {
523 type Error = CStrError;
524 #[inline(always)]
525 fn try_from(s: &StdCStr) -> Result<Self, Self::Error> {
526 let mut out = Self::new();
527 out.try_push_bytes(s.to_bytes())?;
528 Ok(out)
529 }
530 }
531
532 impl<const N: usize> TryFrom<&str> for $name<N> {
533 type Error = CStrError;
534 #[inline(always)]
535 fn try_from(s: &str) -> Result<Self, Self::Error> {
536 let mut out = Self::new();
537 out.try_push_bytes(s.as_bytes())?;
538 Ok(out)
539 }
540 }
541
542 impl<const N: usize> TryFrom<&[u8]> for $name<N> {
543 type Error = CStrError;
544 fn try_from(s: &[u8]) -> Result<Self, Self::Error> {
545 match s.iter().position(|&b| b == 0) {
546 None => Err(CStrError::MissingNul),
547 Some(pos) => {
548 let mut out = Self::new();
549 out.try_push_bytes(&s[..pos])?;
550 Ok(out)
551 }
552 }
553 }
554 }
555
556 impl<const N: usize> ::core::str::FromStr for $name<N> {
557 type Err = CStrError;
558 #[inline(always)]
559 fn from_str(s: &str) -> Result<Self, Self::Err> {
560 Self::try_from(s)
561 }
562 }
563
564 impl<const N: usize> TryFrom<&mut [u8; N]> for $name<N> {
565 type Error = CStrError;
566 fn try_from(arr: &mut [u8; N]) -> Result<Self, Self::Error> {
567 let mut out = Self::new();
568 safe_parse_cstr_and_wipe(&mut arr[..], |p| out.try_push_bytes(p))?;
569 Ok(out)
570 }
571 }
572
573 impl<const N: usize> TryFrom<&mut [u8]> for $name<N> {
574 type Error = CStrError;
575 fn try_from(arr: &mut [u8]) -> Result<Self, Self::Error> {
576 let mut out = Self::new();
577 safe_parse_cstr_and_wipe(arr, |p| out.try_push_bytes(p))?;
578 Ok(out)
579 }
580 }
581
582 #[cfg(feature = "alloc")]
583 impl<const N: usize> TryFrom<alloc::boxed::Box<[u8; N]>> for $name<N> {
584 type Error = CStrError;
585 fn try_from(mut b: alloc::boxed::Box<[u8; N]>) -> Result<Self, Self::Error> {
586 let mut out = Self::new();
587 safe_parse_cstr_and_wipe(&mut *b, |p| out.try_push_bytes(p))?;
588 Ok(out)
589 }
590 }
591
592 #[cfg(feature = "alloc")]
593 impl<const N: usize> TryFrom<alloc::vec::Vec<u8>> for $name<N> {
594 type Error = CStrError;
595 fn try_from(mut v: alloc::vec::Vec<u8>) -> Result<Self, Self::Error> {
596 let mut out = Self::new();
597 safe_parse_cstr_and_wipe(v.as_mut_slice(), |p| out.try_push_bytes(p))?;
598 Ok(out)
599 }
600 }
601
602 #[cfg(feature = "alloc")]
603 impl<const N: usize> TryFrom<alloc::string::String> for $name<N> {
604 type Error = CStrError;
605 #[inline(always)]
606 fn try_from(s: alloc::string::String) -> Result<Self, Self::Error> {
607 Self::try_from(s.into_bytes())
608 }
609 }
610 };
611}
612
613#[inline(always)]
614fn fmt_cstr_lossy(f: &mut core::fmt::Formatter<'_>, mut rest: &[u8]) -> core::fmt::Result {
615 loop {
616 match core::str::from_utf8(rest) {
617 Ok(valid) => return f.write_str(valid),
618 Err(e) => {
619 let valid = e.valid_up_to();
620 if valid > 0 {
621 f.write_str(unsafe { core::str::from_utf8_unchecked(&rest[..valid]) })?;
623 }
624 f.write_str("\u{FFFD}")?;
625 rest = &rest[valid + e.error_len().unwrap_or(1)..];
626 if rest.is_empty() {
627 return Ok(());
628 }
629 }
630 }
631 }
632}
633
634macro_rules! __impl_common_cstr_traits {
635 ($name:ident) => {
636 impl<const N: usize> ::core::ops::Deref for $name<N> {
637 type Target = StdCStr;
638 #[inline(always)]
639 fn deref(&self) -> &Self::Target {
640 self.as_cstr()
641 }
642 }
643 impl<const N: usize> ::core::fmt::Display for $name<N> {
644 #[inline(always)]
645 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
646 fmt_cstr_lossy(f, self.as_bytes())
647 }
648 }
649 impl<const N: usize> ::core::fmt::Debug for $name<N> {
650 #[inline(always)]
651 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
652 ::core::fmt::Debug::fmt(self.as_cstr(), f)
653 }
654 }
655 impl<const N: usize> ::core::convert::AsRef<StdCStr> for $name<N> {
656 #[inline(always)]
657 fn as_ref(&self) -> &StdCStr {
658 self.as_cstr()
659 }
660 }
661 impl<const N: usize> ::core::convert::AsRef<[u8]> for $name<N> {
662 #[inline(always)]
663 fn as_ref(&self) -> &[u8] {
664 self.as_bytes()
665 }
666 }
667 impl<const N: usize> ::core::cmp::PartialEq<StdCStr> for $name<N> {
668 #[inline(always)]
669 fn eq(&self, other: &StdCStr) -> bool {
670 self.as_cstr() == other
671 }
672 }
673 impl<'a, const N: usize> ::core::cmp::PartialEq<&'a StdCStr> for $name<N> {
674 #[inline(always)]
675 fn eq(&self, other: &&'a StdCStr) -> bool {
676 self.as_cstr() == *other
677 }
678 }
679 impl<const N: usize> ::core::cmp::PartialEq<str> for $name<N> {
680 #[inline(always)]
681 fn eq(&self, other: &str) -> bool {
682 self.as_bytes() == other.as_bytes()
683 }
684 }
685 impl<'a, const N: usize> ::core::cmp::PartialEq<&'a str> for $name<N> {
686 #[inline(always)]
687 fn eq(&self, other: &&'a str) -> bool {
688 self.as_bytes() == other.as_bytes()
689 }
690 }
691 impl<const N: usize, const M: usize> ::core::cmp::PartialEq<$name<M>> for $name<N> {
692 #[inline(always)]
693 fn eq(&self, other: &$name<M>) -> bool {
694 self.as_cstr() == other.as_cstr()
695 }
696 }
697 #[cfg(feature = "alloc")]
698 impl<const N: usize> ::core::cmp::PartialEq<alloc::string::String> for $name<N> {
699 #[inline(always)]
700 fn eq(&self, other: &alloc::string::String) -> bool {
701 self.as_bytes() == other.as_bytes()
702 }
703 }
704 impl<const N: usize> ::core::cmp::Eq for $name<N> {}
705 impl<const N: usize> ::core::cmp::PartialOrd for $name<N> {
706 #[inline(always)]
707 fn partial_cmp(&self, other: &Self) -> ::core::option::Option<::core::cmp::Ordering> {
708 Some(self.cmp(other))
709 }
710 }
711 impl<const N: usize> ::core::cmp::Ord for $name<N> {
712 #[inline(always)]
713 fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
714 self.as_cstr().cmp(other.as_cstr())
715 }
716 }
717 impl<const N: usize> ::core::hash::Hash for $name<N> {
718 #[inline(always)]
719 fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
720 self.as_cstr().hash(state)
723 }
724 }
725 impl<const N: usize> ::core::borrow::Borrow<StdCStr> for $name<N> {
726 #[inline(always)]
727 fn borrow(&self) -> &StdCStr {
728 self.as_cstr()
729 }
730 }
731 };
732}
733
734impl_secure_cstr_base!(StackCStr);
735impl_try_from_traits!(StackCStr);
736__impl_common_cstr_traits!(StackCStr);
737
738#[cfg(feature = "alloc")]
739impl_secure_cstr_base!(HeapCStr);
740#[cfg(feature = "alloc")]
741impl_try_from_traits!(HeapCStr);
742#[cfg(feature = "alloc")]
743__impl_common_cstr_traits!(HeapCStr);
744
745#[cfg(feature = "alloc")]
746impl<const N: usize> StackCStr<N> {
747 #[inline]
774 pub fn into_heap<const M: usize>(self) -> Result<HeapCStr<M>, CStrError> {
775 if self.len + 1 > M {
776 return Err(CStrError::CapacityExceeded(BytesError::CapacityExceeded {
777 requested: self.len + 1,
778 max: M,
779 }));
780 }
781 let mut heap = HeapCStr::<M>::new();
782 heap.try_push_bytes(self.as_bytes())
783 .expect("Capacity checked");
784 Ok(heap)
785 }
786}
787
788#[cfg(feature = "alloc")]
789impl<const N: usize, const M: usize> ::core::cmp::PartialEq<StackCStr<M>> for HeapCStr<N> {
790 #[inline(always)]
791 fn eq(&self, other: &StackCStr<M>) -> bool {
792 self.as_cstr() == other.as_cstr()
793 }
794}
795#[cfg(feature = "alloc")]
796impl<const N: usize, const M: usize> ::core::cmp::PartialEq<HeapCStr<N>> for StackCStr<M> {
797 #[inline(always)]
798 fn eq(&self, other: &HeapCStr<N>) -> bool {
799 self.as_cstr() == other.as_cstr()
800 }
801}
802
803#[cfg(feature = "alloc")]
804impl ::core::clone::Clone for alloc::boxed::Box<dyn CStr> {
805 #[inline(always)]
806 fn clone(&self) -> Self {
807 self.dyn_clone()
808 }
809}
810#[cfg(feature = "alloc")]
811impl ::core::cmp::PartialEq for alloc::boxed::Box<dyn CStr> {
812 #[inline(always)]
813 fn eq(&self, other: &Self) -> bool {
814 self.as_cstr() == other.as_cstr()
815 }
816}
817#[cfg(feature = "alloc")]
818impl ::core::cmp::Eq for alloc::boxed::Box<dyn CStr> {}
819
820#[cfg(test)]
821#[allow(clippy::unwrap_used, clippy::expect_used)]
822mod tests {
823 use super::*;
824 #[cfg(feature = "alloc")]
825 use alloc::{boxed::Box, vec, vec::Vec};
826 use core::convert::TryInto;
827
828 #[test]
829 fn test_stack_from_str_literal() {
830 let s: StackCStr<8> = "hello".try_into().unwrap();
831 assert_eq!(&*s, c"hello");
832 assert_eq!(s.len(), 5);
833 assert_eq!(s.as_bytes_with_nul(), b"hello\0");
834 }
835
836 #[test]
837 fn test_non_utf8_payload() {
838 let s: StackCStr<8> = StackCStr::try_from([0xFFu8, 0xFE, 0].as_slice()).unwrap();
839 assert_eq!(s.as_bytes(), &[0xFF, 0xFE]);
840 let _ = alloc_display(&s);
842 }
843
844 #[cfg(feature = "alloc")]
845 fn alloc_display(s: &StackCStr<8>) -> alloc::string::String {
846 use alloc::string::ToString;
847 s.to_string()
848 }
849
850 #[cfg(not(feature = "alloc"))]
851 fn alloc_display(s: &StackCStr<8>) -> usize {
852 use core::fmt::Write;
853 struct Counter(usize);
854 impl Write for Counter {
855 fn write_str(&mut self, s: &str) -> core::fmt::Result {
856 self.0 += s.len();
857 Ok(())
858 }
859 }
860 let mut c = Counter(0);
861 let _ = write!(c, "{s}");
862 c.0
863 }
864
865 #[cfg(feature = "alloc")]
866 #[test]
867 fn test_heap_from_str_literal() {
868 let h: HeapCStr<16> = "hello".try_into().unwrap();
869 assert_eq!(&*h, c"hello");
870 }
871
872 #[cfg(feature = "alloc")]
873 #[test]
874 fn test_vec_dyn_cstr() {
875 let mut list: Vec<Box<dyn CStr>> = Vec::new();
876 let h: HeapCStr<256> = "/tmp/secret".try_into().unwrap();
877 list.push(Box::new(h));
878 let s: StackCStr<16> = "/bin/sh".try_into().unwrap();
879 list.push(Box::new(s));
880 assert_eq!(list.len(), 2);
881 assert!(list[1].as_bytes().starts_with(b"/bin"));
882 }
883
884 #[cfg(feature = "alloc")]
885 #[test]
886 fn from_raw_ptr_with_wiping() {
887 let mut source_data = b"/tmp/x\0".to_vec();
888 let ptr = source_data.as_mut_ptr();
889 let len = source_data.len();
890
891 let h = unsafe { HeapCStr::<32>::from_raw_parts_mut(ptr, len) }.unwrap();
893 assert_eq!(h.as_bytes(), b"/tmp/x");
894
895 assert_eq!(source_data, vec![0u8; 7]);
896 }
897
898 #[test]
899 fn test_capacity_exceeded_fail_fast() {
900 let res: Result<StackCStr<4>, _> = "hello".try_into();
902 assert!(res.is_err());
903 let ok: Result<StackCStr<4>, _> = "hey".try_into();
904 assert!(ok.is_ok());
905 }
906
907 #[test]
908 fn test_interior_nul_rejected() {
909 let res = StackCStr::<16>::try_from("a\0b");
910 assert!(matches!(res, Err(CStrError::InteriorNul(1))));
911 }
912
913 #[test]
914 fn test_missing_nul_wiping() {
915 let mut no_nul = *b"abc";
916 let res = StackCStr::<16>::try_from(no_nul.as_mut_slice());
917 assert!(matches!(res, Err(CStrError::MissingNul)));
918 assert_eq!(no_nul, [0u8; 3]);
919 }
920
921 #[test]
922 fn test_truncates_at_first_nul_and_wipes() {
923 let mut buf = *b"hi\0garbage";
924 let s = StackCStr::<16>::try_from(buf.as_mut_slice()).unwrap();
925 assert_eq!(s.as_bytes(), b"hi");
926 assert_eq!(buf, [0u8; 10]);
927 }
928
929 #[test]
930 fn test_capacity_one_holds_only_empty() {
931 let e: StackCStr<1> = "".try_into().unwrap();
932 assert!(e.is_empty());
933 assert_eq!(e.as_bytes_with_nul(), b"\0");
934 let res: Result<StackCStr<1>, _> = "a".try_into();
935 assert!(matches!(res, Err(CStrError::CapacityExceeded(_))));
936 }
937
938 #[test]
939 fn test_try_grow_and_shrink_fails() {
940 let s: StackCStr<4> = "hi".try_into().unwrap();
941 let big = s.try_grow::<8>().unwrap();
942 assert_eq!(&*big, c"hi");
943 let res = big.try_grow::<2>();
944 assert!(matches!(res, Err(CStrError::CapacityExceeded(_))));
945 }
946
947 #[test]
948 fn test_concat_into_overflow() {
949 let s: StackCStr<4> = "hi".try_into().unwrap();
950 let res = s.concat_into::<4>(b"!!");
951 assert!(matches!(res, Err(CStrError::CapacityExceeded(_))));
952 }
953
954 #[test]
955 fn test_display_lossy_replaces_invalid() {
956 use core::fmt::Write;
957 let s: StackCStr<8> = StackCStr::try_from([0xFFu8, 0xFE, 0].as_slice()).unwrap();
958 struct Buf<'a>(&'a mut [u8; 16], usize);
959 impl Write for Buf<'_> {
960 fn write_str(&mut self, s: &str) -> core::fmt::Result {
961 let b = s.as_bytes();
962 self.0[self.1..self.1 + b.len()].copy_from_slice(b);
963 self.1 += b.len();
964 Ok(())
965 }
966 }
967 let mut storage = [0u8; 16];
968 let written = {
969 let mut buf = Buf(&mut storage, 0);
970 write!(buf, "{s}").unwrap();
971 buf.1
972 };
973 assert_eq!(&storage[..written], "��".as_bytes());
974 }
975
976 #[test]
977 fn test_as_ptr_and_borrow_hash_consistent() {
978 use core::borrow::Borrow;
979 use core::ffi::CStr as StdCStr;
980 use core::hash::{Hash, Hasher};
981 struct Fnv(u64);
982 impl Hasher for Fnv {
983 fn write(&mut self, b: &[u8]) {
984 for &x in b {
985 self.0 = self.0.wrapping_mul(0x100000001b3).wrapping_add(x as u64);
986 }
987 }
988 fn finish(&self) -> u64 {
989 self.0
990 }
991 }
992 let s: StackCStr<8> = "hi".try_into().unwrap();
993 assert!(!s.as_ptr().is_null());
994 assert_eq!(unsafe { StdCStr::from_ptr(s.as_ptr()) }, c"hi");
996 let borrowed: &StdCStr = s.borrow();
997 assert_eq!(borrowed, c"hi");
998 let mut h1 = Fnv(0xcbf29ce484222325);
999 s.hash(&mut h1);
1000 let mut h2 = Fnv(0xcbf29ce484222325);
1001 borrowed.hash(&mut h2);
1002 assert_eq!(h1.finish(), h2.finish());
1003 }
1004
1005 #[test]
1006 fn test_from_str_parse() {
1007 let s: StackCStr<8> = "hi".parse().unwrap();
1008 assert_eq!(&*s, c"hi");
1009 let res = "a\0b".parse::<StackCStr<8>>();
1010 assert!(matches!(res, Err(CStrError::InteriorNul(1))));
1011 }
1012
1013 #[cfg(feature = "alloc")]
1014 #[test]
1015 fn test_into_heap_roundtrip() {
1016 let s: StackCStr<4> = "hi".try_into().unwrap();
1017 let h = s.into_heap::<8>().unwrap();
1018 assert_eq!(&*h, c"hi");
1019 assert_eq!(h, HeapCStr::<8>::try_from("hi").unwrap());
1020 }
1021}