Skip to main content

crafter/wire/
transform.rs

1//! Stateful packet transform contracts.
2
3use super::record::{PacketRecord, TransformTrace};
4use super::Result;
5
6/// Stateful packet-record transform for sniffer and transmitter pipelines.
7///
8/// A transform consumes one [`PacketRecord`] and may emit zero, one, or many
9/// packet records through the supplied callback. Implementers can keep state
10/// across calls for decryption, defragmentation, reassembly, or protocol
11/// decoding without changing the packet-shaped stream contract. Future WPA
12/// decryptors belong here: they can observe handshake records, retain key
13/// state, and emit decrypted packet records when payload frames become
14/// decodable.
15pub trait PacketTransform {
16    /// Stable transform name used in diagnostics and transform traces.
17    fn name(&self) -> &'static str;
18
19    /// Transform one input record and emit zero or more output records.
20    fn transform(
21        &mut self,
22        record: PacketRecord,
23        emit: &mut dyn FnMut(PacketRecord) -> Result<()>,
24    ) -> Result<()>;
25
26    /// Run the transform and collect emitted records into a small buffer.
27    fn transform_to_output(&mut self, record: PacketRecord) -> Result<TransformOutput>
28    where
29        Self: Sized,
30    {
31        let mut output = TransformOutput::new();
32        self.transform(record, &mut |record| {
33            output.push(record);
34            Ok(())
35        })?;
36        Ok(output)
37    }
38}
39
40/// Buffered output from one packet transform invocation.
41#[derive(Debug, Clone, Default)]
42pub struct TransformOutput {
43    records: Vec<PacketRecord>,
44}
45
46impl TransformOutput {
47    /// Create an empty transform output buffer.
48    pub fn new() -> Self {
49        Self::default()
50    }
51
52    /// Buffered records in emission order.
53    pub fn records(&self) -> &[PacketRecord] {
54        &self.records
55    }
56
57    /// Number of buffered records.
58    pub fn len(&self) -> usize {
59        self.records.len()
60    }
61
62    /// Whether no records were emitted.
63    pub fn is_empty(&self) -> bool {
64        self.records.is_empty()
65    }
66
67    /// Append one output record.
68    pub fn push(&mut self, record: PacketRecord) -> &mut Self {
69        self.records.push(record);
70        self
71    }
72
73    /// Append one output record through a callback-compatible method.
74    pub fn emit(&mut self, record: PacketRecord) -> Result<()> {
75        self.records.push(record);
76        Ok(())
77    }
78
79    /// Remove all buffered output records.
80    pub fn clear(&mut self) -> &mut Self {
81        self.records.clear();
82        self
83    }
84
85    /// Consume the output buffer and return records in emission order.
86    pub fn into_records(self) -> Vec<PacketRecord> {
87        self.records
88    }
89}
90
91/// Test and fixture transform that emits each input record unchanged.
92#[derive(Debug, Clone, Default)]
93pub struct PassThroughTransform {
94    input_count: usize,
95    emitted_count: usize,
96}
97
98impl PassThroughTransform {
99    /// Create a pass-through transform.
100    pub fn new() -> Self {
101        Self::default()
102    }
103
104    /// Number of input records seen.
105    pub const fn input_count(&self) -> usize {
106        self.input_count
107    }
108
109    /// Number of records successfully emitted.
110    pub const fn emitted_count(&self) -> usize {
111        self.emitted_count
112    }
113}
114
115impl PacketTransform for PassThroughTransform {
116    fn name(&self) -> &'static str {
117        "pass-through"
118    }
119
120    fn transform(
121        &mut self,
122        record: PacketRecord,
123        emit: &mut dyn FnMut(PacketRecord) -> Result<()>,
124    ) -> Result<()> {
125        self.input_count += 1;
126        emit(record)?;
127        self.emitted_count += 1;
128        Ok(())
129    }
130}
131
132/// Test and fixture transform that drops every input record.
133#[derive(Debug, Clone, Default)]
134pub struct DropAllTransform {
135    dropped_count: usize,
136}
137
138impl DropAllTransform {
139    /// Create a drop-all transform.
140    pub fn new() -> Self {
141        Self::default()
142    }
143
144    /// Number of input records dropped.
145    pub const fn dropped_count(&self) -> usize {
146        self.dropped_count
147    }
148}
149
150impl PacketTransform for DropAllTransform {
151    fn name(&self) -> &'static str {
152        "drop-all"
153    }
154
155    fn transform(
156        &mut self,
157        _record: PacketRecord,
158        _emit: &mut dyn FnMut(PacketRecord) -> Result<()>,
159    ) -> Result<()> {
160        self.dropped_count += 1;
161        Ok(())
162    }
163}
164
165/// Test and fixture transform that emits two copies of each input record.
166#[derive(Debug, Clone, Default)]
167pub struct DuplicateTransform {
168    input_count: usize,
169    emitted_count: usize,
170}
171
172impl DuplicateTransform {
173    /// Create a duplicate transform.
174    pub fn new() -> Self {
175        Self::default()
176    }
177
178    /// Number of input records seen.
179    pub const fn input_count(&self) -> usize {
180        self.input_count
181    }
182
183    /// Number of records successfully emitted.
184    pub const fn emitted_count(&self) -> usize {
185        self.emitted_count
186    }
187}
188
189impl PacketTransform for DuplicateTransform {
190    fn name(&self) -> &'static str {
191        "duplicate"
192    }
193
194    fn transform(
195        &mut self,
196        record: PacketRecord,
197        emit: &mut dyn FnMut(PacketRecord) -> Result<()>,
198    ) -> Result<()> {
199        self.input_count += 1;
200        emit(record.clone())?;
201        self.emitted_count += 1;
202        emit(record)?;
203        self.emitted_count += 1;
204        Ok(())
205    }
206}
207
208/// Test and fixture transform that appends a transform trace and emits one record.
209#[derive(Debug, Clone)]
210pub struct TraceAppendTransform {
211    name: &'static str,
212    note: Option<String>,
213    input_count: usize,
214    emitted_count: usize,
215}
216
217impl TraceAppendTransform {
218    /// Create a trace-append transform with a stable trace name.
219    pub const fn new(name: &'static str) -> Self {
220        Self {
221            name,
222            note: None,
223            input_count: 0,
224            emitted_count: 0,
225        }
226    }
227
228    /// Create the default trace-append helper.
229    pub const fn trace_append() -> Self {
230        Self::new("trace-append")
231    }
232
233    /// Set a note copied into appended transform traces.
234    pub fn with_note(mut self, note: impl Into<String>) -> Self {
235        self.note = Some(note.into());
236        self
237    }
238
239    /// Number of input records seen.
240    pub const fn input_count(&self) -> usize {
241        self.input_count
242    }
243
244    /// Number of records successfully emitted.
245    pub const fn emitted_count(&self) -> usize {
246        self.emitted_count
247    }
248}
249
250impl Default for TraceAppendTransform {
251    fn default() -> Self {
252        Self::trace_append()
253    }
254}
255
256impl PacketTransform for TraceAppendTransform {
257    fn name(&self) -> &'static str {
258        self.name
259    }
260
261    fn transform(
262        &mut self,
263        mut record: PacketRecord,
264        emit: &mut dyn FnMut(PacketRecord) -> Result<()>,
265    ) -> Result<()> {
266        self.input_count += 1;
267
268        let mut trace = TransformTrace::new(self.name());
269        if let Some(note) = &self.note {
270            trace = trace.with_note(note.clone());
271        }
272        record.metadata_mut().push_transform_trace(trace);
273
274        emit(record)?;
275        self.emitted_count += 1;
276        Ok(())
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::super::record::{BackendKind, PacketOrigin};
283    use super::super::WireError;
284    use super::*;
285    use crate::Raw;
286
287    fn record(payload: &'static str) -> PacketRecord {
288        PacketRecord::new(Raw::from(payload))
289            .with_origin(PacketOrigin::Generated)
290            .with_backend(BackendKind::Memory)
291            .with_interface("lo")
292    }
293
294    #[test]
295    fn transform_output_buffers_records_in_order() {
296        let mut output = TransformOutput::new();
297        assert!(output.is_empty());
298
299        output.push(record("one"));
300        output.emit(record("two")).unwrap();
301
302        assert_eq!(output.len(), 2);
303        assert_eq!(output.records()[0].packet().summary(), "Raw(len=3)");
304        assert_eq!(output.records()[1].packet().summary(), "Raw(len=3)");
305
306        output.clear();
307        assert!(output.is_empty());
308    }
309
310    #[test]
311    fn pass_through_emits_one_record_unchanged() {
312        let input = record("payload");
313        let mut transform = PassThroughTransform::new();
314
315        let output = transform.transform_to_output(input).unwrap();
316
317        assert_eq!(transform.name(), "pass-through");
318        assert_eq!(transform.input_count(), 1);
319        assert_eq!(transform.emitted_count(), 1);
320        assert_eq!(output.len(), 1);
321        assert_eq!(output.records()[0].packet().summary(), "Raw(len=7)");
322        assert_eq!(
323            output.records()[0].metadata().origin(),
324            PacketOrigin::Generated
325        );
326        assert_eq!(
327            output.records()[0].metadata().backend(),
328            &BackendKind::Memory
329        );
330        assert_eq!(output.records()[0].metadata().interface(), Some("lo"));
331    }
332
333    #[test]
334    fn drop_all_emits_zero_records() {
335        let mut transform = DropAllTransform::new();
336
337        let output = transform.transform_to_output(record("payload")).unwrap();
338
339        assert_eq!(transform.name(), "drop-all");
340        assert_eq!(transform.dropped_count(), 1);
341        assert!(output.is_empty());
342    }
343
344    #[test]
345    fn duplicate_emits_two_records_per_input() {
346        let mut transform = DuplicateTransform::new();
347
348        let output = transform.transform_to_output(record("payload")).unwrap();
349
350        assert_eq!(transform.name(), "duplicate");
351        assert_eq!(transform.input_count(), 1);
352        assert_eq!(transform.emitted_count(), 2);
353        assert_eq!(output.len(), 2);
354        assert_eq!(output.records()[0].packet().summary(), "Raw(len=7)");
355        assert_eq!(output.records()[1].packet().summary(), "Raw(len=7)");
356        assert_eq!(
357            output.records()[0].metadata().origin(),
358            PacketOrigin::Generated
359        );
360        assert_eq!(
361            output.records()[1].metadata().backend(),
362            &BackendKind::Memory
363        );
364    }
365
366    #[test]
367    fn trace_append_adds_transform_history() {
368        let mut transform = TraceAppendTransform::new("decode-ip").with_note("decoded");
369
370        let output = transform.transform_to_output(record("payload")).unwrap();
371
372        assert_eq!(transform.name(), "decode-ip");
373        assert_eq!(transform.input_count(), 1);
374        assert_eq!(transform.emitted_count(), 1);
375        assert_eq!(output.len(), 1);
376        let traces = output.records()[0].metadata().transforms();
377        assert_eq!(traces.len(), 1);
378        assert_eq!(traces[0].name(), "decode-ip");
379        assert_eq!(traces[0].note(), Some("decoded"));
380        assert_eq!(output.records()[0].packet().summary(), "Raw(len=7)");
381    }
382
383    #[test]
384    fn packet_transform_is_object_safe() {
385        let mut transform: Box<dyn PacketTransform> = Box::new(PassThroughTransform::new());
386        let mut output = TransformOutput::new();
387
388        transform
389            .transform(record("payload"), &mut |record| output.emit(record))
390            .unwrap();
391
392        assert_eq!(transform.name(), "pass-through");
393        assert_eq!(output.len(), 1);
394        assert_eq!(output.records()[0].packet().summary(), "Raw(len=7)");
395    }
396
397    #[test]
398    fn transform_propagates_emitter_errors() {
399        let mut transform = DuplicateTransform::new();
400
401        let err = transform
402            .transform(record("payload"), &mut |_record| {
403                Err(WireError::transform("collector", "closed"))
404            })
405            .unwrap_err();
406
407        assert_eq!(err.to_string(), "wire transform 'collector' failed: closed");
408        assert_eq!(transform.input_count(), 1);
409        assert_eq!(transform.emitted_count(), 0);
410    }
411}