ddp_rs/
packet.rs

1//! Packet parsing for receiving data from DDP displays.
2//!
3//! This module provides the [`Packet`] type for parsing incoming DDP packets,
4//! typically used when receiving responses from displays.
5
6use crate::protocol::{message::Message, Header};
7
8/// A parsed DDP packet received from a display.
9///
10/// This struct represents packets sent back by displays, such as status updates,
11/// configuration responses, or acknowledgments.
12///
13/// # Examples
14///
15/// ```
16/// use ddp_rs::packet::Packet;
17///
18/// // Parse a packet from raw bytes
19/// let bytes = vec![
20///     0x41, 0x01, 0x0D, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
21///     0xFF, 0x00, 0x00  // 1 RGB pixel: red
22/// ];
23/// let packet = Packet::from_bytes(&bytes);
24///
25/// assert_eq!(packet.header.sequence_number, 1);
26/// assert_eq!(packet.data, vec![0xFF, 0x00, 0x00]);
27/// ```
28#[derive(Debug, PartialEq, Clone)]
29pub struct Packet {
30    /// The parsed packet header with metadata
31    pub header: Header,
32
33    /// Raw pixel data (if this packet contains pixels)
34    pub data: Vec<u8>,
35
36    /// Parsed JSON message (if this packet contains a message)
37    pub parsed: Option<Message>,
38}
39
40impl Packet {
41    /// Creates a packet from a header and data slice (without parsing).
42    pub fn from_data(h: Header, d: &[u8]) -> Packet {
43        Packet {
44            header: h,
45            data: d.to_vec(),
46            parsed: None,
47        }
48    }
49
50    /// Parses a DDP packet from raw bytes.
51    ///
52    /// This method handles both 10-byte and 14-byte headers (with timecode),
53    /// and attempts to parse JSON messages if the packet is a reply/query.
54    ///
55    /// # Arguments
56    ///
57    /// * `bytes` - Raw packet bytes including header and data
58    ///
59    /// # Returns
60    ///
61    /// A parsed `Packet`. If parsing fails, returns a default packet with empty data.
62    ///
63    /// # Examples
64    ///
65    /// ```
66    /// use ddp_rs::packet::Packet;
67    ///
68    /// let bytes = vec![
69    ///     0x41, 0x01, 0x0D, 0x01,           // Packet type, seq, config, id
70    ///     0x00, 0x00, 0x00, 0x00,           // Offset
71    ///     0x00, 0x06,                        // Length = 6
72    ///     0xFF, 0x00, 0x00,                 // Pixel 1: Red
73    ///     0x00, 0xFF, 0x00,                 // Pixel 2: Green
74    /// ];
75    /// let packet = Packet::from_bytes(&bytes);
76    /// assert_eq!(packet.data.len(), 6);
77    /// ```
78    pub fn from_bytes(bytes: &[u8]) -> Self {
79        // Ensure we have at least 10 bytes for the minimum header
80        if bytes.len() < 10 {
81            return Packet {
82                header: Header::default(),
83                data: Vec::new(),
84                parsed: None,
85            };
86        }
87
88        // First, parse just enough to check if timecode is present
89        let has_timecode = (bytes[0] & 0b00010000) != 0;
90        let header_size = if has_timecode { 14 } else { 10 };
91
92        // Ensure we have enough bytes for the header
93        if bytes.len() < header_size {
94            return Packet {
95                header: Header::default(),
96                data: Vec::new(),
97                parsed: None,
98            };
99        }
100
101        let header_bytes = &bytes[0..header_size];
102        let header = Header::from(header_bytes);
103        let data = &bytes[header_size..];
104
105        let mut parsed: Option<Message> = None;
106
107        if header.packet_type.reply {
108            // Try to parse the data into typed structs in the spec
109            parsed = match match header.id {
110                crate::protocol::ID::Control => match serde_json::from_slice(data) {
111                    Ok(v) => Some(Message::Control(v)),
112                    Err(_) => None,
113                },
114                crate::protocol::ID::Config => match serde_json::from_slice(data) {
115                    Ok(v) => Some(Message::Config(v)),
116                    Err(_) => None,
117                },
118                crate::protocol::ID::Status => match serde_json::from_slice(data) {
119                    Ok(v) => Some(Message::Status(v)),
120                    Err(_) => None,
121                },
122                _ => None,
123            } {
124                // Worked, return the typed struct
125                Some(v) => Some(v),
126
127                // OK, no bueno, lets try just untyped JSON
128                None => match header.id {
129                    crate::protocol::ID::Control
130                    | crate::protocol::ID::Config
131                    | crate::protocol::ID::Status => match serde_json::from_slice(data) {
132                        // JSON Value it is
133                        Ok(v) => Some(Message::Parsed((header.id, v))),
134                        // Ok we're really screwed, lets just return the raw data as a string
135                        Err(_) => match std::str::from_utf8(data) {
136                            Ok(v) => Some(Message::Unparsed((header.id, v.to_string()))),
137                            // I guess it's... just bytes?
138                            Err(_) => None,
139                        },
140                    },
141                    _ => None,
142                },
143            }
144        }
145        Packet {
146            header,
147            data: data.to_vec(),
148            parsed,
149        }
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_json() {
159        {
160            let data = vec![
161                0x44, 0x00, 0x0D, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x01, 0x8E, 0x7b, 0x0a, 0x20, 0x20,
162                0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x3a, 0x0a, 0x20, 0x20,
163                0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67,
164                0x77, 0x22, 0x3a, 0x20, 0x22, 0x61, 0x2e, 0x62, 0x2e, 0x63, 0x2e, 0x64, 0x22, 0x2c,
165                0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x70, 0x22, 0x3a,
166                0x20, 0x22, 0x61, 0x2e, 0x62, 0x2e, 0x63, 0x2e, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20,
167                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6d, 0x22, 0x3a, 0x20, 0x22, 0x61,
168                0x2e, 0x62, 0x2e, 0x63, 0x2e, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
169                0x20, 0x20, 0x20, 0x22, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x22, 0x3a, 0x0a, 0x20, 0x20,
170                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
171                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
172                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6c, 0x22, 0x3a,
173                0x20, 0x33, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
174                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a, 0x20,
175                0x31, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
176                0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x34, 0x2c, 0x0a,
177                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
178                0x20, 0x20, 0x22, 0x74, 0x73, 0x22, 0x3a, 0x20, 0x32, 0x0a, 0x20, 0x20, 0x20, 0x20,
179                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20,
180                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
181                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22,
182                0x6c, 0x22, 0x3a, 0x20, 0x37, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
183                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x6f, 0x72, 0x74,
184                0x22, 0x3a, 0x20, 0x35, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
185                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x73, 0x22, 0x3a, 0x20,
186                0x38, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
187                0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x73, 0x22, 0x3a, 0x20, 0x36, 0x0a, 0x20,
188                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20,
189                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d,
190                0x0a, 0x7d,
191            ];
192            let packet = Packet::from_bytes(&data);
193
194            assert_eq!(packet.header.length, 398);
195
196            match packet.parsed {
197                Some(p) => match p {
198                    Message::Config(c) => {
199                        assert_eq!(c.config.gw.unwrap(), "a.b.c.d");
200                        assert_eq!(c.config.nm.unwrap(), "a.b.c.d");
201                        assert_eq!(c.config.ports.len(), 2);
202                    }
203                    _ => panic!("not the right packet parsed"),
204                },
205                None => panic!("Packet parsing failed"),
206            }
207        }
208    }
209
210    #[test]
211    fn test_untyped() {
212        {
213            let data = vec![
214                0x44, 0x00, 0x0D, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x7B, 0x22, 0x68, 0x65,
215                0x6C, 0x6C, 0x6F, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x6B, 0x22, 0x7D,
216            ];
217            let packet = Packet::from_bytes(&data);
218
219            match packet.parsed {
220                Some(p) => match p {
221                    Message::Parsed((_, p)) => {
222                        assert_eq!(p["hello"], "ok");
223                    }
224                    _ => panic!("not the right packet parsed"),
225                },
226                None => panic!("Packet parsing failed"),
227            }
228        }
229    }
230
231    #[test]
232    fn test_unparsed() {
233        {
234            let data = vec![
235                0x44, 0x00, 0x0D, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x53, 0x4C, 0x49, 0x43,
236                0x4B, 0x44, 0x45, 0x4E, 0x49, 0x53, 0x34, 0x30, 0x30, 0x30,
237            ];
238            let packet = Packet::from_bytes(&data);
239
240            match packet.parsed {
241                Some(p) => match p {
242                    Message::Unparsed((_, p)) => {
243                        assert_eq!(p, "SLICKDENIS4000");
244                    }
245                    _ => panic!("not the right packet parsed"),
246                },
247                None => panic!("Packet parsing failed"),
248            }
249        }
250    }
251
252    // Property-based tests using proptest
253    use proptest::prelude::*;
254
255    proptest! {
256        #[test]
257        fn test_packet_header_roundtrip(
258            version in 0u8..4,
259            seq_num in 0u8..=15,
260            pixel_config in 0u8..=255,
261            id in 0u8..=255,
262            offset in 0u32..1000000,
263            length in 0u16..1500,
264        ) {
265            // Create a header with random but valid values
266            let mut packet_bytes = vec![
267                (version << 6) | 0b00000001, // packet_type with push bit set
268                seq_num,
269                pixel_config,
270                id,
271            ];
272            packet_bytes.extend_from_slice(&offset.to_be_bytes());
273            packet_bytes.extend_from_slice(&length.to_be_bytes());
274
275            // Add some data
276            let data = vec![0u8; length.min(100) as usize];
277            packet_bytes.extend_from_slice(&data);
278
279            // Parse the packet
280            let packet = Packet::from_bytes(&packet_bytes);
281
282            // Verify the header fields were parsed correctly
283            prop_assert_eq!(packet.header.sequence_number, seq_num);
284            prop_assert_eq!(packet.header.offset, offset);
285            prop_assert_eq!(packet.header.length, length);
286        }
287
288        #[test]
289        fn test_packet_with_arbitrary_data(
290            data_len in 0usize..500,
291            seq_num in 1u8..=15,
292        ) {
293            // Generate random pixel data
294            let data: Vec<u8> = (0..data_len).map(|i| (i % 256) as u8).collect();
295
296            // Create a minimal valid header
297            let mut packet_bytes = vec![
298                0x41, // version 1, push bit set
299                seq_num,
300                0x00, // pixel config
301                0x01, // ID
302                0x00, 0x00, 0x00, 0x00, // offset
303            ];
304            let length = data.len() as u16;
305            packet_bytes.extend_from_slice(&length.to_be_bytes());
306            packet_bytes.extend_from_slice(&data);
307
308            // Parse it
309            let packet = Packet::from_bytes(&packet_bytes);
310
311            // Verify
312            prop_assert_eq!(packet.data, data);
313            prop_assert_eq!(packet.header.sequence_number, seq_num);
314        }
315
316        #[test]
317        fn test_packet_parsing_never_panics(
318            bytes in prop::collection::vec(any::<u8>(), 10..1500)
319        ) {
320            // This test ensures that parsing arbitrary bytes never panics
321            // Even with completely random data, we should handle it gracefully
322            let _ = Packet::from_bytes(&bytes);
323        }
324
325        #[test]
326        fn test_packet_with_timecode_roundtrip(
327            timecode in any::<u32>(),
328            seq_num in 1u8..=15,
329            data_len in 0usize..100,
330        ) {
331            // Create header with timecode bit set
332            let mut packet_bytes = vec![
333                0x51, // version 1, push bit set, timecode bit set (0b01010001)
334                seq_num,
335                0x00, // pixel config
336                0x01, // ID
337                0x00, 0x00, 0x00, 0x00, // offset
338            ];
339
340            let data: Vec<u8> = (0..data_len).map(|i| (i % 256) as u8).collect();
341            let length = data.len() as u16;
342            packet_bytes.extend_from_slice(&length.to_be_bytes());
343            packet_bytes.extend_from_slice(&timecode.to_be_bytes());
344            packet_bytes.extend_from_slice(&data);
345
346            let packet = Packet::from_bytes(&packet_bytes);
347
348            prop_assert_eq!(packet.header.time_code.0, Some(timecode));
349            prop_assert_eq!(packet.data, data);
350        }
351
352        #[test]
353        fn test_offset_values_preserved(
354            offset in 0u32..4000000,
355        ) {
356            let mut packet_bytes = vec![
357                0x41, 1, 0x00, 0x01,
358            ];
359            packet_bytes.extend_from_slice(&offset.to_be_bytes());
360            packet_bytes.extend_from_slice(&[0x00, 0x03]); // length = 3
361            packet_bytes.extend_from_slice(&[255, 0, 0]); // 1 pixel
362
363            let packet = Packet::from_bytes(&packet_bytes);
364            prop_assert_eq!(packet.header.offset, offset);
365        }
366    }
367
368    // Integration tests for full packet roundtrips
369    #[test]
370    fn test_full_packet_roundtrip() {
371        use crate::protocol::{Header, PacketType, PixelConfig, ID, timecode::TimeCode};
372
373        // Create a header
374        let header = Header {
375            packet_type: PacketType {
376                version: 1,
377                timecode: false,
378                storage: false,
379                reply: false,
380                query: false,
381                push: true,
382            },
383            sequence_number: 5,
384            pixel_config: PixelConfig::default(),
385            id: ID::default(),
386            offset: 0,
387            length: 9,
388            time_code: TimeCode(None),
389        };
390
391        // Create RGB data
392        let data = vec![255, 0, 0, 0, 255, 0, 0, 0, 255];
393
394        // Convert header to bytes
395        let header_bytes: [u8; 10] = header.into();
396
397        // Create full packet
398        let mut packet_bytes = header_bytes.to_vec();
399        packet_bytes.extend_from_slice(&data);
400
401        // Parse it back
402        let parsed_packet = Packet::from_bytes(&packet_bytes);
403
404        // Verify
405        assert_eq!(parsed_packet.header.sequence_number, 5);
406        assert_eq!(parsed_packet.header.length, 9);
407        assert_eq!(parsed_packet.data, data);
408    }
409
410    #[test]
411    fn test_packet_with_timecode_integration() {
412        use crate::protocol::{Header, PacketType, PixelConfig, ID, timecode::TimeCode};
413
414        // Create a header with timecode
415        let header = Header {
416            packet_type: PacketType {
417                version: 1,
418                timecode: true,
419                storage: false,
420                reply: false,
421                query: false,
422                push: true,
423            },
424            sequence_number: 3,
425            pixel_config: PixelConfig::default(),
426            id: ID::default(),
427            offset: 100,
428            length: 6,
429            time_code: TimeCode(Some(12345)),
430        };
431
432        // Create RGB data
433        let data = vec![128, 128, 128, 64, 64, 64];
434
435        // Convert header to bytes (14 bytes with timecode)
436        let header_bytes: [u8; 14] = header.into();
437
438        // Create full packet
439        let mut packet_bytes = header_bytes.to_vec();
440        packet_bytes.extend_from_slice(&data);
441
442        // Parse it back
443        let parsed_packet = Packet::from_bytes(&packet_bytes);
444
445        // Verify
446        assert_eq!(parsed_packet.header.sequence_number, 3);
447        assert_eq!(parsed_packet.header.length, 6);
448        assert_eq!(parsed_packet.header.offset, 100);
449        assert_eq!(parsed_packet.header.time_code.0, Some(12345));
450        assert_eq!(parsed_packet.data, data);
451    }
452
453    #[test]
454    fn test_packet_with_config_message_integration() {
455        use crate::protocol::{Header, PacketType, PixelConfig, ID, timecode::TimeCode};
456
457        let json = r#"{"config":{"gw":"192.168.1.1","ip":"192.168.1.100"}}"#;
458
459        let header = Header {
460            packet_type: PacketType {
461                version: 1,
462                timecode: false,
463                storage: false,
464                reply: true,
465                query: false,
466                push: false,
467            },
468            sequence_number: 1,
469            pixel_config: PixelConfig::default(),
470            id: ID::Config,
471            offset: 0,
472            length: json.len() as u16,
473            time_code: TimeCode(None),
474        };
475
476        let header_bytes: [u8; 10] = header.into();
477        let mut packet_bytes = header_bytes.to_vec();
478        packet_bytes.extend_from_slice(json.as_bytes());
479
480        let parsed_packet = Packet::from_bytes(&packet_bytes);
481
482        assert_eq!(parsed_packet.header.id, ID::Config);
483        assert!(parsed_packet.parsed.is_some());
484    }
485
486    #[test]
487    fn test_multiple_packets_different_sequences_integration() {
488        use crate::protocol::{Header, PacketType, PixelConfig, ID, timecode::TimeCode};
489
490        // Test that we can parse multiple packets with different sequence numbers
491        let test_cases = vec![
492            (1, vec![255, 0, 0]),
493            (5, vec![0, 255, 0]),
494            (10, vec![0, 0, 255]),
495            (15, vec![128, 128, 128]),
496        ];
497
498        for (seq_num, data) in test_cases {
499            let header = Header {
500                packet_type: PacketType {
501                    version: 1,
502                    timecode: false,
503                    storage: false,
504                    reply: false,
505                    query: false,
506                    push: true,
507                },
508                sequence_number: seq_num,
509                pixel_config: PixelConfig::default(),
510                id: ID::default(),
511                offset: 0,
512                length: data.len() as u16,
513                time_code: TimeCode(None),
514            };
515
516            let header_bytes: [u8; 10] = header.into();
517            let mut packet_bytes = header_bytes.to_vec();
518            packet_bytes.extend_from_slice(&data);
519
520            let parsed = Packet::from_bytes(&packet_bytes);
521            assert_eq!(parsed.header.sequence_number, seq_num);
522            assert_eq!(parsed.data, data);
523        }
524    }
525
526    #[test]
527    fn test_large_pixel_data_integration() {
528        use crate::protocol::{Header, PacketType, PixelConfig, ID, timecode::TimeCode};
529
530        // Test with a large number of pixels
531        let num_pixels = 480; // Max size in connection
532        let mut data = Vec::with_capacity(num_pixels * 3);
533
534        for i in 0..num_pixels {
535            data.push((i % 256) as u8);
536            data.push(((i * 2) % 256) as u8);
537            data.push(((i * 3) % 256) as u8);
538        }
539
540        let header = Header {
541            packet_type: PacketType {
542                version: 1,
543                timecode: false,
544                storage: false,
545                reply: false,
546                query: false,
547                push: true,
548            },
549            sequence_number: 1,
550            pixel_config: PixelConfig::default(),
551            id: ID::default(),
552            offset: 0,
553            length: data.len() as u16,
554            time_code: TimeCode(None),
555        };
556
557        let header_bytes: [u8; 10] = header.into();
558        let mut packet_bytes = header_bytes.to_vec();
559        packet_bytes.extend_from_slice(&data);
560
561        let parsed = Packet::from_bytes(&packet_bytes);
562        assert_eq!(parsed.data.len(), data.len());
563        assert_eq!(parsed.data, data);
564    }
565}