use netflow_parser::variable_versions::field_value::{FieldDataType, FieldValue};
use netflow_parser::{NetflowPacket, NetflowParser};
#[test]
fn test_v7_round_trip() {
let mut packet = vec![0u8; 24 + 52];
packet[0] = 0;
packet[1] = 7;
packet[2] = 0;
packet[3] = 1;
for (i, byte) in packet.iter_mut().enumerate().skip(4) {
*byte = (i % 256) as u8;
}
if let NetflowPacket::V7(v7) = NetflowParser::default()
.parse_bytes(&packet)
.packets
.first()
.unwrap()
{
assert_eq!(v7.to_be_bytes(), packet);
} else {
panic!("Expected V7 packet");
}
}
#[test]
fn test_v9_template_and_data_round_trip() {
let hex_template = "0009000100000e1061db09bd00000001000000010000001801000004\
00080004000c0004000a0004000e0004";
let mut parser = NetflowParser::builder()
.with_cache_size(100)
.build()
.unwrap();
let template_bytes = hex::decode(hex_template).unwrap();
let template_result = parser.parse_bytes(&template_bytes);
assert!(template_result.error.is_none(), "Template parse failed");
let hex_data = "0009000100000e1061db09bd000000020000000101000014\
c0a801010a0000010000000a00000014";
let data_bytes = hex::decode(hex_data).unwrap();
let data_result = parser.parse_bytes(&data_bytes);
assert!(data_result.error.is_none(), "Data parse failed");
if let Some(NetflowPacket::V9(v9)) = data_result.packets.first() {
let serialized = v9.to_be_bytes().expect("V9 serialization failed");
assert_eq!(serialized, data_bytes, "V9 data packet round-trip failed");
} else {
panic!("Expected V9 data packet");
}
}
#[test]
fn test_v9_template_only_round_trip() {
let hex_template = "0009000100000e1061db09bd00000001000000010000001801000004\
00080004000c0004000a0004000e0004";
let mut parser = NetflowParser::builder()
.with_cache_size(100)
.build()
.unwrap();
let template_bytes = hex::decode(hex_template).unwrap();
let result = parser.parse_bytes(&template_bytes);
if let Some(NetflowPacket::V9(v9)) = result.packets.first() {
let serialized = v9.to_be_bytes().expect("V9 template serialization failed");
assert_eq!(
serialized, template_bytes,
"V9 template packet round-trip failed"
);
} else {
panic!("Expected V9 template packet");
}
}
#[test]
fn test_ipfix_template_and_data_round_trip() {
let hex_template = "000a002862a0b1b9000000086c6a7e11\
000200180100000400080004000c0004000a0004000e0004";
let mut parser = NetflowParser::builder()
.with_cache_size(100)
.build()
.unwrap();
let template_bytes = hex::decode(hex_template).unwrap();
let template_result = parser.parse_bytes(&template_bytes);
assert!(
template_result.error.is_none(),
"IPFIX template parse failed"
);
let hex_data = "000a002462a0b1b9000000096c6a7e11\
01000014c0a801010a0000010000000a00000014";
let data_bytes = hex::decode(hex_data).unwrap();
let data_result = parser.parse_bytes(&data_bytes);
if let Some(NetflowPacket::IPFix(ipfix)) = data_result.packets.first() {
let serialized = ipfix.to_be_bytes().expect("IPFIX serialization failed");
assert_eq!(
serialized, data_bytes,
"IPFIX data packet round-trip failed"
);
} else {
panic!("Expected IPFIX data packet");
}
}
#[test]
fn test_ipfix_template_only_round_trip() {
let hex_template = "000a002462a0b1b9000000086c6a7e11\
000200140100000300080004000c000400040001";
let mut parser = NetflowParser::builder()
.with_cache_size(100)
.build()
.unwrap();
let template_bytes = hex::decode(hex_template).unwrap();
let result = parser.parse_bytes(&template_bytes);
if let Some(NetflowPacket::IPFix(ipfix)) = result.packets.first() {
let serialized = ipfix
.to_be_bytes()
.expect("IPFIX template serialization failed");
assert_eq!(
serialized, template_bytes,
"IPFIX template packet round-trip failed"
);
} else {
panic!("Expected IPFIX template packet");
}
}
#[test]
fn test_v9_options_template_and_data_round_trip() {
let mut parser = NetflowParser::builder()
.with_cache_size(100)
.build()
.unwrap();
let mut template_packet = Vec::new();
template_packet.extend_from_slice(&9u16.to_be_bytes()); template_packet.extend_from_slice(&1u16.to_be_bytes()); template_packet.extend_from_slice(&0u32.to_be_bytes()); template_packet.extend_from_slice(&0u32.to_be_bytes()); template_packet.extend_from_slice(&1u32.to_be_bytes()); template_packet.extend_from_slice(&1u32.to_be_bytes()); template_packet.extend_from_slice(&1u16.to_be_bytes()); template_packet.extend_from_slice(&20u16.to_be_bytes()); template_packet.extend_from_slice(&257u16.to_be_bytes()); template_packet.extend_from_slice(&4u16.to_be_bytes()); template_packet.extend_from_slice(&4u16.to_be_bytes()); template_packet.extend_from_slice(&1u16.to_be_bytes()); template_packet.extend_from_slice(&4u16.to_be_bytes()); template_packet.extend_from_slice(&42u16.to_be_bytes()); template_packet.extend_from_slice(&4u16.to_be_bytes()); template_packet.extend_from_slice(&[0u8; 2]);
let result = parser.parse_bytes(&template_packet);
assert!(
result.error.is_none(),
"V9 options template parse failed: {:?}",
result.error
);
if let Some(NetflowPacket::V9(v9)) = result.packets.first() {
let serialized = v9
.to_be_bytes()
.expect("V9 options template serialization failed");
assert_eq!(
serialized, template_packet,
"V9 options template round-trip failed"
);
} else {
panic!("Expected V9 options template packet");
}
let mut data_packet = Vec::new();
data_packet.extend_from_slice(&9u16.to_be_bytes()); data_packet.extend_from_slice(&1u16.to_be_bytes()); data_packet.extend_from_slice(&0u32.to_be_bytes()); data_packet.extend_from_slice(&0u32.to_be_bytes()); data_packet.extend_from_slice(&2u32.to_be_bytes()); data_packet.extend_from_slice(&1u32.to_be_bytes()); data_packet.extend_from_slice(&257u16.to_be_bytes()); data_packet.extend_from_slice(&12u16.to_be_bytes()); data_packet.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]);
data_packet.extend_from_slice(&[0x00, 0x00, 0x00, 0x2A]);
let data_result = parser.parse_bytes(&data_packet);
assert!(
data_result.error.is_none(),
"V9 options data parse failed: {:?}",
data_result.error
);
if let Some(NetflowPacket::V9(v9)) = data_result.packets.first() {
let serialized = v9
.to_be_bytes()
.expect("V9 options data serialization failed");
assert_eq!(serialized, data_packet, "V9 options data round-trip failed");
} else {
panic!("Expected V9 options data packet");
}
}
#[test]
fn test_ipfix_options_template_and_data_round_trip() {
let mut parser = NetflowParser::builder()
.with_cache_size(100)
.build()
.unwrap();
let template_set = {
let mut set = Vec::new();
set.extend_from_slice(&3u16.to_be_bytes()); set.extend_from_slice(&20u16.to_be_bytes()); set.extend_from_slice(&258u16.to_be_bytes()); set.extend_from_slice(&2u16.to_be_bytes()); set.extend_from_slice(&1u16.to_be_bytes()); set.extend_from_slice(&8u16.to_be_bytes()); set.extend_from_slice(&4u16.to_be_bytes()); set.extend_from_slice(&14u16.to_be_bytes()); set.extend_from_slice(&4u16.to_be_bytes()); set.extend_from_slice(&[0u8; 2]); set
};
let template_msg = build_ipfix_message(0x62A0B1B9, 20, 1, &template_set);
let result = parser.parse_bytes(&template_msg);
assert!(
result.error.is_none(),
"IPFIX options template parse failed: {:?}",
result.error
);
if let Some(NetflowPacket::IPFix(ipfix)) = result.packets.first() {
let serialized = ipfix
.to_be_bytes()
.expect("IPFIX options template serialization failed");
assert_eq!(
serialized, template_msg,
"IPFIX options template round-trip failed"
);
} else {
panic!("Expected IPFIX options template packet");
}
let data_set = {
let mut set = Vec::new();
set.extend_from_slice(&258u16.to_be_bytes()); set.extend_from_slice(&12u16.to_be_bytes()); set.extend_from_slice(&[192, 168, 1, 1]); set.extend_from_slice(&42u32.to_be_bytes()); set
};
let data_msg = build_ipfix_message(0x62A0B1B9, 21, 1, &data_set);
let data_result = parser.parse_bytes(&data_msg);
assert!(
data_result.error.is_none(),
"IPFIX options data parse failed: {:?}",
data_result.error
);
if let Some(NetflowPacket::IPFix(ipfix)) = data_result.packets.first() {
let serialized = ipfix
.to_be_bytes()
.expect("IPFIX options data serialization failed");
assert_eq!(serialized, data_msg, "IPFIX options data round-trip failed");
} else {
panic!("Expected IPFIX options data packet");
}
}
fn assert_field_round_trip(raw: &[u8], data_type: FieldDataType) {
let (remaining, field_value) =
FieldValue::from_field_type(raw, data_type, raw.len() as u16)
.unwrap_or_else(|e| panic!("parse failed for {:?}: {:?}", data_type, e));
assert!(
remaining.is_empty(),
"leftover bytes for {:?}: {} remaining",
data_type,
remaining.len()
);
let mut buf = Vec::new();
field_value
.write_be_bytes(&mut buf)
.unwrap_or_else(|e| panic!("write failed for {:?}: {:?}", data_type, e));
assert_eq!(
buf, raw,
"round-trip failed for {:?}: expected {:?}, got {:?}",
data_type, raw, buf
);
}
#[test]
fn test_field_round_trip_forwarding_status() {
assert_field_round_trip(&[0x40], FieldDataType::ForwardingStatus);
assert_field_round_trip(&[0xC3], FieldDataType::ForwardingStatus);
}
#[test]
fn test_field_round_trip_fragment_flags() {
assert_field_round_trip(&[0x05], FieldDataType::FragmentFlags);
assert_field_round_trip(&[0x02], FieldDataType::FragmentFlags);
}
#[test]
fn test_field_round_trip_tcp_control_bits() {
assert_field_round_trip(&[0x00, 0x12], FieldDataType::TcpControlBits);
assert_field_round_trip(&[0x01, 0xFF], FieldDataType::TcpControlBits);
}
#[test]
fn test_field_round_trip_ipv6_extension_headers() {
assert_field_round_trip(
&[0x00, 0x00, 0x00, 0x3F],
FieldDataType::Ipv6ExtensionHeaders,
);
assert_field_round_trip(
&[0xFF, 0xFF, 0xFF, 0xFF],
FieldDataType::Ipv6ExtensionHeaders,
);
}
#[test]
fn test_field_round_trip_ipv4_options() {
assert_field_round_trip(&[0x00, 0x7F, 0xFF, 0xFF], FieldDataType::Ipv4Options);
assert_field_round_trip(&[0xFF, 0xFF, 0xFF, 0xFF], FieldDataType::Ipv4Options);
}
#[test]
fn test_field_round_trip_tcp_options() {
assert_field_round_trip(
&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
FieldDataType::TcpOptions,
);
assert_field_round_trip(
&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
FieldDataType::TcpOptions,
);
}
#[test]
fn test_field_round_trip_is_multicast() {
assert_field_round_trip(&[0x00], FieldDataType::IsMulticast);
assert_field_round_trip(&[0x01], FieldDataType::IsMulticast);
assert_field_round_trip(&[0xFF], FieldDataType::IsMulticast);
}
#[test]
fn test_field_round_trip_mpls_label_exp() {
assert_field_round_trip(&[0x00], FieldDataType::MplsLabelExp);
assert_field_round_trip(&[0x07], FieldDataType::MplsLabelExp);
}
#[test]
fn test_field_round_trip_flow_end_reason() {
assert_field_round_trip(&[0x01], FieldDataType::FlowEndReason); assert_field_round_trip(&[0x05], FieldDataType::FlowEndReason); assert_field_round_trip(&[0xFF], FieldDataType::FlowEndReason); }
#[test]
fn test_field_round_trip_nat_event() {
assert_field_round_trip(&[0x01], FieldDataType::NatEvent); assert_field_round_trip(&[0x12], FieldDataType::NatEvent); assert_field_round_trip(&[0xFF], FieldDataType::NatEvent); }
#[test]
fn test_field_round_trip_firewall_event() {
assert_field_round_trip(&[0x00], FieldDataType::FirewallEvent); assert_field_round_trip(&[0x05], FieldDataType::FirewallEvent); assert_field_round_trip(&[0xAB], FieldDataType::FirewallEvent); }
#[test]
fn test_field_round_trip_mpls_top_label_type() {
assert_field_round_trip(&[0x00], FieldDataType::MplsTopLabelType); assert_field_round_trip(&[0x06], FieldDataType::MplsTopLabelType); assert_field_round_trip(&[0x7F], FieldDataType::MplsTopLabelType); }
#[test]
fn test_field_round_trip_nat_originating_address_realm() {
assert_field_round_trip(&[0x01], FieldDataType::NatOriginatingAddressRealm); assert_field_round_trip(&[0x02], FieldDataType::NatOriginatingAddressRealm); assert_field_round_trip(&[0x00], FieldDataType::NatOriginatingAddressRealm); }
#[test]
fn test_field_round_trip_ip4addr() {
assert_field_round_trip(&[192, 168, 0, 1], FieldDataType::Ip4Addr);
}
#[test]
fn test_field_round_trip_ip6addr() {
assert_field_round_trip(
&[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
FieldDataType::Ip6Addr,
);
}
#[test]
fn test_field_round_trip_mac_addr() {
assert_field_round_trip(
&[0x00, 0x1B, 0x44, 0x11, 0x3A, 0xB7],
FieldDataType::MacAddr,
);
}
#[test]
fn test_field_round_trip_protocol_type() {
assert_field_round_trip(&[6], FieldDataType::ProtocolType); assert_field_round_trip(&[17], FieldDataType::ProtocolType); }
#[test]
fn test_field_round_trip_unsigned_data_number() {
assert_field_round_trip(&[42], FieldDataType::UnsignedDataNumber);
assert_field_round_trip(&[0x30, 0x39], FieldDataType::UnsignedDataNumber);
assert_field_round_trip(&[0x01, 0x02, 0x03, 0x04], FieldDataType::UnsignedDataNumber);
assert_field_round_trip(
&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
FieldDataType::UnsignedDataNumber,
);
}
#[test]
fn test_field_round_trip_signed_data_number() {
assert_field_round_trip(&[0x80], FieldDataType::SignedDataNumber); assert_field_round_trip(&[0xFF, 0x00], FieldDataType::SignedDataNumber);
assert_field_round_trip(&[0xFF, 0x80, 0x00], FieldDataType::SignedDataNumber);
assert_field_round_trip(&[0xFF, 0xFF, 0xFF, 0xFE], FieldDataType::SignedDataNumber);
assert_field_round_trip(
&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE],
FieldDataType::SignedDataNumber,
);
}
#[test]
fn test_field_round_trip_float64() {
let bytes = 123.456f64.to_be_bytes();
assert_field_round_trip(&bytes, FieldDataType::Float64);
}
#[test]
fn test_field_round_trip_duration_seconds() {
assert_field_round_trip(&[0x00, 0x00, 0x30, 0x39], FieldDataType::DurationSeconds);
assert_field_round_trip(
&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x39],
FieldDataType::DurationSeconds,
);
}
#[test]
fn test_field_round_trip_duration_millis() {
assert_field_round_trip(&[0x00, 0x01, 0x51, 0x80], FieldDataType::DurationMillis);
assert_field_round_trip(
&[0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x51, 0x80],
FieldDataType::DurationMillis,
);
}
#[test]
fn test_field_round_trip_string() {
assert_field_round_trip(b"hello", FieldDataType::String);
}
#[test]
fn test_field_value_application_id_round_trip() {
let raw = &[0x0A, 0x01, 0x02, 0x03];
let (remaining, field_value) =
FieldValue::from_field_type(raw, FieldDataType::ApplicationId, 4).unwrap();
assert!(remaining.is_empty());
let mut buf = Vec::new();
field_value.write_be_bytes(&mut buf).unwrap();
assert_eq!(buf, raw, "ApplicationId round-trip failed");
}
#[test]
fn test_field_value_application_id_1_byte() {
let raw = &[0x05];
let (remaining, field_value) =
FieldValue::from_field_type(raw, FieldDataType::ApplicationId, 1).unwrap();
assert!(remaining.is_empty());
let mut buf = Vec::new();
field_value.write_be_bytes(&mut buf).unwrap();
assert_eq!(buf, raw, "ApplicationId 1-byte round-trip failed");
}
#[test]
fn test_field_wrong_length_fallback_round_trips() {
let raw = &[1, 2, 3, 4, 5, 6, 7, 8];
let (remaining, field_value) =
FieldValue::from_field_type(raw, FieldDataType::Ip4Addr, 8).unwrap();
assert!(remaining.is_empty());
assert!(matches!(field_value, FieldValue::Vec(_)));
let mut buf = Vec::new();
field_value.write_be_bytes(&mut buf).unwrap();
assert_eq!(buf, raw, "Vec fallback round-trip failed");
}
fn build_ipfix_message(export_time: u32, seq: u32, obs_domain: u32, sets: &[u8]) -> Vec<u8> {
let total_len = (16 + sets.len()) as u16;
let mut msg = Vec::with_capacity(total_len as usize);
msg.extend_from_slice(&10u16.to_be_bytes()); msg.extend_from_slice(&total_len.to_be_bytes()); msg.extend_from_slice(&export_time.to_be_bytes());
msg.extend_from_slice(&seq.to_be_bytes());
msg.extend_from_slice(&obs_domain.to_be_bytes());
msg.extend_from_slice(sets);
msg
}
fn build_ipfix_template_set(template_id: u16, fields: &[(u16, u16)]) -> Vec<u8> {
let set_len = (4 + 4 + fields.len() * 4) as u16;
let mut set = Vec::with_capacity(set_len as usize);
set.extend_from_slice(&2u16.to_be_bytes()); set.extend_from_slice(&set_len.to_be_bytes());
set.extend_from_slice(&template_id.to_be_bytes());
set.extend_from_slice(&(fields.len() as u16).to_be_bytes());
for &(type_num, length) in fields {
set.extend_from_slice(&type_num.to_be_bytes());
set.extend_from_slice(&length.to_be_bytes());
}
set
}
#[test]
fn test_ipfix_varlen_short_round_trip() {
let template_set = build_ipfix_template_set(256, &[(8, 4), (94, 65535)]);
let template_msg = build_ipfix_message(0x62A0B1B9, 8, 1, &template_set);
let mut parser = NetflowParser::builder()
.with_cache_size(100)
.build()
.unwrap();
let result = parser.parse_bytes(&template_msg);
assert!(result.error.is_none(), "Template parse failed");
let mut data_set = Vec::new();
data_set.extend_from_slice(&256u16.to_be_bytes()); data_set.extend_from_slice(&12u16.to_be_bytes()); data_set.extend_from_slice(&[192, 168, 1, 1]); data_set.push(3); data_set.extend_from_slice(b"abc");
let data_msg = build_ipfix_message(0x62A0B1B9, 9, 1, &data_set);
let data_result = parser.parse_bytes(&data_msg);
assert!(
data_result.error.is_none(),
"Data parse failed: {:?}",
data_result.error
);
if let Some(NetflowPacket::IPFix(ipfix)) = data_result.packets.first() {
let serialized = ipfix
.to_be_bytes()
.expect("IPFIX varlen serialization failed");
assert_eq!(
serialized, data_msg,
"IPFIX varlen short field round-trip failed"
);
} else {
panic!("Expected IPFIX data packet");
}
}
#[test]
fn test_ipfix_varlen_long_round_trip() {
let template_set = build_ipfix_template_set(257, &[(83, 65535)]);
let template_msg = build_ipfix_message(0x62A0B1B9, 10, 1, &template_set);
let mut parser = NetflowParser::builder()
.with_cache_size(100)
.build()
.unwrap();
let result = parser.parse_bytes(&template_msg);
assert!(result.error.is_none(), "Template parse failed");
let value_len: u16 = 300;
let value: Vec<u8> = (0..value_len).map(|i| (i % 26 + 0x61) as u8).collect();
let body_len = 3 + value_len as usize; let padding_needed = (4 - (body_len % 4)) % 4;
let set_len = 4 + body_len + padding_needed;
let mut data_set = Vec::new();
data_set.extend_from_slice(&257u16.to_be_bytes()); data_set.extend_from_slice(&(set_len as u16).to_be_bytes());
data_set.push(255); data_set.extend_from_slice(&value_len.to_be_bytes()); data_set.extend_from_slice(&value);
data_set.extend(std::iter::repeat_n(0u8, padding_needed));
let data_msg = build_ipfix_message(0x62A0B1B9, 11, 1, &data_set);
let data_result = parser.parse_bytes(&data_msg);
assert!(
data_result.error.is_none(),
"Long varlen data parse failed: {:?}",
data_result.error
);
if let Some(NetflowPacket::IPFix(ipfix)) = data_result.packets.first() {
let serialized = ipfix
.to_be_bytes()
.expect("IPFIX long varlen serialization failed");
assert_eq!(
serialized, data_msg,
"IPFIX varlen long field (3-byte prefix) round-trip failed"
);
} else {
panic!("Expected IPFIX data packet");
}
}
#[test]
fn test_ipfix_mixed_fixed_and_varlen_round_trip() {
let template_set = build_ipfix_template_set(258, &[(8, 4), (94, 65535), (14, 4)]);
let template_msg = build_ipfix_message(0x62A0B1B9, 12, 1, &template_set);
let mut parser = NetflowParser::builder()
.with_cache_size(100)
.build()
.unwrap();
let result = parser.parse_bytes(&template_msg);
assert!(result.error.is_none(), "Template parse failed");
let mut data_set = Vec::new();
data_set.extend_from_slice(&258u16.to_be_bytes());
let body_len = 4 + 1 + 4 + 4; let padding = (4 - (body_len % 4)) % 4; let set_len = 4 + body_len + padding;
data_set.extend_from_slice(&(set_len as u16).to_be_bytes());
data_set.extend_from_slice(&[10, 0, 0, 1]); data_set.push(4); data_set.extend_from_slice(b"test"); data_set.extend_from_slice(&42u32.to_be_bytes()); data_set.extend(std::iter::repeat_n(0u8, padding));
let data_msg = build_ipfix_message(0x62A0B1B9, 13, 1, &data_set);
let data_result = parser.parse_bytes(&data_msg);
assert!(
data_result.error.is_none(),
"Mixed parse failed: {:?}",
data_result.error
);
if let Some(NetflowPacket::IPFix(ipfix)) = data_result.packets.first() {
let serialized = ipfix
.to_be_bytes()
.expect("Mixed varlen serialization failed");
assert_eq!(
serialized, data_msg,
"IPFIX mixed fixed+varlen round-trip failed"
);
} else {
panic!("Expected IPFIX data packet");
}
}
#[test]
fn test_field_round_trip_duration_micros_ntp() {
assert_field_round_trip(
&[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07],
FieldDataType::DurationMicrosNTP,
);
}
#[test]
fn test_field_round_trip_duration_nanos_ntp() {
assert_field_round_trip(
&[0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44],
FieldDataType::DurationNanosNTP,
);
}
#[test]
fn test_field_round_trip_unsigned_data_number_16_bytes() {
assert_field_round_trip(
&[
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
0x0F, 0x10,
],
FieldDataType::UnsignedDataNumber,
);
}
#[test]
fn test_field_round_trip_signed_data_number_16_bytes() {
assert_field_round_trip(
&[
0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
0xF1, 0xF0,
],
FieldDataType::SignedDataNumber,
);
}
#[test]
fn test_field_round_trip_unsigned_data_number_3_bytes() {
assert_field_round_trip(&[0x12, 0x34, 0x56], FieldDataType::UnsignedDataNumber);
}
#[test]
#[cfg(feature = "parse_unknown_fields")]
fn test_ipfix_enterprise_field_round_trip() {
let mut template_set = Vec::new();
template_set.extend_from_slice(&2u16.to_be_bytes()); template_set.extend_from_slice(&20u16.to_be_bytes()); template_set.extend_from_slice(&260u16.to_be_bytes()); template_set.extend_from_slice(&2u16.to_be_bytes()); template_set.extend_from_slice(&0x8001u16.to_be_bytes()); template_set.extend_from_slice(&4u16.to_be_bytes()); template_set.extend_from_slice(&12345u32.to_be_bytes()); template_set.extend_from_slice(&8u16.to_be_bytes()); template_set.extend_from_slice(&4u16.to_be_bytes());
let template_msg = build_ipfix_message(0x62A0B1B9, 30, 1, &template_set);
let mut parser = NetflowParser::builder()
.with_cache_size(100)
.build()
.unwrap();
let result = parser.parse_bytes(&template_msg);
assert!(
result.error.is_none(),
"IPFIX enterprise template parse failed: {:?}",
result.error
);
if let Some(NetflowPacket::IPFix(ipfix)) = result.packets.first() {
let serialized = ipfix
.to_be_bytes()
.expect("IPFIX enterprise template serialization failed");
assert_eq!(
serialized, template_msg,
"IPFIX enterprise template round-trip failed"
);
} else {
panic!("Expected IPFIX template packet");
}
let mut data_set = Vec::new();
data_set.extend_from_slice(&260u16.to_be_bytes()); data_set.extend_from_slice(&12u16.to_be_bytes()); data_set.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]); data_set.extend_from_slice(&[192, 168, 1, 1]);
let data_msg = build_ipfix_message(0x62A0B1B9, 31, 1, &data_set);
let data_result = parser.parse_bytes(&data_msg);
assert!(
data_result.error.is_none(),
"IPFIX enterprise data parse failed: {:?}",
data_result.error
);
if let Some(NetflowPacket::IPFix(ipfix)) = data_result.packets.first() {
let serialized = ipfix
.to_be_bytes()
.expect("IPFIX enterprise data serialization failed");
assert_eq!(
serialized, data_msg,
"IPFIX enterprise data round-trip failed"
);
} else {
panic!("Expected IPFIX data packet");
}
}
#[test]
fn test_v9_multi_template_in_single_flowset() {
let mut packet = Vec::new();
packet.extend_from_slice(&9u16.to_be_bytes()); packet.extend_from_slice(&1u16.to_be_bytes()); packet.extend_from_slice(&0u32.to_be_bytes()); packet.extend_from_slice(&0u32.to_be_bytes()); packet.extend_from_slice(&1u32.to_be_bytes()); packet.extend_from_slice(&1u32.to_be_bytes());
packet.extend_from_slice(&0u16.to_be_bytes()); packet.extend_from_slice(&24u16.to_be_bytes());
packet.extend_from_slice(&256u16.to_be_bytes()); packet.extend_from_slice(&2u16.to_be_bytes()); packet.extend_from_slice(&8u16.to_be_bytes()); packet.extend_from_slice(&4u16.to_be_bytes()); packet.extend_from_slice(&12u16.to_be_bytes()); packet.extend_from_slice(&4u16.to_be_bytes());
packet.extend_from_slice(&257u16.to_be_bytes()); packet.extend_from_slice(&1u16.to_be_bytes()); packet.extend_from_slice(&10u16.to_be_bytes()); packet.extend_from_slice(&4u16.to_be_bytes());
let mut parser = NetflowParser::builder()
.with_cache_size(100)
.build()
.unwrap();
let result = parser.parse_bytes(&packet);
assert!(
result.error.is_none(),
"V9 multi-template parse failed: {:?}",
result.error
);
assert!(
parser.has_v9_template(256),
"Template 256 should be cached after multi-template flowset"
);
assert!(
parser.has_v9_template(257),
"Template 257 should be cached after multi-template flowset"
);
}
#[test]
fn test_ipfix_fixed_only_template_no_varlen_overhead() {
let hex_template = "000a002862a0b1b9000000086c6a7e11\
000200180100000400080004000c0004000a0004000e0004";
let mut parser = NetflowParser::builder()
.with_cache_size(100)
.build()
.unwrap();
let template_bytes = hex::decode(hex_template).unwrap();
let _ = parser.parse_bytes(&template_bytes);
let hex_data = "000a002462a0b1b9000000096c6a7e11\
01000014c0a801010a0000010000000a00000014";
let data_bytes = hex::decode(hex_data).unwrap();
let data_result = parser.parse_bytes(&data_bytes);
if let Some(NetflowPacket::IPFix(ipfix)) = data_result.packets.first() {
let serialized = ipfix.to_be_bytes().expect("serialization failed");
assert_eq!(serialized, data_bytes);
for flowset in &ipfix.flowsets {
if let netflow_parser::variable_versions::ipfix::FlowSetBody::Data(data) =
&flowset.body
{
assert!(
!data.has_varlen_metadata(),
"template_field_lengths should be empty for fixed-length-only templates"
);
}
}
} else {
panic!("Expected IPFIX data packet");
}
}