Skip to main content

tailtalk_packets/afp/
util.rs

1use crate::afp::types::AfpError;
2use encoding_rs::MACINTOSH;
3
4/// A utility type for handling Macintosh Pascal strings (1-byte length prefix followed by MacRoman encoded data).
5#[derive(Debug, Clone, PartialEq, Eq, Default)]
6pub struct MacString(String);
7
8impl MacString {
9    pub fn new(s: String) -> Self {
10        Self(s)
11    }
12
13    pub fn as_str(&self) -> &str {
14        &self.0
15    }
16
17    pub fn into_string(self) -> String {
18        self.0
19    }
20
21    /// Encodes the string to MacRoman and writes it as a Pascal string to the provided buffer.
22    /// Returns the number of bytes written (1 byte length + data).
23    pub fn bytes(&self, buf: &mut [u8]) -> Result<usize, AfpError> {
24        let (encoded, _, _) = MACINTOSH.encode(&self.0);
25        let len = encoded.len().min(255);
26
27        if buf.len() < 1 + len {
28            return Err(AfpError::InvalidSize);
29        }
30
31        buf[0] = len as u8;
32        buf[1..1 + len].copy_from_slice(&encoded[..len]);
33
34        Ok(1 + len)
35    }
36
37    /// Returns the length in bytes of the MacRoman encoded Pascal string (1 byte length prefix + data).
38    pub fn byte_len(&self) -> usize {
39        let (encoded, _, _) = MACINTOSH.encode(&self.0);
40        let len = encoded.len().min(255);
41        1 + len
42    }
43}
44
45impl TryFrom<&[u8]> for MacString {
46    type Error = AfpError;
47
48    /// Attempts to convert from a byte array to a MacString based on the indicated length.
49    /// As part of decoding the string will be decoded from MacRoman to UTF-8. A string length of zero
50    /// (i.e buf contains a single byte with a value of 0) is valid and will result in an empty string.
51    fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
52        if buf.is_empty() {
53            return Err(AfpError::InvalidSize);
54        }
55
56        let len = buf[0] as usize;
57        if len == 0 {
58            return Ok(MacString(String::new()));
59        }
60
61        if buf.len() < 1 + len {
62            return Err(AfpError::InvalidSize);
63        }
64
65        let string_data = &buf[1..1 + len];
66        let (decoded, _, _) = MACINTOSH.decode(string_data);
67
68        Ok(MacString(decoded.into_owned()))
69    }
70}
71
72impl AsRef<str> for MacString {
73    fn as_ref(&self) -> &str {
74        &self.0
75    }
76}
77
78impl std::ops::Deref for MacString {
79    type Target = str;
80
81    fn deref(&self) -> &Self::Target {
82        &self.0
83    }
84}
85
86impl From<String> for MacString {
87    fn from(s: String) -> Self {
88        Self(s)
89    }
90}
91
92impl From<&str> for MacString {
93    fn from(s: &str) -> Self {
94        Self(s.to_string())
95    }
96}
97
98impl AsRef<std::ffi::OsStr> for MacString {
99    fn as_ref(&self) -> &std::ffi::OsStr {
100        std::ffi::OsStr::new(&self.0)
101    }
102}
103
104impl std::fmt::Display for MacString {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        write!(f, "{}", self.0)
107    }
108}