Skip to main content

mermaid_text/
packet.rs

1//! Data model for Mermaid `packet-beta` diagrams.
2//!
3//! A packet diagram maps bit ranges to named fields, visualising the layout of
4//! a network packet header (or any fixed-width binary structure). Mermaid renders
5//! these as 32-bit-wide rows with bit numbers across the top and field labels
6//! occupying their bit ranges.
7//!
8//! Example source:
9//!
10//! ```text
11//! packet-beta
12//!     title TCP Packet
13//!     0-15: "Source Port"
14//!     16-31: "Destination Port"
15//!     32-63: "Sequence Number"
16//! ```
17//!
18//! Constructed by [`crate::parser::packet::parse`] and consumed by
19//! [`crate::render::packet::render`].
20//!
21//! ## Phase 1 limitations
22//!
23//! - Row width is always 32 bits. Custom widths are not supported.
24//! - Custom colours and `accDescr`/`accTitle` are silently ignored.
25//! - No custom bit-numbering direction or endianness selection.
26
27/// A single field in a packet diagram.
28///
29/// Both `start_bit` and `end_bit` are inclusive bit indices (0-based).
30/// For single-bit fields, `end_bit == start_bit`.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct PacketField {
33    /// The inclusive start bit (0-based).
34    pub start_bit: u32,
35    /// The inclusive end bit (0-based). Equal to `start_bit` for single-bit fields.
36    pub end_bit: u32,
37    /// The display label for this field.
38    pub label: String,
39}
40
41impl PacketField {
42    /// The number of bits this field spans.
43    ///
44    /// Always at least 1 (single-bit fields return 1).
45    pub fn bit_width(&self) -> u32 {
46        self.end_bit - self.start_bit + 1
47    }
48}
49
50/// A parsed `packet-beta` diagram.
51///
52/// Constructed by [`crate::parser::packet::parse`] and consumed by
53/// [`crate::render::packet::render`].
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct Packet {
56    /// An optional title displayed above the diagram.
57    pub title: Option<String>,
58    /// Ordered list of fields in the diagram (declaration order).
59    pub fields: Vec<PacketField>,
60}
61
62impl Packet {
63    /// The total number of bits spanned by this packet.
64    ///
65    /// Returns `highest_end_bit + 1`, or `0` when there are no fields.
66    pub fn total_bits(&self) -> u32 {
67        self.fields.iter().map(|f| f.end_bit + 1).max().unwrap_or(0)
68    }
69}
70
71// ---------------------------------------------------------------------------
72// Tests
73// ---------------------------------------------------------------------------
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn packet_field_bit_width_single() {
81        let f = PacketField {
82            start_bit: 5,
83            end_bit: 5,
84            label: "FLAG".to_string(),
85        };
86        assert_eq!(f.bit_width(), 1);
87    }
88
89    #[test]
90    fn packet_field_bit_width_range() {
91        let f = PacketField {
92            start_bit: 0,
93            end_bit: 15,
94            label: "Source Port".to_string(),
95        };
96        assert_eq!(f.bit_width(), 16);
97    }
98
99    #[test]
100    fn packet_total_bits_empty() {
101        let p = Packet {
102            title: None,
103            fields: vec![],
104        };
105        assert_eq!(p.total_bits(), 0);
106    }
107
108    #[test]
109    fn packet_total_bits_multiple_fields() {
110        let p = Packet {
111            title: Some("TCP".to_string()),
112            fields: vec![
113                PacketField {
114                    start_bit: 0,
115                    end_bit: 15,
116                    label: "Source Port".to_string(),
117                },
118                PacketField {
119                    start_bit: 16,
120                    end_bit: 31,
121                    label: "Dest Port".to_string(),
122                },
123            ],
124        };
125        // highest end_bit = 31, so total = 32
126        assert_eq!(p.total_bits(), 32);
127    }
128
129    #[test]
130    fn packet_equality_and_clone() {
131        let a = Packet {
132            title: Some("IP".to_string()),
133            fields: vec![PacketField {
134                start_bit: 0,
135                end_bit: 3,
136                label: "Version".to_string(),
137            }],
138        };
139        let b = a.clone();
140        assert_eq!(a, b);
141
142        let c = Packet {
143            title: None,
144            fields: vec![],
145        };
146        assert_ne!(a, c);
147    }
148}