Skip to main content

lib_unknown/types/
cstr.rs

1//! 定容 C 字符串容器:栈上(`StackCStr`)与堆上(`HeapCStr`,需 `alloc`)实现,内部恒以 `\0` 结尾,搬运/销毁时清零残留。
2//!
3//! 与 [`crate::types::str`] 不同,本模块**允许非 UTF-8 字节**,仅校验 NUL 语义,
4//! 可直接投喂 `sys_open` / `sys_execve` 等取 `core::ffi::CStr` 的调用。
5//!
6//! # 容量语义
7//!
8//! `N` **包含结尾 `\0`**(`N >= 1`):有效载荷至多 `N - 1` 字节。
9//! `len()` 返回不含 `\0` 的载荷长度。
10//!
11//! 需要启用 `"types-cstr"` 特性。
12#![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/// 安全 C 字符串的错误类型。
24///
25/// # Feature Requirement
26///
27/// 需要启用 `"types-cstr"` 特性。
28///
29/// # Examples
30///
31/// ```rust
32/// use core::convert::TryInto;
33/// use lib_unknown::types::cstr::{CStr, StackCStr};
34///
35/// let s: StackCStr<4> = "hi".try_into().unwrap();
36/// assert_eq!(s.len(), 2);
37/// let res: Result<StackCStr<4>, _> = "toolong".try_into();
38/// assert!(res.is_err());
39/// ```
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum CStrError {
42    /// 长度超出容器容量(含结尾 `\0` 的总需求超出 `N`)。
43    CapacityExceeded(BytesError),
44    /// 载荷内部含 NUL,载荷为 NUL 在载荷内的字节下标。
45    InteriorNul(usize),
46    /// 输入中找不到结尾 NUL(源仍会被清零)。
47    MissingNul,
48    /// 收到空指针。
49    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    // SAFETY 交给 push_fn 的借用在 wipe 前结束;此处仅对源做易失清零。
76    res
77}
78
79/// 安全 C 字符串的统一接口:只读视图,内部恒为合法 `CStr`(以 `\0` 结尾、无内部 NUL)。
80///
81/// 注意:本 trait 名为 `CStr`,与 `core::ffi::CStr` 同名。取值时用
82/// [`CStr::as_cstr`] 拿到底层 `&core::ffi::CStr`。
83///
84/// # Feature Requirement
85///
86/// 需要启用 `"types-cstr"` 特性。
87///
88/// # Examples
89///
90/// ```rust
91/// use core::convert::TryInto;
92/// use lib_unknown::types::cstr::{CStr, StackCStr};
93///
94/// let s: StackCStr<8> = "hi".try_into().unwrap();
95/// assert_eq!(s.as_cstr().to_bytes(), b"hi");
96/// assert_eq!(s.len(), 2);
97/// ```
98pub trait CStr:
99    ::core::ops::Deref<Target = StdCStr> + ::core::fmt::Display + ::core::fmt::Debug + Send + Sync
100{
101    /// 以 `&core::ffi::CStr` 形式查看内容,可直接传给 `sys_open` 等。
102    ///
103    /// # Feature Requirement
104    ///
105    /// 需要启用 `"types-cstr"` 特性。
106    fn as_cstr(&self) -> &StdCStr;
107
108    /// 以字节切片查看载荷(不含结尾 `\0`),允许非 UTF-8。
109    ///
110    /// # Feature Requirement
111    ///
112    /// 需要启用 `"types-cstr"` 特性。
113    #[inline(always)]
114    fn as_bytes(&self) -> &[u8] {
115        self.as_cstr().to_bytes()
116    }
117
118    /// 以字节切片查看含结尾 `\0` 的完整存储。
119    ///
120    /// # Feature Requirement
121    ///
122    /// 需要启用 `"types-cstr"` 特性。
123    #[inline(always)]
124    fn as_bytes_with_nul(&self) -> &[u8] {
125        self.as_cstr().to_bytes_with_nul()
126    }
127
128    /// 返回载荷的原始指针,指向容器内部存储(等价于 `as_cstr().as_ptr()`)。
129    ///
130    /// 返回的指针仅在 `self` 存活且未被搬运/修改期间有效;`self` 移动、`Drop`
131    /// 或任何 `&mut self` 操作后不得再使用旧指针。
132    ///
133    /// # Feature Requirement
134    ///
135    /// 需要启用 `"types-cstr"` 特性。
136    #[inline(always)]
137    fn as_ptr(&self) -> *const core::ffi::c_char {
138        self.as_cstr().as_ptr()
139    }
140
141    /// 类型擦除的堆克隆,用于 `Box<dyn CStr>` 容器。
142    ///
143    /// # Feature Requirement
144    ///
145    /// 需要启用 `"types-cstr"` 与 `"alloc"` 特性。
146    #[cfg(feature = "alloc")]
147    fn dyn_clone(&self) -> alloc::boxed::Box<dyn CStr>;
148
149    /// 返回载荷的字节长度(不含结尾 `\0`)。
150    ///
151    /// # Feature Requirement
152    ///
153    /// 需要启用 `"types-cstr"` 特性。
154    #[inline(always)]
155    fn len(&self) -> usize {
156        self.as_cstr().to_bytes().len()
157    }
158
159    /// 载荷是否为空。
160    ///
161    /// # Feature Requirement
162    ///
163    /// 需要启用 `"types-cstr"` 特性。
164    #[inline(always)]
165    fn is_empty(&self) -> bool {
166        self.as_cstr().to_bytes().is_empty()
167    }
168}
169
170/// 栈上定容安全 C 字符串,`Drop` 时自动清零,适用于短路径、argv 等小敏感文本。
171///
172/// `N` 包含结尾 `\0`,`N >= 1`。
173///
174/// # Feature Requirement
175///
176/// 需要启用 `"types-cstr"` 特性。
177///
178/// # Examples
179///
180/// ```rust
181/// use core::convert::TryInto;
182/// use lib_unknown::types::cstr::StackCStr;
183///
184/// let s: StackCStr<8> = "hi".try_into().unwrap();
185/// assert_eq!(&*s, c"hi");
186/// ```
187pub struct StackCStr<const N: usize> {
188    len: usize,
189    data: [u8; N],
190}
191
192/// 堆上定容安全 C 字符串,语义与 [`StackCStr`] 一致,适用于较长的敏感文本。
193///
194/// `N` 包含结尾 `\0`,`N >= 1`。
195///
196/// # Feature Requirement
197///
198/// 需要启用 `"types-cstr"` 与 `"alloc"` 特性。
199///
200/// # Examples
201///
202/// ```rust
203/// use core::convert::TryInto;
204/// use lib_unknown::types::cstr::HeapCStr;
205///
206/// let s: HeapCStr<16> = "hi".try_into().unwrap();
207/// assert_eq!(&*s, c"hi");
208/// ```
209#[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    /// 容器总容量(含结尾 `\0`)。
217    ///
218    /// # Feature Requirement
219    ///
220    /// 需要启用 `"types-cstr"` 特性。
221    pub const CAPACITY: usize = N;
222
223    /// 创建空 C 字符串(仅含结尾 `\0`)。
224    ///
225    /// # Feature Requirement
226    ///
227    /// 需要启用 `"types-cstr"` 特性。
228    ///
229    /// # Panics
230    ///
231    /// - 当 `N == 0` 时 panic:连结尾 `\0` 都放不下。
232    #[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    /// 容器总容量(含结尾 `\0`)。
245    ///
246    /// # Feature Requirement
247    ///
248    /// 需要启用 `"types-cstr"` 与 `"alloc"` 特性。
249    pub const CAPACITY: usize = N;
250
251    /// 创建空 C 字符串(仅含结尾 `\0`)。
252    ///
253    /// # Feature Requirement
254    ///
255    /// 需要启用 `"types-cstr"` 与 `"alloc"` 特性。
256    ///
257    /// # Panics
258    ///
259    /// - 当 `N == 0` 时 panic:连结尾 `\0` 都放不下。
260    #[inline(always)]
261    pub fn new() -> Self {
262        assert!(N > 0, "HeapCStr capacity N must include the NUL (>= 1)");
263        // SAFETY: `[u8; N]` 全零为合法值(首字节 `\0` 即空 C 串),`assume_init` 安全。
264        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            /// 从原始可变指针拷贝并擦除源缓冲构造安全 C 字符串。
299            ///
300            /// 在 `len` 范围内找首个 `\0` 截断为载荷;找不到则报
301            /// [`CStrError::MissingNul`]。无论成败都清零源 `len` 字节。
302            ///
303            /// # Safety
304            ///
305            /// 调用此函数必须保证:
306            /// 1. `ptr` 非空且在 `len` 范围内可读写、正确对齐;
307            /// 2. 调用期间该内存不被其他线程并发访问。
308            ///
309            /// # Errors
310            ///
311            /// - `ptr` 为空时返回 [`CStrError::NullPointer`]。
312            /// - `len` 内无 `\0` 时返回 [`CStrError::MissingNul`]。
313            /// - 载荷(含结尾 `\0`)超出 `N` 时返回 [`CStrError::CapacityExceeded`]。
314            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                // SAFETY: `ptr` 非空已检查;有效性、对齐与独占性由本函数的 `# Safety` 约定保证。
323                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            /// 将内容搬运到容量为 `M` 的同类 C 字符串中。
337            ///
338            /// 本方法按值取走 `self`,原容器在返回前(无论成败)被清零。
339            ///
340            /// # Feature Requirement
341            ///
342            /// 需要启用 `"types-cstr"` 特性(`HeapCStr` 相关还需 `"alloc"` 特性)。
343            ///
344            /// # Examples
345            ///
346            /// ```rust
347            /// use core::convert::TryInto;
348            /// use lib_unknown::types::cstr::StackCStr;
349            ///
350            /// let s: StackCStr<4> = "hi".try_into().unwrap();
351            /// let c = s.try_grow::<8>().unwrap();
352            /// assert_eq!(&*c, c"hi");
353            /// ```
354            ///
355            /// # Errors
356            ///
357            /// - 当已有长度(含 `\0`)超出 `M` 时返回 [`CStrError::CapacityExceeded`]。
358            ///
359            /// # Panics
360            ///
361            /// - 内部 `expect("Capacity already checked")`:前置长度检查保证不触发,仅防御性保留。
362            #[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            /// 将自身与 `suffix` 拼接为容量 `M` 的新 C 字符串。
378            ///
379            /// `suffix` 为不含 `\0` 的载荷字节(允许非 UTF-8);含 `\0` 时报
380            /// [`CStrError::InteriorNul`],其下标为拼接后载荷中的绝对下标。
381            /// 本方法按值取走 `self`,原容器在返回前(无论成败)被清零。
382            ///
383            /// # Feature Requirement
384            ///
385            /// 需要启用 `"types-cstr"` 特性(`HeapCStr` 相关还需 `"alloc"` 特性)。
386            ///
387            /// # Examples
388            ///
389            /// ```rust
390            /// use core::convert::TryInto;
391            /// use lib_unknown::types::cstr::StackCStr;
392            ///
393            /// let s: StackCStr<4> = "hi".try_into().unwrap();
394            /// let c = s.push_bytes_into::<8>(b"!").unwrap();
395            /// assert_eq!(&*c, c"hi!");
396            /// ```
397            ///
398            /// # Errors
399            ///
400            /// - 当拼接后长度(含 `\0`)超出 `M` 时返回 [`CStrError::CapacityExceeded`]。
401            /// - 当 `suffix` 含 `\0` 时返回 [`CStrError::InteriorNul`]。
402            ///
403            /// # Panics
404            ///
405            /// - 两处内部 `expect("Capacity already checked")`:前置长度检查保证不触发,仅防御性保留。
406            #[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            /// 将自身与 `rhs` 拼接为容量 `M` 的新 C 字符串(`rhs` 为任意可借用为字节切片的类型)。
446            ///
447            /// 本方法按值取走 `self`,原容器在返回前(无论成败)被清零。
448            ///
449            /// # Feature Requirement
450            ///
451            /// 需要启用 `"types-cstr"` 特性(`HeapCStr` 相关还需 `"alloc"` 特性)。
452            ///
453            /// # Examples
454            ///
455            /// ```rust
456            /// use core::convert::TryInto;
457            /// use lib_unknown::types::cstr::StackCStr;
458            ///
459            /// let s: StackCStr<4> = "hi".try_into().unwrap();
460            /// let c = s.concat_into::<8>(b"!").unwrap();
461            /// assert_eq!(&*c, c"hi!");
462            /// ```
463            ///
464            /// # Errors
465            ///
466            /// - 当拼接后长度(含 `\0`)超出 `M` 时返回 [`CStrError::CapacityExceeded`]。
467            /// - 当 `rhs` 含 `\0` 时返回 [`CStrError::InteriorNul`],其下标为拼接后载荷中的绝对下标。
468            ///
469            /// # Panics
470            ///
471            /// - 内部 `expect("Capacity already checked")`:前置长度检查保证不触发,仅防御性保留。
472            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                // SAFETY: 类型不变式保证 `data[..len]` 无内部 NUL 且 `data[len] == 0`;
498                // 所有写入经 `try_push_bytes` 校验,`new` 置首字节为 0。
499                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                    // SAFETY: `valid_up_to` 前缀经校验为合法 UTF-8。
622                    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                // 必须与 `core::ffi::CStr` 自身的 `Hash`(派生自含结尾 `\0` 的内部切片)一致,
721                // 否则违反 `Borrow<StdCStr>` 的哈希契约(以 `&CStr` 查 `HashMap` 会失效)。
722                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    /// 将栈 C 字符串搬运为容量 `M` 的堆 C 字符串。
748    ///
749    /// 本方法按值取走 `self`,原容器在返回前(无论成败)被清零。
750    ///
751    /// # Feature Requirement
752    ///
753    /// 需要启用 `"types-cstr"` 与 `"alloc"` 特性。
754    ///
755    /// # Examples
756    ///
757    /// ```rust
758    /// use core::convert::TryInto;
759    /// use lib_unknown::types::cstr::StackCStr;
760    ///
761    /// let s: StackCStr<4> = "hi".try_into().unwrap();
762    /// let h = s.into_heap::<8>().unwrap();
763    /// assert_eq!(&*h, c"hi");
764    /// ```
765    ///
766    /// # Errors
767    ///
768    /// - 当已有长度(含 `\0`)超出 `M` 时返回 [`CStrError::CapacityExceeded`]。
769    ///
770    /// # Panics
771    ///
772    /// - 内部 `expect("Capacity checked")`:前置长度检查保证不触发,仅防御性保留。
773    #[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        // Display 走 lossy,不 panic 即可
841        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        // SAFETY: `ptr` 指向 `source_data` 的独占可写借用派生的有效内存,长度 `len` 精确覆盖向量本体,测试内无并发访问。
892        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        // N 含 \0:容量 4 只能放 3 载荷字节
901        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        // SAFETY: `as_ptr` 指向 `s` 内部以 `\0` 结尾的有效存储,断言求值期间 `s` 存活且无修改。
995        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}