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