use std::net::IpAddr;
use crate::extractor::TcpFlags;
pub(crate) struct ParsedFrame<'a> {
pub ip: Option<ParsedIp<'a>>,
pub l4: Option<ParsedL4>,
}
pub(crate) struct ParsedIp<'a> {
pub src: IpAddr,
pub dst: IpAddr,
pub proto: u8,
pub l4_payload: &'a [u8],
pub l4_offset: usize,
pub l3_len: u32,
pub l4_wire_len: usize,
}
pub(crate) enum ParsedL4 {
Tcp(ParsedTcp),
Udp(ParsedUdp),
Other,
}
pub(crate) struct ParsedTcp {
pub src_port: u16,
pub dst_port: u16,
pub flags: TcpFlags,
pub seq: u32,
pub ack: u32,
pub payload_offset: usize,
pub payload_len: usize,
pub wire_payload_len: usize,
pub window: u16,
}
pub(crate) struct ParsedUdp {
pub src_port: u16,
pub dst_port: u16,
pub payload_offset: usize,
pub payload_len: usize,
pub wire_payload_len: usize,
}
pub(crate) fn parse_eth(frame: &[u8]) -> Option<ParsedFrame<'_>> {
let sp = etherparse::LaxSlicedPacket::from_ethernet(frame).ok()?;
parse_from_lax(&sp, frame)
}
fn parse_from_lax<'a>(
sp: ðerparse::LaxSlicedPacket<'a>,
frame: &'a [u8],
) -> Option<ParsedFrame<'a>> {
use etherparse::{IpNumber, LaxNetSlice, LenSource, err::Layer, err::packet::SliceError};
let net = sp.net.as_ref()?;
let (src, dst, proto, l3_start, claimed, payload, v4_short) = match net {
LaxNetSlice::Ipv4(v4) => {
let h = v4.header();
(
IpAddr::V4(h.source_addr()),
IpAddr::V4(h.destination_addr()),
u8::from(h.protocol()),
h.slice().as_ptr(),
usize::from(h.total_len()),
v4.payload(),
usize::from(h.total_len()) < h.slice().len(),
)
}
LaxNetSlice::Ipv6(v6) => {
let h = v6.header();
(
IpAddr::V6(h.source_addr()),
IpAddr::V6(h.destination_addr()),
u8::from(v6.payload().ip_number),
h.slice().as_ptr(),
40 + usize::from(h.payload_length()),
v6.payload(),
false,
)
}
LaxNetSlice::Arp(_) => return None,
};
let ether_type = sp.ether_payload()?.ether_type;
let version_ok = match net {
LaxNetSlice::Ipv4(_) => ether_type == etherparse::EtherType::IPV4,
LaxNetSlice::Ipv6(_) => ether_type == etherparse::EtherType::IPV6,
LaxNetSlice::Arp(_) => false,
};
if !version_ok {
return None;
}
if let Some((err, layer)) = &sp.stop_err {
let cut = matches!(err, SliceError::Len(_))
&& payload.incomplete
&& matches!(
layer,
Layer::TcpHeader | Layer::UdpHeader | Layer::Icmpv4 | Layer::Icmpv6 | Layer::Igmp
);
if !cut {
return None;
}
}
if sp.transport.is_none()
&& !payload.fragmented
&& (payload.ip_number == IpNumber::TCP || payload.ip_number == IpNumber::UDP)
{
return None;
}
let l4_payload_slice: &[u8] = payload.payload;
let l4_region_offset = byte_offset(frame, l4_payload_slice)?;
let l3_offset = (l3_start as usize).checked_sub(frame.as_ptr() as usize)?;
let l3_header = l4_region_offset.checked_sub(l3_offset)?;
let captured_l3 = l3_header + l4_payload_slice.len();
if v4_short && !(claimed == 0 && captured_l3 > usize::from(u16::MAX)) {
return None;
}
let l3_wire = if payload.incomplete || payload.len_source != LenSource::Slice {
claimed.max(captured_l3)
} else {
captured_l3
};
let incomplete = payload.incomplete;
let l4_wire_len = if incomplete {
l3_wire
.saturating_sub(l3_header)
.max(l4_payload_slice.len())
} else {
l4_payload_slice.len()
};
let l3_len = u32::try_from(l3_wire).unwrap_or(u32::MAX);
let l4 = match sp.transport.as_ref() {
Some(etherparse::TransportSlice::Tcp(tcp)) => {
let payload = tcp.payload();
let payload_offset = byte_offset(frame, payload).unwrap_or(l4_region_offset);
let mut flags = TcpFlags::empty();
if tcp.fin() {
flags |= TcpFlags::FIN;
}
if tcp.syn() {
flags |= TcpFlags::SYN;
}
if tcp.rst() {
flags |= TcpFlags::RST;
}
if tcp.psh() {
flags |= TcpFlags::PSH;
}
if tcp.ack() {
flags |= TcpFlags::ACK;
}
if tcp.urg() {
flags |= TcpFlags::URG;
}
if tcp.ece() {
flags |= TcpFlags::ECE;
}
if tcp.cwr() {
flags |= TcpFlags::CWR;
}
Some(ParsedL4::Tcp(ParsedTcp {
src_port: tcp.source_port(),
dst_port: tcp.destination_port(),
flags,
seq: tcp.sequence_number(),
ack: tcp.acknowledgment_number(),
payload_offset,
payload_len: payload.len(),
wire_payload_len: l4_wire_len
.saturating_sub(tcp.header_slice().len())
.max(payload.len()),
window: tcp.window_size(),
}))
}
Some(etherparse::TransportSlice::Udp(udp)) => {
let payload = udp.payload();
let payload_offset = byte_offset(frame, payload).unwrap_or(l4_region_offset);
let udp_len = usize::from(udp.length());
if udp_len != 0 && !(8..=l4_wire_len).contains(&udp_len) {
return None;
}
let wire_payload_len = match (incomplete, udp_len) {
(false, _) => payload.len(),
(true, 0) => l4_wire_len.saturating_sub(8),
(true, n) => n - 8,
};
Some(ParsedL4::Udp(ParsedUdp {
src_port: udp.source_port(),
dst_port: udp.destination_port(),
payload_offset,
payload_len: payload.len(),
wire_payload_len: wire_payload_len.max(payload.len()),
}))
}
Some(_) => Some(ParsedL4::Other),
None => None,
};
Some(ParsedFrame {
ip: Some(ParsedIp {
src,
dst,
proto,
l4_payload: l4_payload_slice,
l4_offset: l4_region_offset,
l3_len,
l4_wire_len,
}),
l4,
})
}
pub(crate) fn byte_offset(outer: &[u8], inner: &[u8]) -> Option<usize> {
let outer_start = outer.as_ptr() as usize;
let inner_start = inner.as_ptr() as usize;
let outer_end = outer_start.checked_add(outer.len())?;
let inner_end = inner_start.checked_add(inner.len())?;
if inner_start < outer_start || inner_end > outer_end {
return None;
}
Some(inner_start - outer_start)
}
#[cfg(any(test, feature = "test-helpers"))]
pub mod test_frames {
#[allow(clippy::too_many_arguments)]
pub fn ipv4_tcp(
src_mac: [u8; 6],
dst_mac: [u8; 6],
src_ip: [u8; 4],
dst_ip: [u8; 4],
src_port: u16,
dst_port: u16,
seq: u32,
ack: u32,
flags: u8,
payload: &[u8],
) -> Vec<u8> {
use etherparse::{Ethernet2Header, IpNumber, Ipv4Header, TcpHeader};
let mut tcp = TcpHeader::new(src_port, dst_port, seq, 8192);
tcp.acknowledgment_number = ack;
tcp.fin = flags & 0x01 != 0;
tcp.syn = flags & 0x02 != 0;
tcp.rst = flags & 0x04 != 0;
tcp.psh = flags & 0x08 != 0;
tcp.ack = flags & 0x10 != 0;
tcp.urg = flags & 0x20 != 0;
let ip = Ipv4Header::new(
(tcp.header_len() + payload.len()) as u16,
64,
IpNumber::TCP,
src_ip,
dst_ip,
)
.unwrap();
let eth = Ethernet2Header {
destination: dst_mac,
source: src_mac,
ether_type: etherparse::EtherType::IPV4,
};
let mut out = Vec::new();
eth.write(&mut out).unwrap();
ip.write(&mut out).unwrap();
tcp.write(&mut out).unwrap();
out.extend_from_slice(payload);
out
}
pub fn ipv4_udp(
src_ip: [u8; 4],
dst_ip: [u8; 4],
src_port: u16,
dst_port: u16,
payload: &[u8],
) -> Vec<u8> {
use etherparse::{Ethernet2Header, IpNumber, Ipv4Header, UdpHeader};
let udp = UdpHeader::without_ipv4_checksum(src_port, dst_port, payload.len()).unwrap();
let ip = Ipv4Header::new(
(udp.header_len_u16() as usize + payload.len()) as u16,
64,
IpNumber::UDP,
src_ip,
dst_ip,
)
.unwrap();
let eth = Ethernet2Header {
destination: [0; 6],
source: [0; 6],
ether_type: etherparse::EtherType::IPV4,
};
let mut out = Vec::new();
eth.write(&mut out).unwrap();
ip.write(&mut out).unwrap();
udp.write(&mut out).unwrap();
out.extend_from_slice(payload);
out
}
pub fn ipv6_tcp(
src_ip: [u8; 16],
dst_ip: [u8; 16],
src_port: u16,
dst_port: u16,
seq: u32,
flags: u8,
payload: &[u8],
) -> Vec<u8> {
use etherparse::{Ethernet2Header, IpNumber, Ipv6Header, TcpHeader};
let mut tcp = TcpHeader::new(src_port, dst_port, seq, 8192);
tcp.syn = flags & 0x02 != 0;
tcp.ack = flags & 0x10 != 0;
let ip = Ipv6Header {
traffic_class: 0,
flow_label: 0u32.try_into().unwrap(),
payload_length: (tcp.header_len() + payload.len()) as u16,
next_header: IpNumber::TCP,
hop_limit: 64,
source: src_ip,
destination: dst_ip,
};
let eth = Ethernet2Header {
destination: [0; 6],
source: [0; 6],
ether_type: etherparse::EtherType::IPV6,
};
let mut out = Vec::new();
eth.write(&mut out).unwrap();
ip.write(&mut out).unwrap();
tcp.write(&mut out).unwrap();
out.extend_from_slice(payload);
out
}
pub fn with_vlan_tags(frame: &[u8], tags: &[(u16, u16)]) -> Vec<u8> {
let mut out = frame[..12].to_vec();
for &(tpid, tci) in tags {
out.extend_from_slice(&tpid.to_be_bytes());
out.extend_from_slice(&tci.to_be_bytes());
}
out.extend_from_slice(&frame[12..]);
out
}
}
#[cfg(test)]
mod tests {
use test_frames::*;
use super::*;
#[test]
fn parse_ipv4_tcp_basic() {
let f = ipv4_tcp(
[0; 6],
[0; 6],
[10, 0, 0, 1],
[10, 0, 0, 2],
1234,
80,
1000,
0,
0x02,
b"",
);
let parsed = parse_eth(&f).expect("parse");
let ip = parsed.ip.unwrap();
match ip.src {
std::net::IpAddr::V4(a) => assert_eq!(a.octets(), [10, 0, 0, 1]),
_ => panic!("expected ipv4"),
}
match parsed.l4.unwrap() {
ParsedL4::Tcp(t) => {
assert_eq!(t.src_port, 1234);
assert_eq!(t.dst_port, 80);
assert!(t.flags.contains(TcpFlags::SYN));
assert_eq!(t.seq, 1000);
assert_eq!(t.payload_len, 0);
}
_ => panic!("expected tcp"),
}
}
#[test]
fn parse_ipv4_tcp_payload_offset_correct() {
let payload = b"hello";
let f = ipv4_tcp(
[0; 6],
[0; 6],
[1, 2, 3, 4],
[5, 6, 7, 8],
10,
20,
0,
0,
0x10,
payload,
);
let parsed = parse_eth(&f).unwrap();
let tcp = match parsed.l4.unwrap() {
ParsedL4::Tcp(t) => t,
_ => panic!(),
};
assert_eq!(tcp.payload_offset, 54);
assert_eq!(tcp.payload_len, payload.len());
assert_eq!(
&f[tcp.payload_offset..tcp.payload_offset + tcp.payload_len],
payload
);
}
#[test]
fn parse_ipv4_udp_basic() {
let f = ipv4_udp([10, 0, 0, 1], [10, 0, 0, 2], 5353, 53, b"hi");
let parsed = parse_eth(&f).unwrap();
match parsed.l4.unwrap() {
ParsedL4::Udp(u) => {
assert_eq!(u.src_port, 5353);
assert_eq!(u.dst_port, 53);
assert_eq!(u.payload_len, 2);
}
_ => panic!(),
}
}
#[test]
fn parse_ipv6_tcp_basic() {
let f = ipv6_tcp(
[0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2],
12345,
443,
500,
0x12, b"",
);
let parsed = parse_eth(&f).unwrap();
let ip = parsed.ip.unwrap();
match ip.src {
std::net::IpAddr::V6(_) => {}
_ => panic!(),
}
let tcp = match parsed.l4.unwrap() {
ParsedL4::Tcp(t) => t,
_ => panic!(),
};
assert!(tcp.flags.contains(TcpFlags::SYN));
assert!(tcp.flags.contains(TcpFlags::ACK));
}
#[test]
fn parse_arp_returns_none_ip() {
let mut f = vec![0u8; 64];
f[0..6].copy_from_slice(&[0xff; 6]); f[6..12].copy_from_slice(&[0; 6]);
f[12..14].copy_from_slice(&0x0806u16.to_be_bytes()); f[14..16].copy_from_slice(&1u16.to_be_bytes());
f[16..18].copy_from_slice(&0x0800u16.to_be_bytes());
f[18] = 6;
f[19] = 4;
f[20..22].copy_from_slice(&1u16.to_be_bytes());
let parsed = parse_eth(&f);
assert!(parsed.is_none() || parsed.unwrap().ip.is_none());
}
#[test]
fn parse_truncated_returns_none() {
let f = [0u8; 5];
assert!(parse_eth(&f).is_none());
}
fn tcp_frame(payload: &[u8]) -> Vec<u8> {
ipv4_tcp(
[0; 6],
[0; 6],
[10, 0, 0, 1],
[10, 0, 0, 2],
40000,
80,
1000,
0,
0x18,
payload,
)
}
#[test]
fn lax_tcp_reports_wire_payload_len() {
let mut f = tcp_frame(&[b'x'; 100]);
f.truncate(64);
let parsed = parse_eth(&f).expect("a cut segment is parsed");
let ip = parsed.ip.unwrap();
assert_eq!(ip.l3_len, 140, "IP total length from the header");
assert_eq!(ip.l4_wire_len, 120);
assert_eq!(ip.l4_payload.len(), 30, "captured L4 bytes");
let ParsedL4::Tcp(t) = parsed.l4.unwrap() else {
panic!("expected tcp")
};
assert_eq!((t.src_port, t.dst_port, t.seq), (40000, 80, 1000));
assert_eq!((t.payload_offset, t.payload_len), (54, 10));
assert_eq!(t.wire_payload_len, 100);
}
#[test]
fn complete_tcp_has_equal_wire_and_captured_lengths() {
let f = tcp_frame(&[b'x'; 100]);
let parsed = parse_eth(&f).unwrap();
assert_eq!(parsed.ip.as_ref().unwrap().l3_len, 140);
let ParsedL4::Tcp(t) = parsed.l4.unwrap() else {
panic!("expected tcp")
};
assert_eq!((t.payload_len, t.wire_payload_len), (100, 100));
}
#[test]
fn lax_udp_reports_wire_payload_len() {
let mut f = ipv4_udp([10, 0, 0, 1], [10, 0, 0, 2], 5353, 53, &[7; 200]);
f.truncate(14 + 20 + 8 + 20);
let parsed = parse_eth(&f).expect("a cut datagram is parsed");
assert_eq!(parsed.ip.as_ref().unwrap().l3_len, 228);
let ParsedL4::Udp(u) = parsed.l4.unwrap() else {
panic!("expected udp")
};
assert_eq!((u.payload_len, u.wire_payload_len), (20, 200));
}
#[test]
fn tcp_header_cut_is_skipped() {
let mut f = tcp_frame(&[b'x'; 100]);
f.truncate(14 + 20 + 10);
assert!(parse_eth(&f).is_none(), "no complete L4 header, no key");
f.truncate(14 + 10);
assert!(parse_eth(&f).is_none());
}
#[test]
fn malformed_igmp_content_still_skipped() {
use etherparse::{Ethernet2Header, IpNumber, Ipv4Header};
let ip = Ipv4Header::new(4, 1, IpNumber::IGMP, [10, 0, 0, 1], [224, 0, 0, 1]).unwrap();
let mut f = Vec::new();
Ethernet2Header {
destination: [0; 6],
source: [0; 6],
ether_type: etherparse::EtherType::IPV4,
}
.write(&mut f)
.unwrap();
ip.write(&mut f).unwrap();
f.extend_from_slice(&[0x11, 0, 0, 0]);
assert!(parse_eth(&f).is_none());
let mut f = tcp_frame(b"");
f[14 + 20 + 12] = 0xf0; assert!(parse_eth(&f).is_none());
}
#[test]
fn big_tcp_zero_total_len_is_tracked() {
let mut f = tcp_frame(&[b'x'; 1000]);
f.resize(14 + 70_000, b'x');
f[16] = 0;
f[17] = 0;
let parsed = parse_eth(&f).expect("BIG TCP frame parsed");
let ip = parsed.ip.unwrap();
assert_eq!(ip.l3_len as usize, 70_000, "captured IP length");
let ParsedL4::Tcp(t) = parsed.l4.unwrap() else {
panic!("expected tcp")
};
assert_eq!(
(t.payload_len, t.wire_payload_len),
(70_000 - 40, 70_000 - 40)
);
}
#[test]
fn ipv4_total_len_below_its_header_is_skipped() {
for total_len in [0u16, 1, 19] {
let mut f = tcp_frame(&[b'x'; 3000]);
f[16..18].copy_from_slice(&total_len.to_be_bytes());
assert!(parse_eth(&f).is_none(), "total length {total_len}");
}
}
#[test]
fn udp_length_must_fit_the_ip_payload() {
for (udp_len, ok) in [
(0u16, true),
(8, true),
(48, true),
(49, false),
(900, false),
(5, false),
] {
let mut f = ipv4_udp([10, 0, 0, 1], [10, 0, 0, 2], 53, 5000, &[0x42; 40]);
f[14 + 20 + 4..14 + 20 + 6].copy_from_slice(&udp_len.to_be_bytes());
assert_eq!(parse_eth(&f).is_some(), ok, "UDP length {udp_len}");
}
for (udp_len, ok) in [(48u16, true), (900, false), (5, false)] {
let mut f = ipv4_udp([10, 0, 0, 1], [10, 0, 0, 2], 53, 5000, &[0x42; 40]);
f[14 + 20 + 4..14 + 20 + 6].copy_from_slice(&udp_len.to_be_bytes());
f.truncate(14 + 20 + 8 + 10);
assert_eq!(parse_eth(&f).is_some(), ok, "cut, UDP length {udp_len}");
}
}
type Summary = (IpAddr, IpAddr, u8, Option<(u16, u16, usize, usize)>);
fn strict_summary(frame: &[u8]) -> Option<Summary> {
let sp = etherparse::SlicedPacket::from_ethernet(frame).ok()?;
let (src, dst, proto) = match sp.net.as_ref()? {
etherparse::NetSlice::Ipv4(v4) => (
IpAddr::V4(v4.header().source_addr()),
IpAddr::V4(v4.header().destination_addr()),
u8::from(v4.header().protocol()),
),
etherparse::NetSlice::Ipv6(v6) => (
IpAddr::V6(v6.header().source_addr()),
IpAddr::V6(v6.header().destination_addr()),
u8::from(v6.payload().ip_number),
),
etherparse::NetSlice::Arp(_) => return None,
};
let l4 = match sp.transport.as_ref() {
Some(etherparse::TransportSlice::Tcp(t)) => Some((
t.source_port(),
t.destination_port(),
byte_offset(frame, t.payload())?,
t.payload().len(),
)),
Some(etherparse::TransportSlice::Udp(u)) => Some((
u.source_port(),
u.destination_port(),
byte_offset(frame, u.payload())?,
u.payload().len(),
)),
_ => None,
};
Some((src, dst, proto, l4))
}
fn lax_summary(frame: &[u8]) -> Option<(Summary, bool)> {
let p = parse_eth(frame)?;
let ip = p.ip?;
let (l4, cut) = match p.l4 {
Some(ParsedL4::Tcp(t)) => (
Some((t.src_port, t.dst_port, t.payload_offset, t.payload_len)),
t.wire_payload_len != t.payload_len,
),
Some(ParsedL4::Udp(u)) => (
Some((u.src_port, u.dst_port, u.payload_offset, u.payload_len)),
u.wire_payload_len != u.payload_len,
),
_ => (None, false),
};
Some(((ip.src, ip.dst, ip.proto, l4), cut))
}
fn incomplete(frame: &[u8]) -> bool {
etherparse::LaxSlicedPacket::from_ethernet(frame)
.ok()
.and_then(|sp| {
sp.net
.and_then(|n| n.ip_payload_ref().map(|p| p.incomplete))
})
.unwrap_or(false)
}
fn base_frames() -> Vec<Vec<u8>> {
use etherparse::{
EtherType, Ethernet2Header, IpNumber, Ipv4Header, Ipv4Options, Ipv6FlowLabel,
Ipv6Header, Ipv6RawExtHeader, PacketBuilder, UdpHeader,
};
let eth = |t: EtherType| Ethernet2Header {
source: [0; 6],
destination: [0; 6],
ether_type: t,
};
let mut opts = {
let mut ip =
Ipv4Header::new(8 + 20 + 4, 64, IpNumber::UDP, [10, 0, 0, 5], [10, 0, 0, 6])
.unwrap();
ip.options = Ipv4Options::try_from(&[1u8, 1, 1, 0][..]).unwrap();
ip.total_len = ip.header_len() as u16 + 8 + 20;
let mut f = Vec::new();
eth(EtherType::IPV4).write(&mut f).unwrap();
ip.write(&mut f).unwrap();
UdpHeader::without_ipv4_checksum(7, 9, 20)
.unwrap()
.write(&mut f)
.unwrap();
f
};
opts.extend_from_slice(&[3; 20]);
let mut hbh = {
let ext = Ipv6RawExtHeader::new_raw(IpNumber::UDP, &[0; 6]).unwrap();
let ip = Ipv6Header {
traffic_class: 0,
flow_label: Ipv6FlowLabel::ZERO,
payload_length: (ext.header_len() + 8 + 16) as u16,
next_header: IpNumber::IPV6_HEADER_HOP_BY_HOP,
hop_limit: 1,
source: [1; 16],
destination: [2; 16],
};
let mut f = Vec::new();
eth(EtherType::IPV6).write(&mut f).unwrap();
ip.write(&mut f).unwrap();
ext.write(&mut f).unwrap();
UdpHeader::without_ipv4_checksum(11, 12, 16)
.unwrap()
.write(&mut f)
.unwrap();
f
};
hbh.extend_from_slice(&[4; 16]);
let icmp = {
let mut f = Vec::new();
PacketBuilder::ethernet2([0; 6], [0; 6])
.ipv4([10, 0, 0, 7], [10, 0, 0, 8], 64)
.icmpv4_echo_request(1, 2)
.write(&mut f, &[5; 24])
.unwrap();
f
};
vec![
tcp_frame(&[b'x'; 30]),
ipv4_udp([10, 0, 0, 1], [10, 0, 0, 2], 53, 5000, &[0x42; 40]),
ipv6_tcp([1; 16], [2; 16], 1234, 443, 7, 0x10, &[b'y'; 25]),
with_vlan_tags(
&ipv4_udp([10, 0, 0, 3], [10, 0, 0, 4], 123, 123, &[1; 48]),
&[(0x8100, 7)],
),
with_vlan_tags(&tcp_frame(&[b'z'; 10]), &[(0x88a8, 1), (0x8100, 2)]),
opts,
hbh,
icmp,
]
}
proptest::proptest! {
#![proptest_config(proptest::prelude::ProptestConfig::with_cases(20_000))]
#[test]
fn lax_parse_matches_strict_on_complete_frames(
base in 0usize..8,
edits in proptest::collection::vec((0usize..80, proptest::prelude::any::<u8>()), 1..4),
cut in proptest::prelude::any::<bool>(),
keep in 30usize..120,
) {
let mut f = base_frames().swap_remove(base);
for (at, v) in edits {
if at < f.len() {
f[at] = v;
}
}
if cut {
f.truncate(keep.min(f.len()));
}
if !incomplete(&f) {
let lax = lax_summary(&f);
proptest::prop_assert_eq!(lax.as_ref().map(|l| l.0), strict_summary(&f));
proptest::prop_assert!(lax.is_none_or(|l| !l.1), "complete frame read as cut");
}
}
}
#[test]
fn ethernet_padding_is_not_payload() {
let mut f = ipv4_udp([10, 0, 0, 1], [10, 0, 0, 2], 1, 2, b"hi");
f.resize(60, 0);
let parsed = parse_eth(&f).unwrap();
assert_eq!(parsed.ip.as_ref().unwrap().l3_len, 30);
let ParsedL4::Udp(u) = parsed.l4.unwrap() else {
panic!("expected udp")
};
assert_eq!((u.payload_len, u.wire_payload_len), (2, 2));
}
#[test]
fn udp_surplus_area_is_not_a_truncation() {
let mut f = ipv4_udp([10, 0, 0, 1], [10, 0, 0, 2], 5353, 53, &[1; 40]);
f[14 + 20 + 4..14 + 20 + 6].copy_from_slice(&38u16.to_be_bytes());
let parsed = parse_eth(&f).unwrap();
let ParsedL4::Udp(u) = parsed.l4.unwrap() else {
panic!("expected udp")
};
assert_eq!((u.payload_len, u.wire_payload_len), (30, 30));
f.truncate(14 + 20 + 8 + 5);
let ParsedL4::Udp(u) = parse_eth(&f).unwrap().l4.unwrap() else {
panic!("expected udp")
};
assert_eq!((u.payload_len, u.wire_payload_len), (5, 30));
}
#[test]
fn lax_ipv6_tcp_reports_wire_payload_len() {
let mut f = ipv6_tcp([1; 16], [2; 16], 1234, 443, 7, 0x10, &[b'y'; 500]);
f.truncate(14 + 40 + 20 + 30);
let parsed = parse_eth(&f).expect("a cut IPv6 segment is parsed");
assert_eq!(parsed.ip.as_ref().unwrap().l3_len, 40 + 20 + 500);
let ParsedL4::Tcp(t) = parsed.l4.unwrap() else {
panic!("expected tcp")
};
assert_eq!((t.payload_len, t.wire_payload_len), (30, 500));
}
#[test]
fn byte_offset_basic() {
let outer = [0u8, 1, 2, 3, 4, 5];
let inner = &outer[2..4];
assert_eq!(byte_offset(&outer, inner), Some(2));
}
#[test]
fn byte_offset_disjoint_returns_none() {
let outer = [0u8; 4];
let other = [0u8; 4];
assert_eq!(byte_offset(&outer, &other[..]), None);
}
}