toe_beans/v4/message/
sname.rs

1use crate::v4::error::Result;
2use std::{
3    cmp::Ordering,
4    ffi::{CStr, CString},
5};
6
7/// A null-terminated string that always maps to the 64 bytes assigned to it in [Message](crate::v4::Message).
8///
9/// You can get the string value back out with `to_string()`:
10/// ```
11/// # use toe_beans::v4::SName;
12/// # use std::ffi::CString;
13/// # let sname = SName::new(c"Example").unwrap();
14/// let name: CString = sname.to_string();
15/// ```
16#[derive(Debug, PartialEq)]
17pub struct SName([u8; 64]);
18
19impl SName {
20    /// Construct a valid sname field.
21    /// `name` must not be more 64 bytes.
22    pub fn new(name: &CStr) -> Result<Self> {
23        let bytes = name.to_bytes_with_nul();
24        match bytes.len().cmp(&64) {
25            Ordering::Less => {
26                let mut padding = [0u8; 64];
27                bytes
28                    .iter()
29                    .enumerate()
30                    .for_each(|(i, byte)| padding[i] = *byte);
31                Ok(Self(padding))
32            }
33            Ordering::Equal => Ok(Self(bytes.try_into().unwrap())),
34            Ordering::Greater => Err("SName does not fit in a 64 byte array"),
35        }
36    }
37
38    /// Converts an array slice of exactly 64 byte length
39    /// into a SName without performing any checks (such as length or null-termination).
40    ///
41    /// Panics if slice is not 64 bytes.
42    ///
43    /// Useful if you are handling the return of `parse_sname`.
44    pub fn from_slice_unchecked(name: &[u8]) -> Self {
45        Self(name.try_into().unwrap())
46    }
47
48    /// Create a SName by passing the inner type directly.
49    pub fn from_array(name: [u8; 64]) -> Self {
50        Self(name)
51    }
52
53    /// A SName that contains all zero bytes.
54    ///
55    /// This is more performant than `SName::new(c"")`
56    pub const EMPTY: Self = Self([0; 64]);
57
58    /// Convert the internal array to a null-terminated
59    /// string representation with padding removed.
60    pub fn to_string(&self) -> CString {
61        CStr::from_bytes_until_nul(&self.0).unwrap().to_owned()
62    }
63
64    /// Get the length of SName.
65    ///
66    /// Note that this returns the length of the inner array
67    /// which is always 64. To get the length of its string
68    /// representation call `sname.to_string().count_bytes()`.
69    pub fn len(&self) -> usize {
70        64
71    }
72}
73
74impl From<&SName> for [u8; 64] {
75    fn from(sname: &SName) -> Self {
76        sname.0
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_new_sname_below_length() {
86        let sname = SName::new(c"12345");
87        assert!(sname.is_ok());
88    }
89
90    #[test]
91    fn test_new_sname_at_length() {
92        // 63 bytes plus the NUL byte
93        let sname = SName::new(c"123451234512345123451234512345123451234512345123451234512345123");
94        assert!(sname.is_ok());
95    }
96
97    #[test]
98    fn test_new_sname_above_length() {
99        let sname = SName::new(c"1234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345");
100        assert!(sname.is_err());
101    }
102
103    #[test]
104    fn test_empty() {
105        let sname = SName::EMPTY;
106        assert_eq!(sname.0, [0; 64]);
107    }
108}