toe_beans/v4/message/
file.rs

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