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}