Skip to main content

tailtalk_packets/
adsp.rs

1use byteorder::{BigEndian, ByteOrder};
2use thiserror::Error;
3
4#[derive(Error, Debug)]
5pub enum AdspError {
6    #[error("invalid size - expected at least {expected} bytes but found {found}")]
7    InvalidSize { expected: usize, found: usize },
8    #[error("unknown descriptor code {code}")]
9    UnknownDescriptor { code: u8 },
10}
11
12/// ADSP descriptor (packet type) codes
13#[derive(Debug, Copy, Clone, PartialEq, Eq)]
14#[repr(u8)]
15pub enum AdspDescriptor {
16    /// Control packet (probe, acknowledgment)
17    ControlPacket = 0x80,
18    /// Connection open request
19    OpenConnRequest = 0x81,
20    /// Connection open acknowledgment
21    OpenConnAck = 0x82,
22    /// Combined open request and acknowledgment
23    OpenConnReqAck = 0x83,
24    /// Connection denied
25    OpenConnDeny = 0x84,
26    /// Close connection advice
27    CloseAdvice = 0x85,
28    /// Forward reset
29    ForwardReset = 0x86,
30    /// Retransmit advice
31    RetransmitAdvice = 0x87,
32    /// Acknowledgment
33    Acknowledgment = 0x88,
34}
35
36impl TryFrom<u8> for AdspDescriptor {
37    type Error = AdspError;
38
39    fn try_from(value: u8) -> Result<Self, Self::Error> {
40        match value {
41            0x80 => Ok(AdspDescriptor::ControlPacket),
42            0x81 => Ok(AdspDescriptor::OpenConnRequest),
43            0x82 => Ok(AdspDescriptor::OpenConnAck),
44            0x83 => Ok(AdspDescriptor::OpenConnReqAck),
45            0x84 => Ok(AdspDescriptor::OpenConnDeny),
46            0x85 => Ok(AdspDescriptor::CloseAdvice),
47            0x86 => Ok(AdspDescriptor::ForwardReset),
48            0x87 => Ok(AdspDescriptor::RetransmitAdvice),
49            0x88 => Ok(AdspDescriptor::Acknowledgment),
50            _ => Err(AdspError::UnknownDescriptor { code: value }),
51        }
52    }
53}
54
55/// ADSP packet header structure
56///
57/// ADSP (AppleTalk Data Stream Protocol) provides connection-oriented,
58/// full-duplex byte-stream communication over DDP.
59///
60/// Packet format:
61/// - Byte 0: Descriptor (packet type)
62/// - Bytes 1-2: Connection ID (u16, big-endian)
63/// - Bytes 3-6: First Byte Sequence number (u32, big-endian)
64/// - Bytes 7-10: Next Receive Sequence number (u32, big-endian)
65/// - Bytes 11-12: Receive Window size (u16, big-endian)
66/// - Remaining bytes: Data payload (not owned by this struct)
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct AdspPacket {
69    /// Packet type/descriptor
70    pub descriptor: AdspDescriptor,
71    /// Connection identifier
72    pub connection_id: u16,
73    /// Sequence number of the first data byte in this packet
74    pub first_byte_seq: u32,
75    /// Next expected receive sequence number
76    pub next_recv_seq: u32,
77    /// Receive window size (flow control)
78    pub recv_window: u16,
79    /// Flags (Control, Ack, EOM, Attention)
80    pub flags: u8,
81}
82
83impl AdspPacket {
84    /// ADSP header length in bytes
85    pub const HEADER_LEN: usize = 13;
86
87    pub const FLAG_CONTROL: u8 = 0x80;
88    pub const FLAG_ACK: u8 = 0x40;
89    pub const FLAG_EOM: u8 = 0x20;
90    pub const FLAG_ATTENTION: u8 = 0x10;
91
92    /// Parse an ADSP header from bytes
93    ///
94    /// Returns the parsed header. The caller is responsible for
95    /// handling any data following the header in the buffer.
96    pub fn parse(buf: &[u8]) -> Result<Self, AdspError> {
97        if buf.len() < Self::HEADER_LEN {
98            return Err(AdspError::InvalidSize {
99                expected: Self::HEADER_LEN,
100                found: buf.len(),
101            });
102        }
103
104        let descriptor = AdspDescriptor::try_from(buf[0])?;
105        let connection_id = BigEndian::read_u16(&buf[1..3]);
106        let first_byte_seq = BigEndian::read_u32(&buf[3..7]);
107        let next_recv_seq = BigEndian::read_u32(&buf[7..11]);
108        let recv_window = BigEndian::read_u16(&buf[11..13]);
109        let flags = buf[0] & 0xF0; // Top 4 bits are flags on ControlPacket/Ack/etc but we'll store the whole byte since descriptor is just 0x8x
110
111        Ok(Self {
112            descriptor,
113            connection_id,
114            first_byte_seq,
115            next_recv_seq,
116            recv_window,
117            flags,
118        })
119    }
120
121    /// Encode the ADSP header to bytes
122    ///
123    /// Returns the number of bytes written (always HEADER_LEN).
124    /// The caller is responsible for appending any data payload.
125    pub fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, AdspError> {
126        if buf.len() < Self::HEADER_LEN {
127            return Err(AdspError::InvalidSize {
128                expected: Self::HEADER_LEN,
129                found: buf.len(),
130            });
131        }
132
133        // The descriptor byte effectively contains both the control flags and the descriptor code
134        buf[0] = (self.descriptor as u8) | self.flags;
135        BigEndian::write_u16(&mut buf[1..3], self.connection_id);
136        BigEndian::write_u32(&mut buf[3..7], self.first_byte_seq);
137        BigEndian::write_u32(&mut buf[7..11], self.next_recv_seq);
138        BigEndian::write_u16(&mut buf[11..13], self.recv_window);
139
140        Ok(Self::HEADER_LEN)
141    }
142}
143
144/// An ADSP Attention data packet (out-of-band message)
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct AdspAttentionPacket<'a> {
147    pub header: AdspPacket,
148    pub attention_code: u16,
149    pub data: &'a [u8],
150}
151
152impl<'a> AdspAttentionPacket<'a> {
153    /// Parse an Attention packet from a raw ADSP payload
154    pub fn parse(buf: &'a [u8]) -> Result<Self, AdspError> {
155        let header = AdspPacket::parse(buf)?;
156
157        if header.flags & AdspPacket::FLAG_ATTENTION == 0 {
158            return Err(AdspError::UnknownDescriptor { code: buf[0] }); // Not an attention packet
159        }
160
161        let payload = &buf[AdspPacket::HEADER_LEN..];
162        if payload.len() < 2 {
163            return Err(AdspError::InvalidSize {
164                expected: AdspPacket::HEADER_LEN + 2,
165                found: buf.len(),
166            });
167        }
168
169        let attention_code = BigEndian::read_u16(&payload[0..2]);
170        let data = &payload[2..];
171
172        Ok(Self {
173            header,
174            attention_code,
175            data,
176        })
177    }
178
179    /// Write the Attention code into the buffer following the ADSP header
180    pub fn write_payload_to(&self, buf: &mut [u8]) -> Result<usize, AdspError> {
181        if buf.len() < 2 + self.data.len() {
182            return Err(AdspError::InvalidSize {
183                expected: 2 + self.data.len(),
184                found: buf.len(),
185            });
186        }
187
188        BigEndian::write_u16(&mut buf[0..2], self.attention_code);
189        buf[2..2 + self.data.len()].copy_from_slice(self.data);
190
191        Ok(2 + self.data.len())
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn test_parse_open_conn_request() {
201        // OpenConnRequest: descriptor=0x81, conn_id=0x1234,
202        // first_byte_seq=0, next_recv_seq=0, recv_window=4096
203        let data: &[u8] = &[
204            0x81, 0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
205        ];
206
207        let packet = AdspPacket::parse(data).expect("failed to parse");
208
209        assert_eq!(packet.descriptor, AdspDescriptor::OpenConnRequest);
210        assert_eq!(packet.connection_id, 0x1234);
211        assert_eq!(packet.first_byte_seq, 0);
212        assert_eq!(packet.next_recv_seq, 0);
213        assert_eq!(packet.recv_window, 4096);
214    }
215
216    #[test]
217    fn test_parse_control_packet() {
218        // ControlPacket with sequence numbers and window
219        let data: &[u8] = &[
220            0x80, 0xAB, 0xCD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x20, 0x00,
221        ];
222
223        let packet = AdspPacket::parse(data).expect("failed to parse");
224
225        assert_eq!(packet.descriptor, AdspDescriptor::ControlPacket);
226        assert_eq!(packet.connection_id, 0xABCD);
227        assert_eq!(packet.first_byte_seq, 0x00010000);
228        assert_eq!(packet.next_recv_seq, 0x00020000);
229        assert_eq!(packet.recv_window, 8192);
230    }
231
232    #[test]
233    fn test_parse_acknowledgment() {
234        // Acknowledgment packet
235        let data: &[u8] = &[
236            0x88, 0x00, 0x42, 0x00, 0x00, 0x03, 0xE8, 0x00, 0x00, 0x07, 0xD0, 0x08, 0x00,
237        ];
238
239        let packet = AdspPacket::parse(data).expect("failed to parse");
240
241        assert_eq!(packet.descriptor, AdspDescriptor::Acknowledgment);
242        assert_eq!(packet.connection_id, 0x0042);
243        assert_eq!(packet.first_byte_seq, 1000);
244        assert_eq!(packet.next_recv_seq, 2000);
245        assert_eq!(packet.recv_window, 2048);
246    }
247
248    #[test]
249    fn test_encode_open_conn_ack() {
250        let packet = AdspPacket {
251            descriptor: AdspDescriptor::OpenConnAck,
252            connection_id: 0x5678,
253            first_byte_seq: 0,
254            next_recv_seq: 0,
255            recv_window: 8192,
256            flags: 0,
257        };
258
259        let expected: &[u8] = &[
260            0x82, 0x56, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
261        ];
262
263        let mut buf = [0u8; 13];
264        let len = packet.to_bytes(&mut buf).expect("failed to encode");
265
266        assert_eq!(len, AdspPacket::HEADER_LEN);
267        assert_eq!(&buf, expected);
268    }
269
270    #[test]
271    fn test_encode_close_advice() {
272        let packet = AdspPacket {
273            descriptor: AdspDescriptor::CloseAdvice,
274            connection_id: 0x9999,
275            first_byte_seq: 1234567,
276            next_recv_seq: 7654321,
277            recv_window: 0,
278            flags: 0,
279        };
280
281        let mut buf = [0u8; 13];
282        let len = packet.to_bytes(&mut buf).expect("failed to encode");
283
284        assert_eq!(len, AdspPacket::HEADER_LEN);
285        assert_eq!(buf[0], 0x85); // CloseAdvice
286        assert_eq!(BigEndian::read_u16(&buf[1..3]), 0x9999);
287        assert_eq!(BigEndian::read_u32(&buf[3..7]), 1234567);
288        assert_eq!(BigEndian::read_u32(&buf[7..11]), 7654321);
289        assert_eq!(BigEndian::read_u16(&buf[11..13]), 0);
290    }
291
292    #[test]
293    fn test_round_trip() {
294        let original = AdspPacket {
295            descriptor: AdspDescriptor::ForwardReset,
296            connection_id: 0xBEEF,
297            first_byte_seq: 0xDEADBEEF,
298            next_recv_seq: 0xCAFEBABE,
299            recv_window: 0xFFFF,
300            flags: 0x80, // Descriptor is 0x86, so when parsed the top nibble (0x80) is saved as flags
301        };
302
303        let mut buf = [0u8; 13];
304        let len = original.to_bytes(&mut buf).expect("failed to encode");
305        assert_eq!(len, AdspPacket::HEADER_LEN);
306
307        let parsed = AdspPacket::parse(&buf).expect("failed to parse");
308        assert_eq!(original, parsed);
309    }
310
311    #[test]
312    fn test_invalid_descriptor() {
313        // Invalid descriptor code 0x99
314        let data: &[u8] = &[
315            0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
316        ];
317
318        let result = AdspPacket::parse(data);
319        assert!(result.is_err());
320        match result {
321            Err(AdspError::UnknownDescriptor { code: 0x99 }) => {}
322            _ => panic!("Expected UnknownDescriptor error"),
323        }
324    }
325
326    #[test]
327    fn test_buffer_too_small_parse() {
328        let data: &[u8] = &[0x80, 0x00, 0x00]; // Only 3 bytes
329
330        let result = AdspPacket::parse(data);
331        assert!(result.is_err());
332        match result {
333            Err(AdspError::InvalidSize {
334                expected: 13,
335                found: 3,
336            }) => {}
337            _ => panic!("Expected InvalidSize error"),
338        }
339    }
340
341    #[test]
342    fn test_buffer_too_small_encode() {
343        let packet = AdspPacket {
344            descriptor: AdspDescriptor::ControlPacket,
345            connection_id: 1,
346            first_byte_seq: 0,
347            next_recv_seq: 0,
348            recv_window: 1024,
349            flags: 0,
350        };
351
352        let mut buf = [0u8; 5]; // Too small
353        let result = packet.to_bytes(&mut buf);
354        assert!(result.is_err());
355        match result {
356            Err(AdspError::InvalidSize {
357                expected: 13,
358                found: 5,
359            }) => {}
360            _ => panic!("Expected InvalidSize error"),
361        }
362    }
363
364    #[test]
365    fn test_parse_with_data_payload() {
366        // ADSP header followed by data payload
367        let data: &[u8] = &[
368            0x80, 0x11, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00,
369            // Data payload follows:
370            b'H', b'e', b'l', b'l', b'o',
371        ];
372
373        let packet = AdspPacket::parse(data).expect("failed to parse");
374
375        assert_eq!(packet.descriptor, AdspDescriptor::ControlPacket);
376        assert_eq!(packet.connection_id, 0x1122);
377        assert_eq!(packet.first_byte_seq, 1);
378        assert_eq!(packet.next_recv_seq, 2);
379        assert_eq!(packet.recv_window, 4096);
380
381        // Verify caller can access data after header
382        let payload = &data[AdspPacket::HEADER_LEN..];
383        assert_eq!(payload, b"Hello");
384    }
385}