use netflow_parser::NetflowParser;
#[test]
fn test_clear_templates() {
let mut parser = NetflowParser::default();
let v9_template_packet: Vec<u8> = vec![
0, 9, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 12, 1, 0, 0, 1, 0, 1, 0, 4, ];
assert!(
!parser.parse_bytes(&v9_template_packet).packets.is_empty(),
"Template packet should parse successfully"
);
let v9_info = parser.v9_cache_info();
assert!(
v9_info.current_size > 0,
"V9 cache should have a template before clearing"
);
parser.clear_v9_templates();
let v9_info = parser.v9_cache_info();
assert_eq!(
v9_info.current_size, 0,
"V9 cache should be empty after clearing"
);
parser.clear_ipfix_templates();
let ipfix_info = parser.ipfix_cache_info();
assert_eq!(ipfix_info.current_size, 0);
}
#[test]
fn test_custom_cache_size() {
let parser = NetflowParser::builder()
.with_cache_size(500)
.build()
.expect("Failed to build parser");
let v9_info = parser.v9_cache_info();
assert_eq!(v9_info.max_size_per_cache, 500);
let ipfix_info = parser.ipfix_cache_info();
assert_eq!(ipfix_info.max_size_per_cache, 500);
}
#[test]
fn test_different_cache_sizes() {
let parser = NetflowParser::builder()
.with_v9_cache_size(750)
.with_ipfix_cache_size(1500)
.build()
.expect("Failed to build parser");
let v9_info = parser.v9_cache_info();
assert_eq!(v9_info.max_size_per_cache, 750);
let ipfix_info = parser.ipfix_cache_info();
assert_eq!(ipfix_info.max_size_per_cache, 1500);
}
#[test]
fn test_template_ids_after_parsing() {
let mut parser = NetflowParser::default();
let v9_template_packet: Vec<u8> = vec![
0, 9, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 12, 1, 0, 0, 1, 0,
1, 0, 4,
];
assert!(
!parser.parse_bytes(&v9_template_packet).packets.is_empty(),
"Template packet should parse successfully"
);
let v9_templates = parser.v9_template_ids();
assert!(
v9_templates.contains(&256),
"V9 template IDs should include 256 after parsing template"
);
assert!(
parser.has_v9_template(256),
"has_v9_template(256) should return true after caching"
);
}
#[test]
fn test_hit_rate_after_activity() {
let mut parser = NetflowParser::default();
let v9_template_packet: Vec<u8> = vec![
0, 9, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 12, 1, 0, 0, 1, 0,
1, 0, 4,
];
assert!(
!parser.parse_bytes(&v9_template_packet).packets.is_empty(),
"Template packet should parse successfully"
);
let v9_data_packet: Vec<u8> = vec![
0, 9, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1,
0, 0, 8, 0, 0, 0, 42, ];
let result = parser.parse_bytes(&v9_data_packet);
assert!(
result.error.is_none(),
"Data packet parse should succeed after template is cached"
);
let stats = parser.v9_cache_info();
let hit_rate = stats.metrics.hit_rate();
assert!(
hit_rate.is_some(),
"hit_rate should return Some after cache activity"
);
assert_eq!(
hit_rate.unwrap(),
1.0,
"hit_rate should be 1.0 after 1 hit and 0 misses"
);
}
fn ipfix_template_packet(template_id: u16, fields: &[(u16, u16)]) -> Vec<u8> {
let template_record_len = 4 + fields.len() * 4;
let set_len = (4 + template_record_len) as u16;
let msg_len = 16 + set_len;
let mut pkt = Vec::with_capacity(msg_len as usize);
pkt.extend_from_slice(&0x000Au16.to_be_bytes()); pkt.extend_from_slice(&msg_len.to_be_bytes());
pkt.extend_from_slice(&1u32.to_be_bytes()); pkt.extend_from_slice(&1u32.to_be_bytes()); pkt.extend_from_slice(&1u32.to_be_bytes()); pkt.extend_from_slice(&2u16.to_be_bytes()); pkt.extend_from_slice(&set_len.to_be_bytes());
pkt.extend_from_slice(&template_id.to_be_bytes());
pkt.extend_from_slice(&(fields.len() as u16).to_be_bytes());
for &(field_type, field_length) in fields {
pkt.extend_from_slice(&field_type.to_be_bytes());
pkt.extend_from_slice(&field_length.to_be_bytes());
}
pkt
}
fn ipfix_withdrawal_packet(template_id: u16) -> Vec<u8> {
let set_len: u16 = 8; let msg_len: u16 = 16 + set_len;
let mut pkt = Vec::with_capacity(msg_len as usize);
pkt.extend_from_slice(&0x000Au16.to_be_bytes());
pkt.extend_from_slice(&msg_len.to_be_bytes());
pkt.extend_from_slice(&1u32.to_be_bytes());
pkt.extend_from_slice(&2u32.to_be_bytes()); pkt.extend_from_slice(&1u32.to_be_bytes());
pkt.extend_from_slice(&2u16.to_be_bytes()); pkt.extend_from_slice(&set_len.to_be_bytes());
pkt.extend_from_slice(&template_id.to_be_bytes());
pkt.extend_from_slice(&0u16.to_be_bytes()); pkt
}
fn ipfix_options_withdrawal_packet(template_id: u16) -> Vec<u8> {
let set_len: u16 = 10; let msg_len: u16 = 16 + set_len;
let mut pkt = Vec::with_capacity(msg_len as usize);
pkt.extend_from_slice(&0x000Au16.to_be_bytes());
pkt.extend_from_slice(&msg_len.to_be_bytes());
pkt.extend_from_slice(&1u32.to_be_bytes());
pkt.extend_from_slice(&2u32.to_be_bytes());
pkt.extend_from_slice(&1u32.to_be_bytes());
pkt.extend_from_slice(&3u16.to_be_bytes()); pkt.extend_from_slice(&set_len.to_be_bytes());
pkt.extend_from_slice(&template_id.to_be_bytes());
pkt.extend_from_slice(&0u16.to_be_bytes()); pkt.extend_from_slice(&0u16.to_be_bytes()); pkt
}
fn ipfix_options_template_pkt(template_id: u16) -> Vec<u8> {
let set_len: u16 = 22; let msg_len: u16 = 16 + set_len;
let mut pkt = Vec::with_capacity(msg_len as usize);
pkt.extend_from_slice(&0x000Au16.to_be_bytes());
pkt.extend_from_slice(&msg_len.to_be_bytes());
pkt.extend_from_slice(&1u32.to_be_bytes());
pkt.extend_from_slice(&1u32.to_be_bytes());
pkt.extend_from_slice(&1u32.to_be_bytes());
pkt.extend_from_slice(&3u16.to_be_bytes()); pkt.extend_from_slice(&set_len.to_be_bytes());
pkt.extend_from_slice(&template_id.to_be_bytes());
pkt.extend_from_slice(&3u16.to_be_bytes()); pkt.extend_from_slice(&1u16.to_be_bytes()); pkt.extend_from_slice(&1u16.to_be_bytes());
pkt.extend_from_slice(&4u16.to_be_bytes());
pkt.extend_from_slice(&3u16.to_be_bytes());
pkt.extend_from_slice(&4u16.to_be_bytes());
pkt.extend_from_slice(&4u16.to_be_bytes());
pkt.extend_from_slice(&4u16.to_be_bytes());
pkt
}
#[test]
fn test_ipfix_individual_template_withdrawal() {
let mut parser = NetflowParser::default();
assert!(
!parser
.parse_bytes(&ipfix_template_packet(256, &[(1, 4)]))
.packets
.is_empty()
);
assert!(
!parser
.parse_bytes(&ipfix_template_packet(257, &[(2, 4)]))
.packets
.is_empty()
);
assert!(parser.has_ipfix_template(256));
assert!(parser.has_ipfix_template(257));
assert!(
!parser
.parse_bytes(&ipfix_withdrawal_packet(256))
.packets
.is_empty()
);
assert!(
!parser.has_ipfix_template(256),
"Template 256 should be withdrawn"
);
assert!(parser.has_ipfix_template(257), "Template 257 should remain");
}
#[test]
fn test_ipfix_withdraw_all_data_templates() {
let mut parser = NetflowParser::default();
assert!(
!parser
.parse_bytes(&ipfix_template_packet(256, &[(1, 4)]))
.packets
.is_empty()
);
assert!(
!parser
.parse_bytes(&ipfix_template_packet(257, &[(2, 4)]))
.packets
.is_empty()
);
assert!(
!parser
.parse_bytes(&ipfix_template_packet(258, &[(3, 4)]))
.packets
.is_empty()
);
assert_eq!(parser.ipfix_cache_info().current_size, 3);
assert!(
!parser
.parse_bytes(&ipfix_options_template_pkt(259))
.packets
.is_empty()
);
assert_eq!(parser.ipfix_cache_info().current_size, 4);
assert!(
!parser
.parse_bytes(&ipfix_withdrawal_packet(2))
.packets
.is_empty()
);
assert!(!parser.has_ipfix_template(256));
assert!(!parser.has_ipfix_template(257));
assert!(!parser.has_ipfix_template(258));
assert_eq!(
parser.ipfix_cache_info().current_size,
1,
"Only the options template should remain after withdraw-all data"
);
}
#[test]
fn test_ipfix_withdraw_all_options_templates() {
let mut parser = NetflowParser::default();
assert!(
!parser
.parse_bytes(&ipfix_template_packet(256, &[(1, 4)]))
.packets
.is_empty()
);
assert!(
!parser
.parse_bytes(&ipfix_options_template_pkt(258))
.packets
.is_empty()
);
assert!(
!parser
.parse_bytes(&ipfix_options_template_pkt(259))
.packets
.is_empty()
);
assert_eq!(parser.ipfix_cache_info().current_size, 3);
assert!(
!parser
.parse_bytes(&ipfix_options_withdrawal_packet(3))
.packets
.is_empty()
);
assert!(
parser.has_ipfix_template(256),
"Data template should survive options withdraw-all"
);
assert_eq!(
parser.ipfix_cache_info().current_size,
1,
"Only the data template should remain after withdraw-all options"
);
}
#[test]
fn test_ipfix_withdraw_all_drains_pending_flows() {
use netflow_parser::variable_versions::PendingFlowsConfig;
let mut parser = NetflowParser::builder()
.with_ipfix_pending_flows(PendingFlowsConfig::default())
.build()
.expect("valid config");
let data_pkt = vec![
0x00, 0x0A, 0x00, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
0x00, 0x01, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x42,
];
let _ = parser.parse_bytes(&data_pkt); assert_eq!(parser.ipfix_cache_info().pending_flow_count, 1);
assert!(
!parser
.parse_bytes(&ipfix_template_packet(256, &[(1, 4)]))
.packets
.is_empty()
);
assert_eq!(parser.ipfix_cache_info().pending_flow_count, 0);
assert_eq!(parser.ipfix_cache_info().metrics.pending_replayed, 1);
let mut data_257 = data_pkt.clone();
data_257[16] = 0x01;
data_257[17] = 0x01; let _ = parser.parse_bytes(&data_257); assert_eq!(parser.ipfix_cache_info().pending_flow_count, 1);
assert!(
!parser
.parse_bytes(&ipfix_withdrawal_packet(2))
.packets
.is_empty()
);
assert!(!parser.has_ipfix_template(256));
assert_eq!(
parser.ipfix_cache_info().metrics.pending_dropped,
0,
"No pending flows should be dropped (257 was not in template cache)"
);
assert_eq!(
parser.ipfix_cache_info().pending_flow_count,
1,
"Pending flow for template 257 should still be cached"
);
}