Skip to main content

ddp_rs/protocol/
mod.rs

1// Protocol specification: http://www.3waylabs.com/ddp/
2
3//! DDP protocol types and structures.
4//!
5//! This module contains all the types defined by the Distributed Display Protocol specification,
6//! including headers, packet types, pixel configurations, and control messages.
7//!
8//! # Protocol Overview
9//!
10//! The DDP protocol uses a 10 or 14 byte header (depending on whether timecode is included)
11//! followed by pixel data or JSON control messages.
12//!
13//! ## Header Structure (10 bytes)
14//!
15//! - Byte 0: Packet type flags (version, timecode, storage, reply, query, push)
16//! - Byte 1: Sequence number (1-15, wraps around)
17//! - Byte 2: Pixel format configuration
18//! - Byte 3: Protocol ID
19//! - Bytes 4-7: Data offset (32-bit, big-endian)
20//! - Bytes 8-9: Data length (16-bit, big-endian)
21//! - Bytes 10-13: Optional timecode (32-bit, big-endian) if timecode flag is set
22
23pub mod packet_type;
24pub use packet_type::*;
25
26pub mod pixel_config;
27pub use pixel_config::{DataType, PixelConfig, PixelFormat};
28
29pub mod id;
30pub use id::ID;
31
32#[cfg(feature = "std")]
33pub mod message;
34
35pub mod timecode;
36use timecode::TimeCode;
37
38mod frame;
39pub use frame::{FrameBuilder, MAX_DATA_LENGTH};
40
41/// DDP packet header containing metadata and control flags.
42///
43/// The header is 10 bytes (or 14 with timecode) and contains all the information
44/// needed to interpret the packet payload.
45///
46/// # Examples
47///
48/// ```
49/// use ddp_rs::protocol::{Header, PacketType, PixelConfig, ID, timecode::TimeCode};
50///
51/// let header = Header {
52///     packet_type: PacketType::default(),
53///     sequence_number: 1,
54///     pixel_config: PixelConfig::default(),
55///     id: ID::Default,
56///     offset: 0,
57///     length: 9,
58///     time_code: TimeCode(None),
59/// };
60///
61/// // Convert to bytes for transmission
62/// let bytes: [u8; 10] = header.into();
63/// ```
64#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)]
65pub struct Header {
66    /// Packet type flags (version, timecode, storage, reply, query, push)
67    pub packet_type: PacketType,
68
69    /// Sequence number (1-15, wraps around to 1)
70    pub sequence_number: u8,
71
72    /// Pixel format configuration (RGB, RGBW, etc.)
73    pub pixel_config: PixelConfig,
74
75    /// Protocol message ID
76    pub id: ID,
77
78    /// Byte offset into the display buffer
79    pub offset: u32,
80
81    /// Length of data in this packet (in bytes)
82    pub length: u16,
83
84    /// Optional timecode for synchronization (if timecode flag is set)
85    pub time_code: TimeCode,
86}
87
88impl From<Header> for [u8; 10] {
89    fn from(header: Header) -> Self {
90        // Define a byte array with the size of the header
91        let mut buffer: [u8; 10] = [0u8; 10];
92
93        // Write the packet type field to the buffer
94
95        let packet_type_byte: u8 = header.packet_type.into();
96        buffer[0] = packet_type_byte;
97
98        // Write the sequence number field to the buffer
99        buffer[1] = header.sequence_number;
100
101        // Write the pixel config field to the buffer
102        buffer[2] = header.pixel_config.into();
103
104        // Write the id field to the buffer
105        buffer[3] = header.id.into();
106
107        // Write the offset field to the buffer
108        let offset_bytes = header.offset.to_be_bytes();
109        buffer[4..8].copy_from_slice(&offset_bytes);
110
111        // Write the length field to the buffer
112        let length_bytes = header.length.to_be_bytes();
113        buffer[8..10].copy_from_slice(&length_bytes);
114
115        // Return a slice of the buffer representing the entire header
116        buffer
117    }
118}
119impl From<Header> for [u8; 14] {
120    fn from(header: Header) -> Self {
121        // Define a byte array with the size of the header
122        let mut buffer = [0u8; 14];
123
124        // Write the packet type field to the buffer
125
126        let packet_type_byte: u8 = header.packet_type.into();
127        buffer[0] = packet_type_byte;
128
129        // Write the sequence number field to the buffer
130        buffer[1] = header.sequence_number;
131
132        // Write the pixel config field to the buffer
133        buffer[2] = header.pixel_config.into();
134
135        // Write the id field to the buffer
136        buffer[3] = header.id.into();
137
138        // Write the offset field to the buffer
139        let offset_bytes: [u8; 4] = header.offset.to_be_bytes();
140        buffer[4..8].copy_from_slice(&offset_bytes);
141
142        // Write the length field to the buffer
143        let length_bytes: [u8; 2] = header.length.to_be_bytes();
144        buffer[8..10].copy_from_slice(&length_bytes);
145
146        let time_code: [u8; 4] = header.time_code.to_bytes();
147        buffer[10..14].copy_from_slice(&time_code);
148
149        // Return a slice of the buffer representing the entire header
150        buffer
151    }
152}
153
154impl<'a> From<&'a [u8]> for Header {
155    fn from(bytes: &'a [u8]) -> Self {
156        // Extract the packet type field from the buffer
157        let packet_type = PacketType::from(bytes[0]);
158
159        // Extract the sequence number field from the buffer
160        let sequence_number = bytes[1];
161
162        // Extract the pixel config field from the buffer
163        let pixel_config = PixelConfig::from(bytes[2]);
164
165        // Extract the id field from the buffer
166        let id = ID::from(bytes[3]);
167
168        // Extract the offset field from the buffer
169        let offset = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
170
171        // Extract the length field from the buffer
172        let length = u16::from_be_bytes([bytes[8], bytes[9]]);
173
174        if packet_type.timecode && bytes.len() >= 14 {
175            let time_code = TimeCode::from_4_bytes([bytes[10], bytes[11], bytes[12], bytes[13]]);
176
177            Header {
178                packet_type,
179                sequence_number,
180                pixel_config,
181                id,
182                offset,
183                length,
184                time_code,
185            }
186        } else {
187            Header {
188                packet_type,
189                sequence_number,
190                pixel_config,
191                id,
192                offset,
193                length,
194                time_code: TimeCode(None),
195            }
196        }
197    }
198}
199
200impl Header {
201    /// Serializes this header into the start of `buf`, returning the number of bytes written
202    /// (10, or 14 when a timecode is present).
203    ///
204    /// This is the allocation-free, `no_std`-friendly equivalent of the `Into<[u8; 10]>` /
205    /// `Into<[u8; 14]>` conversions — useful when writing a packet directly into a reusable
206    /// transmit buffer.
207    ///
208    /// # Panics
209    ///
210    /// Panics if `buf` is shorter than the required header length.
211    ///
212    /// # Examples
213    ///
214    /// ```
215    /// use ddp_rs::protocol::Header;
216    ///
217    /// let header = Header::default();
218    /// let mut buf = [0u8; 10];
219    /// let n = header.write_into(&mut buf);
220    /// assert_eq!(n, 10);
221    /// ```
222    pub fn write_into(&self, buf: &mut [u8]) -> usize {
223        if self.packet_type.timecode {
224            let bytes: [u8; 14] = (*self).into();
225            buf[..14].copy_from_slice(&bytes);
226            14
227        } else {
228            let bytes: [u8; 10] = (*self).into();
229            buf[..10].copy_from_slice(&bytes);
230            10
231        }
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn test_parsing() {
241        // Normal
242        {
243            let data: [u8; 10] = [65, 6, 10, 1, 0, 0, 0, 0, 0, 3];
244            let header = Header::from(&data[..]);
245
246            assert_eq!(
247                header.packet_type,
248                PacketType {
249                    version: 1,
250                    timecode: false,
251                    storage: false,
252                    reply: false,
253                    query: false,
254                    push: true
255                }
256            );
257            assert_eq!(header.sequence_number, 6);
258            assert_eq!(header.length, 3);
259            assert_eq!(header.offset, 0);
260        }
261
262        // oddity
263        {
264            let data: [u8; 10] = [255, 12, 13, 1, 0, 0, 0x99, 0xd5, 0x01, 0x19];
265            let header = Header::from(&data[..]);
266
267            assert_eq!(
268                header.packet_type,
269                PacketType {
270                    version: 3,
271                    timecode: true,
272                    storage: true,
273                    reply: true,
274                    query: true,
275                    push: true
276                }
277            );
278
279            assert_eq!(header.sequence_number, 12);
280            assert_eq!(
281                header.pixel_config,
282                PixelConfig {
283                    data_type: pixel_config::DataType::RGB,
284                    data_size: PixelFormat::Pixel24Bits,
285                    customer_defined: false
286                }
287            );
288            assert_eq!(header.length, 281);
289            assert_eq!(header.offset, 39381);
290        }
291    }
292
293    // Property-based tests
294    use proptest::prelude::*;
295
296    proptest! {
297        #[test]
298        fn test_header_10_byte_roundtrip(
299            packet_type_byte in any::<u8>(),
300            seq_num in any::<u8>(),
301            pixel_config in any::<u8>(),
302            id in any::<u8>(),
303            offset in any::<u32>(),
304            length in any::<u16>(),
305        ) {
306            // Create a 10-byte header from arbitrary values
307            let mut bytes = vec![packet_type_byte, seq_num, pixel_config, id];
308            bytes.extend_from_slice(&offset.to_be_bytes());
309            bytes.extend_from_slice(&length.to_be_bytes());
310
311            // Parse it
312            let header = Header::from(&bytes[..]);
313
314            // Convert back to bytes
315            let roundtrip_bytes: [u8; 10] = header.into();
316
317            // Verify the roundtrip
318            prop_assert_eq!(header.sequence_number, seq_num);
319            prop_assert_eq!(header.offset, offset);
320            prop_assert_eq!(header.length, length);
321
322            // The roundtrip should produce the same parsed values
323            let roundtrip_header = Header::from(&roundtrip_bytes[..]);
324            prop_assert_eq!(header.sequence_number, roundtrip_header.sequence_number);
325            prop_assert_eq!(header.offset, roundtrip_header.offset);
326            prop_assert_eq!(header.length, roundtrip_header.length);
327        }
328
329        #[test]
330        fn test_header_14_byte_with_timecode_roundtrip(
331            seq_num in any::<u8>(),
332            pixel_config in any::<u8>(),
333            id in any::<u8>(),
334            offset in any::<u32>(),
335            length in any::<u16>(),
336            timecode in any::<u32>(),
337        ) {
338            // Create a header with timecode bit set
339            let packet_type_byte = 0b01010000u8; // timecode bit set
340            let mut bytes = vec![packet_type_byte, seq_num, pixel_config, id];
341            bytes.extend_from_slice(&offset.to_be_bytes());
342            bytes.extend_from_slice(&length.to_be_bytes());
343            bytes.extend_from_slice(&timecode.to_be_bytes());
344
345            // Parse it
346            let header = Header::from(&bytes[..]);
347
348            // Verify timecode was parsed
349            prop_assert_eq!(header.time_code.0, Some(timecode));
350            prop_assert_eq!(header.sequence_number, seq_num);
351            prop_assert_eq!(header.offset, offset);
352            prop_assert_eq!(header.length, length);
353            prop_assert!(header.packet_type.timecode);
354
355            // Convert back to 14-byte format
356            let roundtrip_bytes: [u8; 14] = header.into();
357
358            // Parse again and verify
359            let roundtrip_header = Header::from(&roundtrip_bytes[..]);
360            prop_assert_eq!(header.time_code, roundtrip_header.time_code);
361            prop_assert_eq!(header.sequence_number, roundtrip_header.sequence_number);
362        }
363
364        #[test]
365        fn test_header_parsing_never_panics(
366            bytes in prop::collection::vec(any::<u8>(), 10..20)
367        ) {
368            // Parsing arbitrary bytes should never panic
369            let _ = Header::from(&bytes[..]);
370        }
371
372        #[test]
373        fn test_header_offset_range(
374            offset in 0u32..=0xFFFFFFFF,
375        ) {
376            let header = Header {
377                offset,
378                ..Default::default()
379            };
380
381            let bytes: [u8; 10] = header.into();
382            let parsed = Header::from(&bytes[..]);
383
384            prop_assert_eq!(parsed.offset, offset);
385        }
386
387        #[test]
388        fn test_header_length_range(
389            length in 0u16..=1500,
390        ) {
391            let header = Header {
392                length,
393                ..Default::default()
394            };
395
396            let bytes: [u8; 10] = header.into();
397            let parsed = Header::from(&bytes[..]);
398
399            prop_assert_eq!(parsed.length, length);
400        }
401    }
402}