use std::io::{BufWriter, Write};
use std::net::Ipv4Addr;
use crate::error::{AppError, AppResult};
const PCAP_MAGIC: u32 = 0xa1b2c3d4; const PCAP_VERSION_MAJOR: u16 = 2;
const PCAP_VERSION_MINOR: u16 = 4;
const LINK_TYPE_ETHERNET: u32 = 1;
const CLIENT_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01]; const SERVER_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x02];
const UPSTREAM_IP: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 1);
const DOWNSTREAM_IP: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 2);
const MODBUS_TCP_PORT: u16 = 502;
const IP_TTL: u8 = 64;
const IP_PROTO_TCP: u8 = 6;
const ETHERTYPE_IPV4: u16 = 0x0800;
pub struct PcapWriter {
writer: BufWriter<std::fs::File>,
seq_upstream: u32,
seq_downstream: u32,
src_port_upstream: u16,
src_port_downstream: u16,
}
impl PcapWriter {
pub fn create(path: &str) -> AppResult<Self> {
let file = std::fs::File::create(path)
.map_err(|e| AppError::Io(e))?;
let mut writer = BufWriter::new(file);
write_global_header(&mut writer)?;
Ok(Self {
writer,
seq_upstream: 0,
seq_downstream: 0,
src_port_upstream: 60000,
src_port_downstream: 60001,
})
}
pub fn write_packet(
&mut self,
ts: chrono::DateTime<chrono::Local>,
upstream_rx: bool,
payload: &[u8],
) -> AppResult<()> {
let (src_ip, dst_ip, src_port, dst_port, seq) = if upstream_rx {
let s = self.seq_upstream;
self.seq_upstream = self.seq_upstream.wrapping_add(payload.len() as u32);
(UPSTREAM_IP, DOWNSTREAM_IP, self.src_port_upstream, MODBUS_TCP_PORT, s)
} else {
let s = self.seq_downstream;
self.seq_downstream = self.seq_downstream.wrapping_add(payload.len() as u32);
(DOWNSTREAM_IP, UPSTREAM_IP, self.src_port_downstream, MODBUS_TCP_PORT, s)
};
let tcp_hdr = build_tcp_header(src_port, dst_port, seq, payload);
let ip_hdr = build_ipv4_header(src_ip, dst_ip, &tcp_hdr, payload);
let eth_frame = build_ethernet_frame(&ip_hdr, &tcp_hdr, payload, upstream_rx);
let ts_sec = ts.timestamp() as u32;
let ts_usec = ts.timestamp_subsec_micros();
let total_len = eth_frame.len() as u32;
write_u32_le(&mut self.writer, ts_sec)?;
write_u32_le(&mut self.writer, ts_usec)?;
write_u32_le(&mut self.writer, total_len)?; write_u32_le(&mut self.writer, total_len)?;
self.writer.write_all(ð_frame)
.map_err(AppError::Io)?;
Ok(())
}
pub fn flush(&mut self) -> AppResult<()> {
self.writer.flush().map_err(AppError::Io)
}
}
fn write_global_header(w: &mut impl Write) -> AppResult<()> {
write_u32_le(w, PCAP_MAGIC)?;
write_u16_le(w, PCAP_VERSION_MAJOR)?;
write_u16_le(w, PCAP_VERSION_MINOR)?;
write_i32_le(w, 0)?; write_u32_le(w, 0)?; write_u32_le(w, 65535)?; write_u32_le(w, LINK_TYPE_ETHERNET)?;
Ok(())
}
fn build_ethernet_frame(
ip_hdr: &[u8],
tcp_hdr: &[u8],
payload: &[u8],
upstream_rx: bool,
) -> Vec<u8> {
let mut frame = Vec::with_capacity(14 + ip_hdr.len() + tcp_hdr.len() + payload.len());
if upstream_rx {
frame.extend_from_slice(&SERVER_MAC); frame.extend_from_slice(&CLIENT_MAC); } else {
frame.extend_from_slice(&CLIENT_MAC);
frame.extend_from_slice(&SERVER_MAC);
}
frame.push((ETHERTYPE_IPV4 >> 8) as u8);
frame.push((ETHERTYPE_IPV4 & 0xFF) as u8);
frame.extend_from_slice(ip_hdr);
frame.extend_from_slice(tcp_hdr);
frame.extend_from_slice(payload);
frame
}
fn build_ipv4_header(src: Ipv4Addr, dst: Ipv4Addr, tcp_hdr: &[u8], payload: &[u8]) -> Vec<u8> {
let total_len = (20 + tcp_hdr.len() + payload.len()) as u16;
let mut hdr = vec![
0x45, 0x00, (total_len >> 8) as u8, (total_len & 0xFF) as u8, 0x00, 0x01, 0x40, 0x00, IP_TTL, IP_PROTO_TCP, 0x00, 0x00, ];
hdr.extend_from_slice(&src.octets());
hdr.extend_from_slice(&dst.octets());
let cksum = ipv4_checksum(&hdr);
hdr[10] = (cksum >> 8) as u8;
hdr[11] = (cksum & 0xFF) as u8;
hdr
}
fn build_tcp_header(src_port: u16, dst_port: u16, seq: u32, _payload: &[u8]) -> Vec<u8> {
vec![
(src_port >> 8) as u8, (src_port & 0xFF) as u8, (dst_port >> 8) as u8, (dst_port & 0xFF) as u8, (seq >> 24) as u8, (seq >> 16) as u8, (seq >> 8) as u8, (seq & 0xFF) as u8,
0x00, 0x00, 0x00, 0x00, 0x50, 0x18, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, ]
}
fn ipv4_checksum(header: &[u8]) -> u16 {
let mut sum: u32 = 0;
let mut i = 0;
while i + 1 < header.len() {
let word = ((header[i] as u32) << 8) | (header[i + 1] as u32);
sum = sum.wrapping_add(word);
i += 2;
}
if i < header.len() {
sum = sum.wrapping_add((header[i] as u32) << 8);
}
while sum >> 16 != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}
#[derive(Debug)]
pub struct PcapRecord {
pub ts_sec: u32,
pub ts_usec: u32,
pub data: Vec<u8>,
}
pub fn read_pcap_records(path: &str) -> AppResult<Vec<PcapRecord>> {
use std::io::{BufReader, Read};
let file = std::fs::File::open(path).map_err(AppError::Io)?;
let mut r = BufReader::new(file);
let magic = read_u32(&mut r)?;
let byte_swap = match magic {
0xa1b2c3d4 => false, 0xd4c3b2a1 => true, _ => return Err(AppError::Config(format!("not a pcap file (magic 0x{magic:08x})"))),
};
let mut skip = [0u8; 20];
r.read_exact(&mut skip).map_err(AppError::Io)?;
let mut records = Vec::new();
loop {
let ts_sec = match read_u32_opt(&mut r)? {
Some(v) => v,
None => break,
};
let ts_usec = read_u32(&mut r)?;
let incl_len = read_u32(&mut r)?;
let _orig_len = read_u32(&mut r)?;
let (ts_sec, ts_usec, incl_len) = if byte_swap {
(ts_sec.swap_bytes(), ts_usec.swap_bytes(), incl_len.swap_bytes())
} else {
(ts_sec, ts_usec, incl_len)
};
let mut data = vec![0u8; incl_len as usize];
r.read_exact(&mut data).map_err(AppError::Io)?;
records.push(PcapRecord { ts_sec, ts_usec, data });
}
Ok(records)
}
fn write_u16_le(w: &mut impl Write, v: u16) -> AppResult<()> {
w.write_all(&v.to_le_bytes()).map_err(AppError::Io)
}
fn write_u32_le(w: &mut impl Write, v: u32) -> AppResult<()> {
w.write_all(&v.to_le_bytes()).map_err(AppError::Io)
}
fn write_i32_le(w: &mut impl Write, v: i32) -> AppResult<()> {
w.write_all(&v.to_le_bytes()).map_err(AppError::Io)
}
fn read_u32(r: &mut impl std::io::Read) -> AppResult<u32> {
let mut buf = [0u8; 4];
r.read_exact(&mut buf).map_err(AppError::Io)?;
Ok(u32::from_le_bytes(buf))
}
fn read_u32_opt(r: &mut impl std::io::Read) -> AppResult<Option<u32>> {
let mut buf = [0u8; 4];
match r.read_exact(&mut buf) {
Ok(()) => Ok(Some(u32::from_le_bytes(buf))),
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => Ok(None),
Err(e) => Err(AppError::Io(e)),
}
}