Skip to main content

tailtalk_packets/
pap.rs

1use byteorder::{BigEndian, ByteOrder};
2use thiserror::Error;
3
4#[derive(Error, Debug)]
5pub enum PapError {
6    #[error("invalid size - expected at least {expected} bytes but found {found}")]
7    InvalidSize { expected: usize, found: usize },
8    #[error("unknown function code {code}")]
9    UnknownFunction { code: u8 },
10}
11
12/// PAP function codes
13#[derive(Debug, Copy, Clone, PartialEq, Eq)]
14#[repr(u8)]
15pub enum PapFunction {
16    OpenConn = 1,
17    OpenConnReply = 2,
18    SendData = 3,
19    Data = 4,
20    Tickle = 5,
21    CloseConn = 6,
22    CloseConnReply = 7,
23    SendStatus = 8,
24    Status = 9,
25}
26
27impl TryFrom<u8> for PapFunction {
28    type Error = PapError;
29
30    fn try_from(value: u8) -> Result<Self, Self::Error> {
31        match value {
32            1 => Ok(PapFunction::OpenConn),
33            2 => Ok(PapFunction::OpenConnReply),
34            3 => Ok(PapFunction::SendData),
35            4 => Ok(PapFunction::Data),
36            5 => Ok(PapFunction::Tickle),
37            6 => Ok(PapFunction::CloseConn),
38            7 => Ok(PapFunction::CloseConnReply),
39            8 => Ok(PapFunction::SendStatus),
40            9 => Ok(PapFunction::Status),
41            _ => Err(PapError::UnknownFunction { code: value }),
42        }
43    }
44}
45
46/// PAP packet structure
47///
48/// PAP packets are sent over ATP and have the following format:
49/// - Byte 0: Connection ID
50/// - Byte 1: Function code
51/// - Bytes 2-3: Sequence number (for some functions)
52/// - Remaining bytes: Function-specific data
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct PapPacket {
55    pub connection_id: u8,
56    pub function: PapFunction,
57    pub sequence_num: u16,
58    pub data: Vec<u8>,
59}
60
61impl PapPacket {
62    /// Minimum header length (connection_id + function)
63    pub const MIN_HEADER_LEN: usize = 2;
64
65    /// Parse a PAP packet from bytes
66    pub fn parse(buf: &[u8]) -> Result<Self, PapError> {
67        if buf.len() < Self::MIN_HEADER_LEN {
68            return Err(PapError::InvalidSize {
69                expected: Self::MIN_HEADER_LEN,
70                found: buf.len(),
71            });
72        }
73
74        let connection_id = buf[0];
75        let function = PapFunction::try_from(buf[1])?;
76
77        // Some functions include a sequence number, others don't
78        let (sequence_num, data_start) = match function {
79            PapFunction::SendData | PapFunction::Data => {
80                if buf.len() < 4 {
81                    return Err(PapError::InvalidSize {
82                        expected: 4,
83                        found: buf.len(),
84                    });
85                }
86                (BigEndian::read_u16(&buf[2..4]), 4)
87            }
88            _ => (0, 2),
89        };
90
91        let data = buf[data_start..].to_vec();
92
93        Ok(Self {
94            connection_id,
95            function,
96            sequence_num,
97            data,
98        })
99    }
100
101    /// Encode a PAP packet to bytes
102    pub fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, PapError> {
103        let has_seq_num = matches!(self.function, PapFunction::SendData | PapFunction::Data);
104
105        let header_len = if has_seq_num { 4 } else { 2 };
106        let total_len = header_len + self.data.len();
107
108        if buf.len() < total_len {
109            return Err(PapError::InvalidSize {
110                expected: total_len,
111                found: buf.len(),
112            });
113        }
114
115        buf[0] = self.connection_id;
116        buf[1] = self.function as u8;
117
118        if has_seq_num {
119            BigEndian::write_u16(&mut buf[2..4], self.sequence_num);
120            buf[4..total_len].copy_from_slice(&self.data);
121        } else {
122            buf[2..total_len].copy_from_slice(&self.data);
123        }
124
125        Ok(total_len)
126    }
127
128    /// Get the total length of this packet when encoded
129    pub fn len(&self) -> usize {
130        let header_len = match self.function {
131            PapFunction::SendData | PapFunction::Data => 4,
132            _ => 2,
133        };
134        header_len + self.data.len()
135    }
136
137    /// Check if the packet is empty (no data)
138    pub fn is_empty(&self) -> bool {
139        self.data.is_empty()
140    }
141
142    /// Convert to ATP user bytes and data payload
143    ///
144    /// PAP transmits its header in the ATP user bytes.
145    /// - Byte 0: Connection ID
146    /// - Byte 1: Function code
147    /// - Bytes 2-3: Sequence number (or unused/zero)
148    pub fn to_atp_parts(&self) -> ([u8; 4], &[u8]) {
149        let mut user_bytes = [0u8; 4];
150        user_bytes[0] = self.connection_id;
151        user_bytes[1] = self.function as u8;
152
153        if matches!(self.function, PapFunction::SendData | PapFunction::Data) {
154            BigEndian::write_u16(&mut user_bytes[2..4], self.sequence_num);
155        }
156        // All other PAP functions: bytes 2-3 in user_bytes remain zero
157
158        (user_bytes, &self.data)
159    }
160
161    /// Parse from ATP user bytes and data payload
162    pub fn parse_from_atp(user_bytes: [u8; 4], data: &[u8]) -> Result<Self, PapError> {
163        let connection_id = user_bytes[0];
164        let function = PapFunction::try_from(user_bytes[1])?;
165
166        let sequence_num = match function {
167            PapFunction::SendData | PapFunction::Data => BigEndian::read_u16(&user_bytes[2..4]),
168            _ => 0,
169        };
170
171        Ok(Self {
172            connection_id,
173            function,
174            sequence_num,
175            data: data.to_vec(),
176        })
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_parse_open_conn() {
186        // OpenConn packet: conn_id=0, function=1, socket_num=0x42, flow_quantum=8
187        let data: &[u8] = &[0x00, 0x01, 0x42, 0x00, 0x08];
188
189        let packet = PapPacket::parse(data).expect("failed to parse");
190
191        assert_eq!(packet.connection_id, 0);
192        assert_eq!(packet.function, PapFunction::OpenConn);
193        assert_eq!(packet.sequence_num, 0);
194        assert_eq!(packet.data, vec![0x42, 0x00, 0x08]);
195    }
196
197    #[test]
198    fn test_parse_send_data() {
199        // SendData packet: conn_id=5, function=3, seq=0x0001, data="Hello"
200        let data: &[u8] = &[0x05, 0x03, 0x00, 0x01, b'H', b'e', b'l', b'l', b'o'];
201
202        let packet = PapPacket::parse(data).expect("failed to parse");
203
204        assert_eq!(packet.connection_id, 5);
205        assert_eq!(packet.function, PapFunction::SendData);
206        assert_eq!(packet.sequence_num, 1);
207        assert_eq!(packet.data, b"Hello");
208    }
209
210    #[test]
211    fn test_encode_open_conn_reply() {
212        let packet = PapPacket {
213            connection_id: 7,
214            function: PapFunction::OpenConnReply,
215            sequence_num: 0,                          // Not used for OpenConnReply
216            data: vec![0x42, 0x00, 0x08, 0x00, 0x01], // socket, flow_quantum, result
217        };
218
219        let mut buf = [0u8; 32];
220        let len = packet.to_bytes(&mut buf).expect("failed to encode");
221
222        assert_eq!(len, 7); // 2 byte header + 5 bytes data
223        assert_eq!(&buf[..len], &[0x07, 0x02, 0x42, 0x00, 0x08, 0x00, 0x01]);
224    }
225
226    #[test]
227    fn test_encode_data() {
228        let packet = PapPacket {
229            connection_id: 3,
230            function: PapFunction::Data,
231            sequence_num: 42,
232            data: b"PostScript data".to_vec(),
233        };
234
235        let mut buf = [0u8; 64];
236        let len = packet.to_bytes(&mut buf).expect("failed to encode");
237
238        assert_eq!(len, 4 + 15); // 4 byte header + 15 bytes data
239        assert_eq!(buf[0], 3); // connection_id
240        assert_eq!(buf[1], 4); // function code for Data
241        assert_eq!(BigEndian::read_u16(&buf[2..4]), 42); // sequence_num
242        assert_eq!(&buf[4..len], b"PostScript data");
243    }
244
245    #[test]
246    fn test_round_trip_tickle() {
247        let original = PapPacket {
248            connection_id: 10,
249            function: PapFunction::Tickle,
250            sequence_num: 0,
251            data: vec![],
252        };
253
254        let mut buf = [0u8; 32];
255        let len = original.to_bytes(&mut buf).expect("failed to encode");
256        assert_eq!(len, 2); // Just header, no data
257
258        let parsed = PapPacket::parse(&buf[..len]).expect("failed to parse");
259        assert_eq!(original, parsed);
260    }
261
262    #[test]
263    fn test_round_trip_with_data() {
264        let original = PapPacket {
265            connection_id: 15,
266            function: PapFunction::SendData,
267            sequence_num: 100,
268            data: b"Test print job data".to_vec(),
269        };
270
271        let mut buf = [0u8; 64];
272        let len = original.to_bytes(&mut buf).expect("failed to encode");
273
274        let parsed = PapPacket::parse(&buf[..len]).expect("failed to parse");
275        assert_eq!(original, parsed);
276    }
277
278    #[test]
279    fn test_invalid_function_code() {
280        let data: &[u8] = &[0x01, 0xFF]; // Invalid function code
281
282        let result = PapPacket::parse(data);
283        assert!(result.is_err());
284        match result {
285            Err(PapError::UnknownFunction { code: 0xFF }) => {}
286            _ => panic!("Expected UnknownFunction error"),
287        }
288    }
289
290    #[test]
291    fn test_buffer_too_small() {
292        let packet = PapPacket {
293            connection_id: 1,
294            function: PapFunction::Status,
295            sequence_num: 0,
296            data: vec![1, 2, 3, 4, 5],
297        };
298
299        let mut buf = [0u8; 4]; // Too small
300        let result = packet.to_bytes(&mut buf);
301        assert!(result.is_err());
302    }
303
304    #[test]
305    fn test_atp_helpers() {
306        let original = PapPacket {
307            connection_id: 10,
308            function: PapFunction::SendData,
309            sequence_num: 12345,
310            data: b"Data Payload".to_vec(),
311        };
312
313        let (user_bytes, data) = original.to_atp_parts();
314        assert_eq!(user_bytes[0], 10);
315        assert_eq!(user_bytes[1], 3); // SendData
316        assert_eq!(BigEndian::read_u16(&user_bytes[2..4]), 12345);
317        assert_eq!(data, b"Data Payload");
318
319        let parsed = PapPacket::parse_from_atp(user_bytes, data).expect("failed to parse from atp");
320        assert_eq!(original, parsed);
321    }
322}