Skip to main content

crafter/wire/ip/
metadata.rs

1//! Inspectable metadata for IP fragmentation transforms.
2
3/// IP version associated with fragment or defragmentation metadata.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum IpFragmentFamily {
6    /// IPv4 fragmentation metadata.
7    Ipv4,
8    /// IPv6 fragmentation metadata.
9    Ipv6,
10}
11
12impl IpFragmentFamily {
13    /// Numeric IP version.
14    pub const fn version(self) -> u8 {
15        match self {
16            Self::Ipv4 => 4,
17            Self::Ipv6 => 6,
18        }
19    }
20
21    /// Stable lowercase family label.
22    pub const fn label(self) -> &'static str {
23        match self {
24            Self::Ipv4 => "ipv4",
25            Self::Ipv6 => "ipv6",
26        }
27    }
28}
29
30/// Byte range from the fragmentable payload, using an exclusive end offset.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub struct IpFragmentRange {
33    start: u32,
34    end: u32,
35}
36
37impl IpFragmentRange {
38    /// Create a byte range with an exclusive end offset.
39    pub const fn new(start: u32, end: u32) -> Self {
40        Self { start, end }
41    }
42
43    /// Start offset in bytes.
44    pub const fn start(self) -> u32 {
45        self.start
46    }
47
48    /// Exclusive end offset in bytes.
49    pub const fn end(self) -> u32 {
50        self.end
51    }
52
53    /// Length in bytes, saturating to zero for malformed ranges.
54    pub const fn len(self) -> u32 {
55        self.end.saturating_sub(self.start)
56    }
57
58    /// Whether this range has no bytes.
59    pub const fn is_empty(self) -> bool {
60        self.len() == 0
61    }
62}
63
64/// Why an IP fragment metadata record was attached.
65#[derive(Debug, Clone, PartialEq, Eq, Hash)]
66pub enum IpFragmentReason {
67    /// The input packet exceeded the configured MTU and was split.
68    Fragmented,
69    /// The input already fit the configured MTU.
70    AlreadyFits,
71    /// IPv4 Don't Fragment policy prevented splitting.
72    DontFragment,
73    /// The packet family or header shape is not supported by the transform.
74    Unsupported,
75    /// Caller-defined reason.
76    Other(String),
77}
78
79/// Metadata attached to one emitted IP fragment record.
80#[derive(Debug, Clone, PartialEq, Eq, Hash)]
81pub struct IpFragmentMetadata {
82    family: IpFragmentFamily,
83    mtu: usize,
84    identification: u32,
85    fragment_offset: u16,
86    more_fragments: bool,
87    fragment_count: usize,
88    emitted_index: usize,
89    byte_range: IpFragmentRange,
90    original_len: Option<u32>,
91    reason: Option<IpFragmentReason>,
92}
93
94impl IpFragmentMetadata {
95    /// Create metadata for one emitted fragment.
96    #[allow(clippy::too_many_arguments)]
97    pub const fn new(
98        family: IpFragmentFamily,
99        mtu: usize,
100        identification: u32,
101        fragment_offset: u16,
102        more_fragments: bool,
103        fragment_count: usize,
104        emitted_index: usize,
105        byte_range: IpFragmentRange,
106    ) -> Self {
107        Self {
108            family,
109            mtu,
110            identification,
111            fragment_offset,
112            more_fragments,
113            fragment_count,
114            emitted_index,
115            byte_range,
116            original_len: None,
117            reason: None,
118        }
119    }
120
121    /// IP version family.
122    pub const fn family(&self) -> IpFragmentFamily {
123        self.family
124    }
125
126    /// Configured MTU in bytes.
127    pub const fn mtu(&self) -> usize {
128        self.mtu
129    }
130
131    /// IPv4 or IPv6 fragment identification value.
132    pub const fn identification(&self) -> u32 {
133        self.identification
134    }
135
136    /// Fragment offset in the protocol header's 8-octet units.
137    pub const fn fragment_offset(&self) -> u16 {
138        self.fragment_offset
139    }
140
141    /// Fragment offset converted to bytes.
142    pub const fn fragment_offset_bytes(&self) -> u32 {
143        (self.fragment_offset as u32) * 8
144    }
145
146    /// Whether the More Fragments flag is set.
147    pub const fn more_fragments(&self) -> bool {
148        self.more_fragments
149    }
150
151    /// Total number of fragments emitted for the source packet.
152    pub const fn fragment_count(&self) -> usize {
153        self.fragment_count
154    }
155
156    /// Zero-based index of this emitted fragment.
157    pub const fn emitted_index(&self) -> usize {
158        self.emitted_index
159    }
160
161    /// Compatibility alias for the zero-based emitted fragment index.
162    pub const fn fragment_index(&self) -> usize {
163        self.emitted_index()
164    }
165
166    /// Byte range from the source packet's fragmentable payload.
167    pub const fn byte_range(&self) -> IpFragmentRange {
168        self.byte_range
169    }
170
171    /// Original packet length in bytes when known.
172    pub const fn original_len(&self) -> Option<u32> {
173        self.original_len
174    }
175
176    /// Reason this metadata was attached.
177    pub const fn reason(&self) -> Option<&IpFragmentReason> {
178        self.reason.as_ref()
179    }
180
181    /// Set the original packet length.
182    pub const fn with_original_len(mut self, original_len: u32) -> Self {
183        self.original_len = Some(original_len);
184        self
185    }
186
187    /// Set the fragment metadata reason.
188    pub fn with_reason(mut self, reason: IpFragmentReason) -> Self {
189        self.reason = Some(reason);
190        self
191    }
192}
193
194/// Overlap status observed while defragmenting one datagram.
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
196pub enum IpDefragOverlapStatus {
197    /// No overlapping byte ranges were observed.
198    None,
199    /// Overlapping byte ranges were observed and did not conflict.
200    NonConflicting,
201    /// At least one overlapping byte range carried conflicting bytes.
202    Conflicting,
203}
204
205impl IpDefragOverlapStatus {
206    /// Whether any overlap was observed.
207    pub const fn has_overlap(self) -> bool {
208        !matches!(self, Self::None)
209    }
210
211    /// Whether any observed overlap had conflicting bytes.
212    pub const fn has_conflict(self) -> bool {
213        matches!(self, Self::Conflicting)
214    }
215}
216
217/// Why defragmentation state was evicted before a complete packet was emitted.
218#[derive(Debug, Clone, PartialEq, Eq, Hash)]
219pub enum IpDefragEvictionReason {
220    /// The datagram exceeded the configured age bound.
221    Timeout,
222    /// The transform exceeded its datagram-count bound.
223    DatagramLimit,
224    /// The transform exceeded its byte-count bound.
225    ByteLimit,
226    /// Conflicting overlaps made the datagram ambiguous.
227    Conflict,
228    /// Caller-defined reason.
229    Other(String),
230}
231
232/// Metadata attached to an IP defragmentation result or eviction.
233#[derive(Debug, Clone, PartialEq, Eq, Hash)]
234pub struct IpDefragMetadata {
235    family: IpFragmentFamily,
236    identification: u32,
237    datagram_key: Option<String>,
238    fragment_count: usize,
239    duplicate_count: usize,
240    overlap_status: IpDefragOverlapStatus,
241    byte_ranges: Vec<IpFragmentRange>,
242    total_len: Option<u32>,
243    eviction_reason: Option<IpDefragEvictionReason>,
244}
245
246impl IpDefragMetadata {
247    /// Create metadata for one defragmentation result.
248    pub const fn new(family: IpFragmentFamily, identification: u32) -> Self {
249        Self {
250            family,
251            identification,
252            datagram_key: None,
253            fragment_count: 0,
254            duplicate_count: 0,
255            overlap_status: IpDefragOverlapStatus::None,
256            byte_ranges: Vec::new(),
257            total_len: None,
258            eviction_reason: None,
259        }
260    }
261
262    /// IP version family.
263    pub const fn family(&self) -> IpFragmentFamily {
264        self.family
265    }
266
267    /// IPv4 or IPv6 fragment identification value.
268    pub const fn identification(&self) -> u32 {
269        self.identification
270    }
271
272    /// Human-readable datagram key summary when available.
273    pub fn datagram_key(&self) -> Option<&str> {
274        self.datagram_key.as_deref()
275    }
276
277    /// Number of unique fragments accepted for this datagram.
278    pub const fn fragment_count(&self) -> usize {
279        self.fragment_count
280    }
281
282    /// Number of exact duplicate fragments observed.
283    pub const fn duplicate_count(&self) -> usize {
284        self.duplicate_count
285    }
286
287    /// Overlap and conflict status observed for this datagram.
288    pub const fn overlap_status(&self) -> IpDefragOverlapStatus {
289        self.overlap_status
290    }
291
292    /// Whether any conflicting overlap was observed.
293    pub const fn has_conflict(&self) -> bool {
294        self.overlap_status.has_conflict()
295    }
296
297    /// Accepted byte ranges for this datagram.
298    pub fn byte_ranges(&self) -> &[IpFragmentRange] {
299        &self.byte_ranges
300    }
301
302    /// Reassembled packet length in bytes when known.
303    pub const fn total_len(&self) -> Option<u32> {
304        self.total_len
305    }
306
307    /// Eviction reason when state was discarded before completion.
308    pub const fn eviction_reason(&self) -> Option<&IpDefragEvictionReason> {
309        self.eviction_reason.as_ref()
310    }
311
312    /// Whether this metadata records a timeout eviction.
313    pub const fn timed_out(&self) -> bool {
314        matches!(self.eviction_reason, Some(IpDefragEvictionReason::Timeout))
315    }
316
317    /// Set a human-readable datagram key summary.
318    pub fn with_datagram_key(mut self, datagram_key: impl Into<String>) -> Self {
319        self.datagram_key = Some(datagram_key.into());
320        self
321    }
322
323    /// Set the number of unique fragments accepted for this datagram.
324    pub const fn with_fragment_count(mut self, fragment_count: usize) -> Self {
325        self.fragment_count = fragment_count;
326        self
327    }
328
329    /// Set the number of exact duplicate fragments observed.
330    pub const fn with_duplicate_count(mut self, duplicate_count: usize) -> Self {
331        self.duplicate_count = duplicate_count;
332        self
333    }
334
335    /// Set the overlap and conflict status.
336    pub const fn with_overlap_status(mut self, overlap_status: IpDefragOverlapStatus) -> Self {
337        self.overlap_status = overlap_status;
338        self
339    }
340
341    /// Append one accepted byte range.
342    pub fn with_byte_range(mut self, byte_range: IpFragmentRange) -> Self {
343        self.byte_ranges.push(byte_range);
344        self
345    }
346
347    /// Replace accepted byte ranges.
348    pub fn with_byte_ranges(
349        mut self,
350        byte_ranges: impl IntoIterator<Item = IpFragmentRange>,
351    ) -> Self {
352        self.byte_ranges = byte_ranges.into_iter().collect();
353        self
354    }
355
356    /// Set the reassembled packet length.
357    pub const fn with_total_len(mut self, total_len: u32) -> Self {
358        self.total_len = Some(total_len);
359        self
360    }
361
362    /// Set the state eviction reason.
363    pub fn with_eviction_reason(mut self, eviction_reason: IpDefragEvictionReason) -> Self {
364        self.eviction_reason = Some(eviction_reason);
365        self
366    }
367}