1#![allow(unused_qualifications)]
5#![allow(clippy::similar_names)]
6#![allow(unused)]
7
8#[cfg(feature = "alloc")]
9extern crate alloc;
10
11#[cfg(feature = "alloc")]
12use super::bytes::HeapBytes;
13use super::bytes::{Bytes, BytesError, BytesResult, StackBytes, volatile_zero};
14use core::convert::TryFrom;
15use core::str::Utf8Error;
16
17#[derive(Debug)]
33pub enum StrError {
34 CapacityExceeded(BytesError),
36 InvalidUtf8(Utf8Error),
38 NullPointer,
40}
41
42impl core::error::Error for StrError {}
43
44impl core::fmt::Display for StrError {
45 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46 match self {
47 Self::CapacityExceeded(e) => write!(f, "capacity exceeded: {e}"),
48 Self::InvalidUtf8(e) => write!(f, "invalid UTF-8 sequence: {e}"),
49 Self::NullPointer => write!(f, "received null pointer"),
50 }
51 }
52}
53
54#[inline(always)]
55fn safe_parse_and_wipe<F>(buffer: &mut [u8], mut push_fn: F) -> Result<(), StrError>
56where
57 F: FnMut(&str) -> Result<(), StrError>,
58{
59 let res = match ::core::str::from_utf8(buffer) {
60 Ok(valid_str) => push_fn(valid_str),
61 Err(e) => Err(StrError::InvalidUtf8(e)),
62 };
63 volatile_zero(buffer);
64 res
65}
66
67pub trait Str:
84 ::core::ops::Deref<Target = str> + ::core::fmt::Display + ::core::fmt::Debug + Send + Sync
85{
86 fn as_str(&self) -> &str;
92
93 #[inline(always)]
99 fn as_bytes(&self) -> &[u8] {
100 self.as_str().as_bytes()
101 }
102
103 #[cfg(feature = "alloc")]
109 fn dyn_clone(&self) -> alloc::boxed::Box<dyn Str>;
110
111 #[inline(always)]
117 fn len(&self) -> usize {
118 self.as_str().len()
119 }
120
121 #[inline(always)]
127 fn is_empty(&self) -> bool {
128 self.as_str().is_empty()
129 }
130}
131
132pub struct StackStr<const N: usize>(StackBytes<N>);
148
149#[cfg(feature = "alloc")]
165pub struct HeapStr<const N: usize>(HeapBytes<N>);
166
167impl<const N: usize> StackStr<N> {
168 pub const CAPACITY: usize = N;
174 #[inline(always)]
180 pub const fn new() -> Self {
181 Self(StackBytes::new())
182 }
183}
184
185#[cfg(feature = "alloc")]
186impl<const N: usize> HeapStr<N> {
187 pub const CAPACITY: usize = N;
193 #[inline(always)]
199 pub fn new() -> Self {
200 Self(HeapBytes::new())
201 }
202}
203
204macro_rules! impl_secure_str_base {
205 ($name:ident) => {
206 impl<const N: usize> $name<N> {
207 pub unsafe fn from_raw_parts_mut(ptr: *mut u8, len: usize) -> Result<Self, StrError> {
223 if ptr.is_null() {
224 return Err(StrError::NullPointer);
225 }
226 if len == 0 {
227 return Ok(Self::new());
228 }
229
230 let slice = unsafe { ::core::slice::from_raw_parts_mut(ptr, len) };
232
233 let mut out = Self::new();
234 safe_parse_and_wipe(slice, |s| out.try_push_str(s))?;
235 Ok(out)
236 }
237
238 #[inline(always)]
239 fn try_push_str(&mut self, s: &str) -> Result<(), StrError> {
240 self.0
241 .extend_from_slice(s.as_bytes())
242 .map_err(StrError::CapacityExceeded)
243 }
244
245 #[inline]
270 pub fn try_grow<const M: usize>(self) -> Result<$name<M>, StrError> {
271 if M < self.len() {
272 return Err(StrError::CapacityExceeded(BytesError::CapacityExceeded {
273 requested: self.len(),
274 max: M,
275 }));
276 }
277 let mut larger = $name::<M>::new();
278 larger
279 .0
280 .extend_from_slice(self.as_bytes())
281 .expect("Capacity already checked");
282 Ok(larger)
283 }
284
285 #[inline]
310 pub fn push_str_into<const M: usize>(self, suffix: &str) -> Result<$name<M>, StrError> {
311 let required_len = self.len() + suffix.len();
312 if required_len > M {
313 return Err(StrError::CapacityExceeded(BytesError::CapacityExceeded {
314 requested: required_len,
315 max: M,
316 }));
317 }
318 let mut larger = $name::<M>::new();
319 larger.0.extend_from_slice(self.as_bytes()).unwrap();
320 larger.0.extend_from_slice(suffix.as_bytes()).unwrap();
321 Ok(larger)
322 }
323
324 pub fn concat_into<const M: usize>(
345 self,
346 rhs: impl ::core::convert::AsRef<str>,
347 ) -> Result<$name<M>, StrError> {
348 let mut new_buf = $name::<M>::new();
349 new_buf.try_push_str(self.as_str())?;
350 new_buf.try_push_str(rhs.as_ref())?;
351 Ok(new_buf)
352 }
353 }
354
355 impl<const N: usize> ::core::default::Default for $name<N> {
356 #[inline(always)]
357 fn default() -> Self {
358 Self::new()
359 }
360 }
361
362 impl<const N: usize> Str for $name<N> {
363 #[inline(always)]
364 fn as_str(&self) -> &str {
365 unsafe { ::core::str::from_utf8_unchecked(self.0.as_slice()) }
367 }
368
369 #[cfg(feature = "alloc")]
370 #[inline(always)]
371 fn dyn_clone(&self) -> alloc::boxed::Box<dyn Str> {
372 alloc::boxed::Box::new(self.clone())
373 }
374 }
375
376 impl<const N: usize> ::core::clone::Clone for $name<N> {
377 #[inline(always)]
378 fn clone(&self) -> Self {
379 let mut new_str = Self::new();
380 let _ = new_str.0.extend_from_slice(self.as_bytes());
381 new_str
382 }
383 }
384 };
385}
386
387macro_rules! impl_try_from_traits {
388 ($name:ident) => {
389 impl<const N: usize> TryFrom<&str> for $name<N> {
390 type Error = StrError;
391 #[inline(always)]
392 fn try_from(s: &str) -> Result<Self, Self::Error> {
393 let mut out = Self::new();
394 out.try_push_str(s)?;
395 Ok(out)
396 }
397 }
398
399 impl<const N: usize> ::core::str::FromStr for $name<N> {
400 type Err = StrError;
401 #[inline(always)]
402 fn from_str(s: &str) -> Result<Self, Self::Err> {
403 Self::try_from(s)
404 }
405 }
406
407 impl<const N: usize> TryFrom<&mut [u8; N]> for $name<N> {
408 type Error = StrError;
409 fn try_from(arr: &mut [u8; N]) -> Result<Self, Self::Error> {
410 let mut out = Self::new();
411 safe_parse_and_wipe(arr, |s| out.try_push_str(s))?;
412 Ok(out)
413 }
414 }
415
416 impl<const N: usize> TryFrom<&mut [u8]> for $name<N> {
417 type Error = StrError;
418 fn try_from(arr: &mut [u8]) -> Result<Self, Self::Error> {
419 let mut out = Self::new();
420 safe_parse_and_wipe(arr, |s| out.try_push_str(s))?;
421 Ok(out)
422 }
423 }
424
425 #[cfg(feature = "alloc")]
426 impl<const N: usize> TryFrom<alloc::boxed::Box<[u8; N]>> for $name<N> {
427 type Error = StrError;
428 fn try_from(mut b: alloc::boxed::Box<[u8; N]>) -> Result<Self, Self::Error> {
429 let mut out = Self::new();
430 safe_parse_and_wipe(&mut *b, |s| out.try_push_str(s))?;
431 Ok(out)
432 }
433 }
434
435 #[cfg(feature = "alloc")]
436 impl<const N: usize> TryFrom<alloc::vec::Vec<u8>> for $name<N> {
437 type Error = StrError;
438 fn try_from(mut v: alloc::vec::Vec<u8>) -> Result<Self, Self::Error> {
439 let mut out = Self::new();
440 safe_parse_and_wipe(v.as_mut_slice(), |s| out.try_push_str(s))?;
441 Ok(out)
442 }
443 }
444
445 #[cfg(feature = "alloc")]
446 impl<const N: usize> TryFrom<alloc::string::String> for $name<N> {
447 type Error = StrError;
448 #[inline(always)]
449 fn try_from(s: alloc::string::String) -> Result<Self, Self::Error> {
450 Self::try_from(s.into_bytes())
451 }
452 }
453 };
454}
455
456macro_rules! __impl_common_str_traits {
457 ($name:ident) => {
458 impl<const N: usize> ::core::ops::Deref for $name<N> {
459 type Target = str;
460 #[inline(always)]
461 fn deref(&self) -> &Self::Target {
462 self.as_str()
463 }
464 }
465 impl<const N: usize> ::core::fmt::Display for $name<N> {
466 #[inline(always)]
467 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
468 f.write_str(self.as_str())
469 }
470 }
471 impl<const N: usize> ::core::fmt::Debug for $name<N> {
472 #[inline(always)]
473 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
474 ::core::fmt::Debug::fmt(self.as_str(), f)
475 }
476 }
477 impl<const N: usize> ::core::convert::AsRef<str> for $name<N> {
478 #[inline(always)]
479 fn as_ref(&self) -> &str {
480 self.as_str()
481 }
482 }
483 impl<const N: usize> ::core::convert::AsRef<[u8]> for $name<N> {
484 #[inline(always)]
485 fn as_ref(&self) -> &[u8] {
486 self.as_bytes()
487 }
488 }
489 impl<const N: usize> ::core::cmp::PartialEq<str> for $name<N> {
490 #[inline(always)]
491 fn eq(&self, other: &str) -> bool {
492 self.as_str() == other
493 }
494 }
495 impl<'a, const N: usize> ::core::cmp::PartialEq<&'a str> for $name<N> {
496 #[inline(always)]
497 fn eq(&self, other: &&'a str) -> bool {
498 self.as_str() == *other
499 }
500 }
501 impl<const N: usize, const M: usize> ::core::cmp::PartialEq<$name<M>> for $name<N> {
502 #[inline(always)]
503 fn eq(&self, other: &$name<M>) -> bool {
504 self.as_str() == other.as_str()
505 }
506 }
507 #[cfg(feature = "alloc")]
508 impl<const N: usize> ::core::cmp::PartialEq<alloc::string::String> for $name<N> {
509 #[inline(always)]
510 fn eq(&self, other: &alloc::string::String) -> bool {
511 self.as_str() == other.as_str()
512 }
513 }
514 #[cfg(feature = "alloc")]
515 impl<const N: usize> ::core::cmp::PartialEq<$name<N>> for alloc::string::String {
516 #[inline(always)]
517 fn eq(&self, other: &$name<N>) -> bool {
518 self.as_str() == other.as_str()
519 }
520 }
521 impl<const N: usize> ::core::cmp::Eq for $name<N> {}
522 impl<const N: usize> ::core::cmp::PartialOrd for $name<N> {
523 #[inline(always)]
524 fn partial_cmp(&self, other: &Self) -> ::core::option::Option<::core::cmp::Ordering> {
525 Some(self.cmp(other))
526 }
527 }
528 impl<const N: usize> ::core::cmp::Ord for $name<N> {
529 #[inline(always)]
530 fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
531 self.as_str().cmp(other.as_str())
532 }
533 }
534 impl<const N: usize> ::core::hash::Hash for $name<N> {
535 #[inline(always)]
536 fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
537 self.as_str().hash(state)
538 }
539 }
540 impl<const N: usize> ::core::borrow::Borrow<str> for $name<N> {
541 #[inline(always)]
542 fn borrow(&self) -> &str {
543 self.as_str()
544 }
545 }
546 };
547}
548
549impl_secure_str_base!(StackStr);
550impl_try_from_traits!(StackStr);
551__impl_common_str_traits!(StackStr);
552
553#[cfg(feature = "alloc")]
554impl_secure_str_base!(HeapStr);
555#[cfg(feature = "alloc")]
556impl_try_from_traits!(HeapStr);
557#[cfg(feature = "alloc")]
558__impl_common_str_traits!(HeapStr);
559
560#[cfg(feature = "alloc")]
561impl<const N: usize> HeapStr<N> {
562 #[inline(always)]
568 #[deprecated(note = "This leaks secure memory into alloc::string::String. Use with caution.")]
569 pub fn leak_into_string(self) -> alloc::string::String {
570 use alloc::borrow::ToOwned;
571 self.as_str().to_owned()
572 }
573}
574
575#[cfg(feature = "alloc")]
576impl<const N: usize> StackStr<N> {
577 #[inline]
602 pub fn into_heap<const M: usize>(self) -> Result<HeapStr<M>, StrError> {
603 if M < self.len() {
604 return Err(StrError::CapacityExceeded(BytesError::CapacityExceeded {
605 requested: self.len(),
606 max: M,
607 }));
608 }
609 let mut heap_str = HeapStr::<M>::new();
610 heap_str
611 .0
612 .extend_from_slice(self.as_bytes())
613 .expect("Capacity checked");
614 Ok(heap_str)
615 }
616}
617
618#[cfg(feature = "alloc")]
619impl<const N: usize, const M: usize> ::core::cmp::PartialEq<StackStr<M>> for HeapStr<N> {
620 #[inline(always)]
621 fn eq(&self, other: &StackStr<M>) -> bool {
622 self.as_str() == other.as_str()
623 }
624}
625#[cfg(feature = "alloc")]
626impl<const N: usize, const M: usize> ::core::cmp::PartialEq<HeapStr<N>> for StackStr<M> {
627 #[inline(always)]
628 fn eq(&self, other: &HeapStr<N>) -> bool {
629 self.as_str() == other.as_str()
630 }
631}
632
633#[cfg(feature = "alloc")]
634impl ::core::clone::Clone for alloc::boxed::Box<dyn Str> {
635 #[inline(always)]
636 fn clone(&self) -> Self {
637 self.dyn_clone()
638 }
639}
640#[cfg(feature = "alloc")]
641impl ::core::cmp::PartialEq for alloc::boxed::Box<dyn Str> {
642 #[inline(always)]
643 fn eq(&self, other: &Self) -> bool {
644 self.as_str() == other.as_str()
645 }
646}
647#[cfg(feature = "alloc")]
648impl ::core::cmp::Eq for alloc::boxed::Box<dyn Str> {}
649#[cfg(feature = "alloc")]
650impl ::core::cmp::PartialEq<str> for alloc::boxed::Box<dyn Str> {
651 #[inline(always)]
652 fn eq(&self, other: &str) -> bool {
653 self.as_str() == other
654 }
655}
656#[cfg(feature = "alloc")]
657impl ::core::cmp::PartialEq<&str> for alloc::boxed::Box<dyn Str> {
658 #[inline(always)]
659 fn eq(&self, other: &&str) -> bool {
660 self.as_str() == *other
661 }
662}
663#[cfg(feature = "alloc")]
664impl ::core::cmp::PartialEq<alloc::string::String> for alloc::boxed::Box<dyn Str> {
665 #[inline(always)]
666 fn eq(&self, other: &alloc::string::String) -> bool {
667 self.as_str() == other.as_str()
668 }
669}
670
671#[cfg(test)]
672#[allow(clippy::unwrap_used, clippy::expect_used)]
673mod tests {
674 use super::*;
675 #[cfg(feature = "alloc")]
676 use alloc::{boxed::Box, vec, vec::Vec};
677 use core::convert::TryInto;
678
679 #[test]
680 fn test_stack_from_str_literal() {
681 let s: StackStr<8> = "hello".try_into().unwrap();
682 assert_eq!(s, "hello");
683 }
684
685 #[cfg(feature = "alloc")]
686 #[test]
687 fn test_heap_from_str_literal() {
688 let h: HeapStr<16> = "hello".try_into().unwrap();
689 assert_eq!(h, "hello");
690 }
691
692 #[cfg(feature = "alloc")]
693 #[test]
694 fn test_vec_dyn_str() {
695 let mut secret_list: Vec<Box<dyn Str>> = Vec::new();
696
697 let h: HeapStr<256> = "SuperSecret_Heap".try_into().unwrap();
698 secret_list.push(Box::new(h));
699
700 let s: StackStr<16> = "Short_Stack".try_into().unwrap();
701 secret_list.push(Box::new(s));
702
703 let s2: StackStr<32> = "Another_Stack_32".try_into().unwrap();
704 secret_list.push(Box::new(s2));
705
706 assert_eq!(secret_list.len(), 3);
707 assert!(secret_list[0].starts_with("Super"));
708 assert!(secret_list[1].ends_with("Stack"));
709 }
710
711 #[cfg(feature = "alloc")]
712 #[test]
713 fn from_raw_ptr_with_wiping() {
714 let mut source_data = b"secret_data".to_vec();
715 let ptr = source_data.as_mut_ptr();
716 let len = source_data.len();
717
718 let h = unsafe { HeapStr::<32>::from_raw_parts_mut(ptr, len) }.unwrap();
719 assert_eq!(h.as_str(), "secret_data");
720
721 assert_eq!(source_data, vec![0u8; 11]);
722 }
723
724 #[test]
725 fn test_capacity_exceeded_fail_fast() {
726 let res: Result<StackStr<4>, _> = "hello".try_into();
727 assert!(res.is_err());
728 }
729
730 #[test]
731 fn test_invalid_utf8_wiping() {
732 let mut bad_utf8 = [0, 159, 146, 150];
733 let res = StackStr::<16>::try_from(bad_utf8.as_mut_slice());
734 assert!(res.is_err());
735 assert_eq!(bad_utf8, [0u8; 4]);
736 }
737}