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 pub orig_len: Option<u32>,
13}
14pub struct SyncPcapWriter<W: Write + Seek> {
22 target: W,
23 header: PcapFileHeader,
24 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 pub fn into_inner(self) -> W {
71 self.target
72 }
73 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}