rusty_pcap/pcap/sync/
writer.rs

1use std::io::{self, Seek, Write};
2pub mod seekless;
3use crate::pcap::{
4    file_header::PcapFileHeader,
5    packet_header::{PacketHeader, PacketTimestamp},
6};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub struct NewPacketHeader {
10    pub timestamp: PacketTimestamp,
11    /// The original length of the packet data
12    pub orig_len: Option<u32>,
13}
14/// A Sync Pcap Writer
15///
16/// ## Why is Seek Required?
17///
18/// If you write a packet larger than the snap_length then the header has to be rewritten
19///
20/// A seekless version is available in the `seekless` module
21pub struct SyncPcapWriter<W: Write + Seek> {
22    target: W,
23    header: PcapFileHeader,
24    /// If a written packet size exceeds snap_len then this will flip to true
25    requires_header_rewrite: bool,
26}
27
28impl<W: Write + Seek> SyncPcapWriter<W> {
29    pub fn new(mut target: W, header: PcapFileHeader) -> Result<Self, io::Error> {
30        header.write(&mut target)?;
31        Ok(Self {
32            target,
33            header,
34            requires_header_rewrite: false,
35        })
36    }
37
38    pub fn write_header(
39        &mut self,
40        header: NewPacketHeader,
41        content: &[u8],
42    ) -> Result<(), io::Error> {
43        let new_header = PacketHeader {
44            timestamp: header.timestamp,
45            include_len: content.len() as u32,
46            orig_len: header.orig_len.unwrap_or(content.len() as u32),
47        };
48        if new_header.include_len > self.header.snap_length {
49            self.requires_header_rewrite = true;
50            self.header.snap_length = self.header.snap_length.max(new_header.include_len);
51        }
52
53        new_header.write(
54            &mut self.target,
55            self.header.magic_number_and_endianness.endianness,
56            &self.header.version,
57        )?;
58        self.target.write_all(content)?;
59        Ok(())
60    }
61
62    pub fn finish(mut self) -> Result<(), io::Error> {
63        self.target.flush()?;
64        self.update_snap_length()?;
65        Ok(())
66    }
67    /// Consumes the writer and returns the underlying target
68    ///
69    /// Warning: this will not update the header even if packets larger than snap_length were written
70    pub fn into_inner(self) -> W {
71        self.target
72    }
73    /// Seeks back to the start and updates the snap_length in the header if required
74    /// Returns `Ok(true)` if the header was rewritten, `Ok(false)` if no rewrite was necessary
75    ///
76    /// Will seek back to the end of the file after updating
77    pub fn update_snap_length(&mut self) -> Result<bool, io::Error> {
78        if !self.requires_header_rewrite {
79            return Ok(false);
80        }
81
82        self.target.seek(io::SeekFrom::Start(0))?;
83
84        self.header.write(&mut self.target)?;
85        self.requires_header_rewrite = false;
86        self.target.seek(io::SeekFrom::End(0))?;
87        Ok(true)
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use std::{fs::File, time::Duration};
94
95    use chrono::{TimeZone, Utc};
96    use etherparse::PacketBuilder;
97
98    use crate::{
99        byte_order::Endianness,
100        link_type::LinkType,
101        pcap::{
102            file_header::{MagicNumber, MagicNumberAndEndianness, PcapFileHeader},
103            packet_header::PacketTimestamp,
104            sync::{
105                SyncPcapReader,
106                writer::{NewPacketHeader, SyncPcapWriter},
107            },
108        },
109    };
110
111    #[test]
112    fn test_write() -> anyhow::Result<()> {
113        let (actual, expected) = crate::test_helpers::test_files("sync_writer_basic.pcap")?;
114        let mut packets_written = Vec::with_capacity(100);
115
116        {
117            let file = File::create(&actual)?;
118
119            let mut writer = SyncPcapWriter::new(
120                file,
121                PcapFileHeader {
122                    link_type: LinkType::Ethernet,
123                    magic_number_and_endianness: MagicNumberAndEndianness {
124                        endianness: Endianness::LittleEndian,
125                        magic_number: MagicNumber::Microsecond,
126                    },
127                    ..Default::default()
128                },
129            )?;
130            let specific_datetime_utc = Utc.with_ymd_and_hms(2025, 11, 27, 10, 30, 0).unwrap();
131
132            let unix_epoch = Utc.timestamp_opt(0, 0).unwrap();
133            for i in 0..100 {
134                let builder = PacketBuilder::ethernet2([1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12])
135                    .ipv4([192, 168, 1, 1], [192, 168, 1, 2], 20)
136                    .udp(21, 1234);
137
138                let payload = [1, 2, 3, 4, 5, 6, 7, 8];
139
140                let mut result = Vec::<u8>::with_capacity(builder.size(payload.len()));
141                builder.write(&mut result, &payload).unwrap();
142                let time = specific_datetime_utc + Duration::from_secs(i as u64);
143
144                let duration_since_epoch = time.signed_duration_since(unix_epoch);
145                let ts_sec = duration_since_epoch.num_seconds() as u32;
146                let packet_header = NewPacketHeader {
147                    orig_len: None,
148                    timestamp: PacketTimestamp {
149                        seconds: ts_sec,
150                        usec: 0,
151                    },
152                };
153                writer.write_header(packet_header, &result)?;
154                packets_written.push((ts_sec, result))
155            }
156            writer.finish()?;
157        }
158
159        let mut packet_reader = SyncPcapReader::new(File::open(&actual)?)?;
160        let mut written_packets_iter = packets_written.into_iter();
161        while let Some((header, bytes)) = packet_reader.next_packet()? {
162            let (expected_ts, expected_bytes) = written_packets_iter
163                .next()
164                .expect("We are short a packet??");
165
166            assert_eq!(
167                expected_ts, header.timestamp.seconds,
168                "Written ts_sec does not match expected ts_sec"
169            );
170            assert_eq!(expected_bytes, bytes);
171        }
172        assert!(
173            written_packets_iter.next().is_none(),
174            "Not all packets were written"
175        );
176
177        crate::test_helpers::do_files_match(actual, expected)?;
178        Ok(())
179    }
180}