heapless/
c_string.rs

1//! A fixed capacity [`CString`](https://doc.rust-lang.org/std/ffi/struct.CString.html).
2
3use crate::{vec::Vec, CapacityError, LenType};
4use core::{
5    borrow::Borrow,
6    cmp::Ordering,
7    error::Error,
8    ffi::{c_char, CStr, FromBytesWithNulError},
9    fmt,
10    ops::Deref,
11};
12
13#[cfg(feature = "zeroize")]
14use zeroize::Zeroize;
15
16/// A fixed capacity [`CString`](https://doc.rust-lang.org/std/ffi/struct.CString.html).
17///
18/// It stores up to `N - 1` non-nul characters with a trailing nul terminator.
19#[derive(Clone, Hash)]
20pub struct CString<const N: usize, LenT: LenType = usize> {
21    inner: Vec<u8, N, LenT>,
22}
23
24#[cfg(feature = "zeroize")]
25impl<const N: usize, LenT: LenType> Zeroize for CString<N, LenT> {
26    fn zeroize(&mut self) {
27        self.inner.zeroize();
28
29        const {
30            assert!(N > 0);
31        }
32
33        // SAFETY: We just asserted that `N > 0`.
34        unsafe { self.inner.push_unchecked(b'\0') };
35    }
36}
37
38impl<const N: usize, LenT: LenType> CString<N, LenT> {
39    /// Creates a new C-compatible string with a terminating nul byte.
40    ///
41    /// ```rust
42    /// use heapless::CString;
43    ///
44    /// // A fixed-size `CString` that can store up to 10 characters
45    /// // including the nul terminator.
46    /// let empty = CString::<10>::new();
47    ///
48    /// assert_eq!(empty.as_c_str(), c"");
49    /// assert_eq!(empty.to_str(), Ok(""));
50    /// ```
51    pub fn new() -> Self {
52        const {
53            assert!(N > 0);
54        }
55
56        let mut inner = Vec::new();
57
58        // SAFETY: We just asserted that `N > 0`.
59        unsafe { inner.push_unchecked(b'\0') };
60
61        Self { inner }
62    }
63
64    /// Unsafely creates a [`CString`] from a byte slice.
65    ///
66    /// This function will copy the provided `bytes` to a [`CString`] without
67    /// performing any sanity checks.
68    ///
69    /// The function will fail if `bytes.len() > N`.
70    ///
71    /// # Safety
72    ///
73    /// The provided slice **must** be nul-terminated and not contain any interior
74    /// nul bytes.
75    ///
76    /// # Examples
77    ///
78    /// ```rust
79    /// use heapless::CString;
80    /// let mut c_string = unsafe { CString::<7>::from_bytes_with_nul_unchecked(b"string\0").unwrap() };
81    ///
82    /// assert_eq!(c_string.to_str(), Ok("string"));
83    /// ```
84    pub unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> Result<Self, CapacityError> {
85        let mut inner = Vec::new();
86
87        inner.extend_from_slice(bytes)?;
88
89        Ok(Self { inner })
90    }
91
92    /// Instantiates a [`CString`] copying from the giving byte slice, assuming it is
93    /// nul-terminated.
94    ///
95    /// Fails if the given byte slice has any interior nul byte, if the slice does not
96    /// end with a nul byte, or if the byte slice can't fit in `N`.
97    pub fn from_bytes_with_nul(bytes: &[u8]) -> Result<Self, ExtendError> {
98        let mut string = Self::new();
99
100        string.extend_from_bytes(bytes)?;
101
102        Ok(string)
103    }
104
105    /// Builds a [`CString`] copying from a raw C string pointer.
106    ///
107    /// # Safety
108    ///
109    /// - The memory pointed to by `ptr` must contain a valid nul terminator at the end of the
110    ///   string.
111    /// - `ptr` must be valid for reads of bytes up to and including the nul terminator. This means
112    ///   in particular:
113    ///     - The entire memory range of this `CStr` must be contained within a single allocated
114    ///       object!
115    ///     - `ptr` must be non-nul even for a zero-length `CStr`.
116    ///
117    /// # Example
118    ///
119    /// ```rust
120    /// use core::ffi::{c_char, CStr};
121    /// use heapless::CString;
122    ///
123    /// const HELLO_PTR: *const c_char = {
124    ///     const BYTES: &[u8] = b"Hello, world!\0";
125    ///     BYTES.as_ptr().cast()
126    /// };
127    ///
128    /// let copied = unsafe { CString::<14>::from_raw(HELLO_PTR) }.unwrap();
129    ///
130    /// assert_eq!(copied.to_str(), Ok("Hello, world!"));
131    /// ```
132    pub unsafe fn from_raw(ptr: *const c_char) -> Result<Self, ExtendError> {
133        // SAFETY: The given pointer to a string is assumed to be nul-terminated.
134        Self::from_bytes_with_nul(unsafe { CStr::from_ptr(ptr).to_bytes_with_nul() })
135    }
136
137    /// Converts the [`CString`] to a [`CStr`] slice.
138    #[inline]
139    pub fn as_c_str(&self) -> &CStr {
140        unsafe { CStr::from_bytes_with_nul_unchecked(&self.inner) }
141    }
142
143    /// Calculates the length of `self.inner` would have if it appended `bytes`.
144    fn capacity_with_bytes(&self, bytes: &[u8]) -> Option<usize> {
145        match bytes.last() {
146            None => None,
147            Some(0) if bytes.len() < 2 => None,
148            Some(0) => {
149                // `bytes` is nul-terminated and so is `self.inner`.
150                // Adding up both would account for 2 nul bytes when only a single byte
151                // would end up in the resulting CString.
152                Some(self.inner.len() + bytes.len() - 1)
153            }
154            Some(_) => {
155                // No terminating nul byte in `bytes` but there's one in
156                // `self.inner`, so the math lines up nicely.
157                //
158                // In the case that `bytes` has a nul byte anywhere else, we would
159                // error after `memchr` is called. So there's no problem.
160                Some(self.inner.len() + bytes.len())
161            }
162        }
163    }
164
165    /// Extends the [`CString`] with the given bytes.
166    ///
167    /// This function fails if the [`CString`] would not have enough capacity to append the bytes or
168    /// if the bytes contain an interior nul byte.
169    ///
170    /// # Example
171    ///
172    /// ```rust
173    /// use heapless::CString;
174    ///
175    /// let mut c_string = CString::<10>::new();
176    ///
177    /// c_string.extend_from_bytes(b"hey").unwrap();
178    /// c_string.extend_from_bytes(b" there\0").unwrap();
179    ///
180    /// assert_eq!(c_string.to_str(), Ok("hey there"));
181    /// ```
182    pub fn extend_from_bytes(&mut self, bytes: &[u8]) -> Result<(), ExtendError> {
183        let Some(capacity) = self.capacity_with_bytes(bytes) else {
184            return Ok(());
185        };
186
187        if capacity > N {
188            // Cannot store these bytes due to an insufficient capacity.
189            return Err(CapacityError.into());
190        }
191
192        match CStr::from_bytes_with_nul(bytes) {
193            Ok(_) => {
194                // SAFETY: A string is left in a valid state because appended bytes are
195                // nul-terminated.
196                unsafe { self.extend_from_bytes_unchecked(bytes) }?;
197
198                Ok(())
199            }
200            Err(FromBytesWithNulError::InteriorNul { position }) => {
201                Err(ExtendError::InteriorNul { position })
202            }
203            Err(FromBytesWithNulError::NotNulTerminated) => {
204                // Because given bytes has no nul byte anywhere, we insert the bytes and
205                // then add the nul byte terminator.
206                //
207                // We've ensured above that we have enough space left to insert these bytes,
208                // so the operations below must succeed.
209                //
210                // SAFETY: We append a missing nul terminator right below.
211                unsafe {
212                    self.extend_from_bytes_unchecked(bytes).unwrap();
213                    self.inner.push_unchecked(0);
214                };
215
216                Ok(())
217            }
218        }
219    }
220
221    /// Removes the nul byte terminator from the inner buffer.
222    ///
223    /// # Safety
224    ///
225    /// Callers must ensure to add the nul terminator back after this function is called.
226    #[inline]
227    unsafe fn pop_terminator(&mut self) {
228        debug_assert_eq!(self.inner.last(), Some(&0));
229
230        // SAFETY: We always have the nul terminator at the end.
231        unsafe { self.inner.pop_unchecked() };
232    }
233
234    /// Removes the existing nul terminator and then extends `self` with the given bytes.
235    ///
236    /// # Safety
237    ///
238    /// If `additional` is not nul-terminated, the [`CString`] is left non nul-terminated, which is
239    /// an invalid state. Caller must ensure that either `additional` has a terminating nul byte
240    /// or ensure to append a trailing nul terminator.
241    unsafe fn extend_from_bytes_unchecked(
242        &mut self,
243        additional: &[u8],
244    ) -> Result<(), CapacityError> {
245        // SAFETY: A caller is responsible for adding a nul terminator back to the inner buffer.
246        unsafe { self.pop_terminator() }
247
248        self.inner.extend_from_slice(additional)
249    }
250
251    /// Returns the underlying byte slice including the trailing nul terminator.
252    ///
253    /// # Example
254    ///
255    /// ```rust
256    /// use heapless::CString;
257    ///
258    /// let mut c_string = CString::<5>::new();
259    /// c_string.extend_from_bytes(b"abc").unwrap();
260    ///
261    /// assert_eq!(c_string.as_bytes_with_nul(), b"abc\0");
262    /// ```
263    #[inline]
264    pub fn as_bytes_with_nul(&self) -> &[u8] {
265        &self.inner
266    }
267
268    /// Returns the underlying byte slice excluding the trailing nul terminator.
269    ///
270    /// # Example
271    ///
272    /// ```rust
273    /// use heapless::CString;
274    ///
275    /// let mut c_string = CString::<5>::new();
276    /// c_string.extend_from_bytes(b"abc").unwrap();
277    ///
278    /// assert_eq!(c_string.as_bytes(), b"abc");
279    /// ```
280    #[inline]
281    pub fn as_bytes(&self) -> &[u8] {
282        &self.inner[..self.inner.len() - 1]
283    }
284}
285
286impl<const N: usize, LenT: LenType> AsRef<CStr> for CString<N, LenT> {
287    #[inline]
288    fn as_ref(&self) -> &CStr {
289        self.as_c_str()
290    }
291}
292
293impl<const N: usize, LenT: LenType> Borrow<CStr> for CString<N, LenT> {
294    #[inline]
295    fn borrow(&self) -> &CStr {
296        self.as_c_str()
297    }
298}
299
300impl<const N: usize, LenT: LenType> Default for CString<N, LenT> {
301    #[inline]
302    fn default() -> Self {
303        Self::new()
304    }
305}
306
307impl<const N: usize, LenT: LenType> Deref for CString<N, LenT> {
308    type Target = CStr;
309
310    #[inline]
311    fn deref(&self) -> &Self::Target {
312        self.as_c_str()
313    }
314}
315
316impl<const N: usize, const M: usize, LenT1: LenType, LenT2: LenType> PartialEq<CString<M, LenT2>>
317    for CString<N, LenT1>
318{
319    #[inline]
320    fn eq(&self, rhs: &CString<M, LenT2>) -> bool {
321        self.as_c_str() == rhs.as_c_str()
322    }
323}
324
325impl<const N: usize, LenT: LenType> Eq for CString<N, LenT> {}
326
327impl<const N: usize, const M: usize, LenT1: LenType, LenT2: LenType> PartialOrd<CString<M, LenT2>>
328    for CString<N, LenT1>
329{
330    #[inline]
331    fn partial_cmp(&self, rhs: &CString<M, LenT2>) -> Option<Ordering> {
332        self.as_c_str().partial_cmp(rhs.as_c_str())
333    }
334}
335
336impl<const N: usize, LenT: LenType> Ord for CString<N, LenT> {
337    #[inline]
338    fn cmp(&self, rhs: &Self) -> Ordering {
339        self.as_c_str().cmp(rhs.as_c_str())
340    }
341}
342
343impl<const N: usize, LenT: LenType> fmt::Debug for CString<N, LenT> {
344    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345        self.as_c_str().fmt(f)
346    }
347}
348
349/// An error to extend [`CString`] with bytes.
350#[derive(Debug)]
351pub enum ExtendError {
352    /// The capacity of the [`CString`] is too small.
353    Capacity(CapacityError),
354    /// An invalid interior nul byte found in a given byte slice.
355    InteriorNul {
356        /// A position of a nul byte.
357        position: usize,
358    },
359}
360
361impl Error for ExtendError {}
362
363impl fmt::Display for ExtendError {
364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365        match self {
366            Self::Capacity(error) => write!(f, "{error}"),
367            Self::InteriorNul { position } => write!(f, "interior nul byte at {position}"),
368        }
369    }
370}
371
372impl From<CapacityError> for ExtendError {
373    fn from(error: CapacityError) -> Self {
374        Self::Capacity(error)
375    }
376}
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381
382    #[test]
383    fn empty() {
384        let empty = CString::<1>::new();
385
386        assert_eq!(empty.as_c_str(), c"");
387        assert_eq!(empty.as_bytes(), &[]);
388        assert_eq!(empty.to_str(), Ok(""));
389    }
390
391    #[test]
392    fn create_with_capacity_error() {
393        assert!(CString::<1>::from_bytes_with_nul(b"a\0").is_err());
394    }
395
396    #[test]
397    fn extend_no_byte() {
398        let mut c_string = CString::<1>::new();
399
400        c_string.extend_from_bytes(b"").unwrap();
401    }
402
403    #[test]
404    fn extend_from_bytes() {
405        let mut c_string = CString::<11>::new();
406        assert_eq!(c_string.to_str(), Ok(""));
407
408        c_string.extend_from_bytes(b"hello").unwrap();
409
410        assert_eq!(c_string.to_str(), Ok("hello"));
411
412        // Call must fail since `w\0rld` contains an interior nul byte.
413        assert!(matches!(
414            c_string.extend_from_bytes(b"w\0rld"),
415            Err(ExtendError::InteriorNul { position: 1 })
416        ));
417
418        // However, the call above _must not_ have invalidated the state of our CString
419        assert_eq!(c_string.to_str(), Ok("hello"));
420
421        // Call must fail since we can't store "hello world\0" in 11 bytes
422        assert!(matches!(
423            c_string.extend_from_bytes(b" world"),
424            Err(ExtendError::Capacity(CapacityError))
425        ));
426
427        // Yet again, the call above must not have invalidated the state of our CString
428        // (as it would e.g. if we pushed the bytes but then failed to push the nul terminator)
429        assert_eq!(c_string.to_str(), Ok("hello"));
430
431        c_string.extend_from_bytes(b" Bill").unwrap();
432
433        assert_eq!(c_string.to_str(), Ok("hello Bill"));
434    }
435
436    #[test]
437    fn calculate_capacity_with_additional_bytes() {
438        const INITIAL_BYTES: &[u8] = b"abc";
439
440        let mut c_string = CString::<5>::new();
441
442        c_string.extend_from_bytes(INITIAL_BYTES).unwrap();
443
444        assert_eq!(c_string.to_bytes_with_nul().len(), 4);
445        assert_eq!(c_string.capacity_with_bytes(b""), None);
446        assert_eq!(c_string.capacity_with_bytes(b"\0"), None);
447        assert_eq!(
448            c_string.capacity_with_bytes(b"d"),
449            Some(INITIAL_BYTES.len() + 2)
450        );
451        assert_eq!(
452            c_string.capacity_with_bytes(b"d\0"),
453            Some(INITIAL_BYTES.len() + 2)
454        );
455        assert_eq!(
456            c_string.capacity_with_bytes(b"defg"),
457            Some(INITIAL_BYTES.len() + 5)
458        );
459        assert_eq!(
460            c_string.capacity_with_bytes(b"defg\0"),
461            Some(INITIAL_BYTES.len() + 5)
462        );
463    }
464    #[test]
465    fn default() {
466        assert_eq!(CString::<1>::default().as_c_str(), c"");
467    }
468
469    #[test]
470    fn deref() {
471        assert_eq!(CString::<1>::new().deref(), c"");
472        assert_eq!(CString::<2>::new().deref(), c"");
473        assert_eq!(CString::<3>::new().deref(), c"");
474
475        let mut string = CString::<2>::new();
476        string.extend_from_bytes(&[65]).unwrap();
477
478        assert_eq!(string.deref(), c"A");
479
480        let mut string = CString::<3>::new();
481        string.extend_from_bytes(&[65, 66]).unwrap();
482
483        assert_eq!(string.deref(), c"AB");
484
485        let mut string = CString::<4>::new();
486        string.extend_from_bytes(&[65, 66, 67]).unwrap();
487
488        assert_eq!(string.deref(), c"ABC");
489    }
490
491    #[test]
492    fn as_ref() {
493        let mut string = CString::<4>::new();
494        string.extend_from_bytes(b"foo").unwrap();
495        assert_eq!(string.as_ref(), c"foo");
496    }
497
498    #[test]
499    fn borrow() {
500        let mut string = CString::<4>::new();
501        string.extend_from_bytes(b"foo").unwrap();
502        assert_eq!(Borrow::<CStr>::borrow(&string), c"foo");
503    }
504
505    #[test]
506    #[cfg(feature = "zeroize")]
507    fn test_cstring_zeroize() {
508        use zeroize::Zeroize;
509
510        let mut c_string = CString::<32>::from_bytes_with_nul(b"sensitive_password\0").unwrap();
511
512        assert_eq!(c_string.to_str(), Ok("sensitive_password"));
513        assert!(!c_string.to_bytes().is_empty());
514        let original_length = c_string.to_bytes().len();
515        assert_eq!(original_length, 18);
516
517        let new_string = CString::<32>::from_bytes_with_nul(b"short\0").unwrap();
518        c_string = new_string;
519
520        assert_eq!(c_string.to_str(), Ok("short"));
521        assert_eq!(c_string.to_bytes().len(), 5);
522
523        // zeroized using Vec's implementation
524        c_string.zeroize();
525
526        assert_eq!(c_string.to_bytes().len(), 0);
527        assert_eq!(c_string.to_bytes_with_nul(), &[0]);
528
529        c_string.extend_from_bytes(b"new_data").unwrap();
530        assert_eq!(c_string.to_str(), Ok("new_data"));
531        assert_eq!(c_string.to_bytes().len(), 8);
532    }
533
534    mod equality {
535        use super::*;
536
537        #[test]
538        fn c_string() {
539            // Empty strings
540            assert!(CString::<1>::new() == CString::<1>::new());
541            assert!(CString::<1>::new() == CString::<2>::new());
542            assert!(CString::<1>::from_bytes_with_nul(b"\0").unwrap() == CString::<3>::new());
543
544            // Single character
545            assert!(
546                CString::<2>::from_bytes_with_nul(b"a\0").unwrap()
547                    == CString::<2>::from_bytes_with_nul(b"a\0").unwrap()
548            );
549            assert!(
550                CString::<2>::from_bytes_with_nul(b"a\0").unwrap()
551                    == CString::<3>::from_bytes_with_nul(b"a\0").unwrap()
552            );
553            assert!(
554                CString::<2>::from_bytes_with_nul(b"a\0").unwrap()
555                    != CString::<2>::from_bytes_with_nul(b"b\0").unwrap()
556            );
557
558            // Multiple characters
559            assert!(
560                CString::<4>::from_bytes_with_nul(b"abc\0").unwrap()
561                    == CString::<4>::from_bytes_with_nul(b"abc\0").unwrap()
562            );
563            assert!(
564                CString::<3>::from_bytes_with_nul(b"ab\0").unwrap()
565                    != CString::<4>::from_bytes_with_nul(b"abc\0").unwrap()
566            );
567        }
568    }
569
570    mod ordering {
571        use super::*;
572
573        #[test]
574        fn c_string() {
575            assert_eq!(
576                CString::<1>::new().partial_cmp(&CString::<1>::new()),
577                Some(Ordering::Equal)
578            );
579            assert_eq!(
580                CString::<2>::from_bytes_with_nul(b"a\0")
581                    .unwrap()
582                    .partial_cmp(&CString::<2>::from_bytes_with_nul(b"b\0").unwrap()),
583                Some(Ordering::Less)
584            );
585            assert_eq!(
586                CString::<2>::from_bytes_with_nul(b"b\0")
587                    .unwrap()
588                    .partial_cmp(&CString::<2>::from_bytes_with_nul(b"a\0").unwrap()),
589                Some(Ordering::Greater)
590            );
591        }
592
593        #[test]
594        fn c_str() {
595            assert_eq!(c"".partial_cmp(&CString::<1>::new()), Some(Ordering::Equal));
596            assert_eq!(
597                c"a".partial_cmp(&CString::<2>::from_bytes_with_nul(b"b\0").unwrap()),
598                Some(Ordering::Less)
599            );
600            assert_eq!(
601                c"b".partial_cmp(&CString::<2>::from_bytes_with_nul(b"a\0").unwrap()),
602                Some(Ordering::Greater)
603            );
604        }
605    }
606}