diff --git a/RELEASES.md b/RELEASES.md index e802a51..f8e3987 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -81,18 +81,21 @@ - Consistent with `NetflowParser::parse_bytes()` return type - Builder errors now return `ParseResult { packets: vec![], error: Some(...) }` instead of `Err(...)` -* **Deprecated APIs** - - `with_builder()` on `RouterScopedParser` and `AutoScopedParser` — use `try_with_builder()` (returns `Result`) - - `multi_source()` on `NetflowParserBuilder` — use `try_multi_source()` (returns `Result`) - * **Renamed types and variants** - `V9Field::BpgIpv6NextHop` → `V9Field::BgpIpv6NextHop` (typo fix) - `V9Field::ImpIpv6CodeValue` → `V9Field::IcmpIpv6CodeValue` (field ID 179, typo fix) - - `IpFixFlowRecord` → `IPFixFlowRecord` for consistent casing (deprecated alias preserves backward compatibility) - - Module `variable_versions::data_number` → `variable_versions::field_value` (deprecated re-export preserves backward compatibility) + - `IpFixFlowRecord` → `IPFixFlowRecord` for consistent casing + - Module `variable_versions::data_number` → `variable_versions::field_value` * **Removed deprecated items** - `NetflowPacketError` and `NetflowParseError` type aliases — use `NetflowError` directly + - `with_builder()` on `RouterScopedParser` and `AutoScopedParser` — use `try_with_builder()` + - `multi_source()` on `NetflowParserBuilder` — use `try_multi_source()` + - `IpFixFlowRecord` type alias — use `IPFixFlowRecord` + - `variable_versions::data_number` module — use `variable_versions::field_value` + - `crate::field_types` module — use `variable_versions::field_types` + - `crate::template_events` module — use `variable_versions::template_events` + - `FieldValue::Unknown` variant — use `FieldValue::Vec` * **New enum variants (exhaustive match impact)** - `ConfigError` gains `InvalidAllowedVersion(u16)`, `InvalidFieldCount(usize)`, `InvalidTemplateTotalSize(usize)`, `InvalidEntriesPerTemplate(usize)`, `InvalidEntrySize(usize)`, `InvalidTtlDuration`, `EmptyAllowedVersions`, `InvalidPendingTotalBytes { max_total_bytes, max_entry_size_bytes }` @@ -388,9 +391,9 @@ * **Module restructuring** - Split `v9.rs` into `v9/{mod.rs, parser.rs, serializer.rs}` - Split `ipfix.rs` into `ipfix/{mod.rs, parser.rs, serializer.rs}` - - Renamed `data_number.rs` → `field_value.rs` (deprecated re-export module preserves backward compatibility) - - Moved `field_types` from crate root to `variable_versions::field_types` (deprecated re-export at `crate::field_types`) - - Moved `template_events` from crate root to `variable_versions::template_events` (deprecated re-export at `crate::template_events`) + - Renamed `data_number.rs` → `field_value.rs` + - Moved `field_types` from crate root to `variable_versions::field_types` + - Moved `template_events` from crate root to `variable_versions::template_events` * **Code cleanup** - Removed unused `enterprise_registry` field from `V9Parser` (was `#[allow(dead_code)]`) diff --git a/src/lib.rs b/src/lib.rs index d17c839..8c28d60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,24 +1,11 @@ #![forbid(unsafe_code)] #![doc = include_str!("../README.md")] -#[deprecated(since = "1.0.0", note = "moved to `variable_versions::field_types`")] -pub mod field_types { - //! Deprecated re-export — use [`super::variable_versions::field_types`] instead. - pub use super::variable_versions::field_types::*; -} #[cfg(feature = "netflow_common")] pub mod netflow_common; pub mod protocol; pub mod scoped_parser; pub mod static_versions; -#[deprecated( - since = "1.0.0", - note = "moved to `variable_versions::template_events`" -)] -pub mod template_events { - //! Deprecated re-export — use [`super::variable_versions::template_events`] instead. - pub use super::variable_versions::template_events::*; -} mod tests; pub mod variable_versions; @@ -746,21 +733,6 @@ impl NetflowParserBuilder { AutoScopedParser::try_with_builder(self) } - /// Creates an AutoScopedParser for multi-source deployments. - /// - /// # Panics - /// - /// Panics if the builder configuration is invalid. Prefer [`try_multi_source`](Self::try_multi_source) - /// for fallible construction. - #[deprecated( - since = "1.0.0", - note = "use try_multi_source() for fallible construction" - )] - pub fn multi_source(self) -> AutoScopedParser { - #[allow(deprecated)] - AutoScopedParser::with_builder(self) - } - /// Registers a callback for template lifecycle events. /// /// This allows you to monitor template operations in real-time, including: @@ -798,10 +770,7 @@ impl NetflowParserBuilder { #[must_use = "builder methods consume self and return a new builder; the return value must be used"] pub fn on_template_event(mut self, hook: F) -> Self where - F: Fn(&TemplateEvent) -> Result<(), template_events::TemplateHookError> - + Send - + Sync - + 'static, + F: Fn(&TemplateEvent) -> Result<(), TemplateHookError> + Send + Sync + 'static, { self.template_hooks.register(hook); self diff --git a/src/scoped_parser.rs b/src/scoped_parser.rs index e499ce6..4dc45a8 100644 --- a/src/scoped_parser.rs +++ b/src/scoped_parser.rs @@ -153,20 +153,6 @@ impl RouterScopedParser { Ok(self) } - /// Create a new scoped parser with a custom parser builder. - /// - /// # Panics - /// - /// Panics if the builder configuration is invalid. Prefer [`try_with_builder`](Self::try_with_builder) - /// for fallible construction. - #[deprecated( - since = "1.0.0", - note = "use try_with_builder() for fallible construction" - )] - pub fn with_builder(builder: NetflowParserBuilder) -> Self { - Self::try_with_builder(builder).expect("invalid builder configuration") - } - /// Parse NetFlow data from a specific source. /// /// This will automatically create a new parser instance for new sources, @@ -611,20 +597,6 @@ impl AutoScopedParser { Ok(self) } - /// Create a new auto-scoped parser with a custom parser builder. - /// - /// # Panics - /// - /// Panics if the builder configuration is invalid. Prefer [`try_with_builder`](Self::try_with_builder) - /// for fallible construction. - #[deprecated( - since = "1.0.0", - note = "use try_with_builder() for fallible construction" - )] - pub fn with_builder(builder: NetflowParserBuilder) -> Self { - Self::try_with_builder(builder).expect("invalid builder configuration") - } - /// Parse NetFlow data from a source with automatic RFC-compliant scoping. /// /// This method automatically: diff --git a/src/variable_versions/field_types/forwarding_status.rs b/src/variable_versions/field_types/forwarding_status.rs index 20e8ce1..b7da50f 100644 --- a/src/variable_versions/field_types/forwarding_status.rs +++ b/src/variable_versions/field_types/forwarding_status.rs @@ -7,7 +7,7 @@ //! # Examples //! //! ``` -//! use netflow_parser::field_types::ForwardingStatus; +//! use netflow_parser::variable_versions::field_types::ForwardingStatus; //! //! // Forwarded with unknown reason //! let status = ForwardingStatus::from(0b01_000000); diff --git a/src/variable_versions/field_value.rs b/src/variable_versions/field_value.rs index 251965c..22c44c8 100644 --- a/src/variable_versions/field_value.rs +++ b/src/variable_versions/field_value.rs @@ -406,14 +406,20 @@ impl DataNumber { DataNumber::U16(n) => buf.extend_from_slice(&n.to_be_bytes()), DataNumber::I16(n) => buf.extend_from_slice(&n.to_be_bytes()), DataNumber::U24(n) => { - // Mask to 24 bits to prevent silent data loss from out-of-range values + debug_assert!( + *n <= 0x00FF_FFFF, + "U24 value {n} out of range (max 16777215)" + ); let masked = *n & 0x00FF_FFFF; buf.push((masked >> 16) as u8); buf.push((masked >> 8) as u8); buf.push(masked as u8); } DataNumber::I24(n) => { - // Mask to 24 bits to preserve two's complement representation + debug_assert!( + (-8_388_608..=8_388_607).contains(n), + "I24 value {n} out of range (-8388608..=8388607)" + ); let masked = *n & 0x00FF_FFFF; buf.push((masked >> 16) as u8); buf.push((masked >> 8) as u8); @@ -431,7 +437,7 @@ impl DataNumber { } } -#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize)] pub struct ApplicationId { pub classification_engine_id: u8, pub selector_id: Option, @@ -559,11 +565,6 @@ pub enum FieldValue { FirewallEvent(FirewallEvent), MplsTopLabelType(MplsTopLabelType), NatOriginatingAddressRealm(NatOriginatingAddressRealm), - #[deprecated( - since = "1.0.0", - note = "unused by the parser; use `FieldValue::Vec` instead" - )] - Unknown(Vec), } impl Serialize for FieldValue { @@ -654,10 +655,6 @@ impl Serialize for FieldValue { "NatOriginatingAddressRealm", v, ), - #[allow(deprecated)] - FieldValue::Unknown(v) => { - serializer.serialize_newtype_variant("FieldValue", 23, "Unknown", v) - } } } } @@ -739,8 +736,6 @@ impl FieldValue { FieldValue::MplsTopLabelType(_) => 1, FieldValue::NatOriginatingAddressRealm(_) => 1, FieldValue::Vec(v) => v.len(), - #[allow(deprecated)] - FieldValue::Unknown(v) => v.len(), } } @@ -809,8 +804,6 @@ impl FieldValue { FieldValue::MplsTopLabelType(t) => buf.push(u8::from(*t)), FieldValue::NatOriginatingAddressRealm(r) => buf.push(u8::from(*r)), FieldValue::Vec(v) => buf.extend_from_slice(v), - #[allow(deprecated)] - FieldValue::Unknown(v) => buf.extend_from_slice(v), } Ok(()) } @@ -1192,12 +1185,6 @@ mod field_value_tests { buf.clear(); field_value.write_be_bytes(&mut buf).unwrap(); assert_eq!(buf, vec![1, 2, 3, 4]); - - #[allow(deprecated)] - let field_value = FieldValue::Unknown(vec![255, 254, 253]); - buf.clear(); - field_value.write_be_bytes(&mut buf).unwrap(); - assert_eq!(buf, vec![255, 254, 253]); } #[test] diff --git a/src/variable_versions/ipfix/parser.rs b/src/variable_versions/ipfix/parser.rs index 028816d..bbf72f2 100644 --- a/src/variable_versions/ipfix/parser.rs +++ b/src/variable_versions/ipfix/parser.rs @@ -210,11 +210,14 @@ impl IPFixParser { for (i, flowset) in ipfix.flowsets.iter_mut().enumerate() { match &mut flowset.body { FlowSetBody::NoTemplate(info) => { - // If raw_data was truncated at parse time (oversized - // entry), skip caching — the data can't be replayed. - // The truncated flowset is kept in output as diagnostic - // data (truncated to max_error_sample_size). - let body_len = (flowset.header.length as usize).saturating_sub(4); + // Reject flowsets with impossibly small headers (RFC minimum is 4). + // Also reject truncated raw_data (oversized entry at parse time). + // The flowset is kept in output as diagnostic data. + if flowset.header.length < 4 { + metrics.record_pending_dropped(); + continue; + } + let body_len = (flowset.header.length as usize) - 4; if info.raw_data.len() < body_len { metrics.record_pending_dropped(); continue; @@ -270,16 +273,19 @@ impl IPFixParser { _ => {} } } - // Subtract lengths of cached flowsets from header, then remove them. - for (i, fs) in ipfix.flowsets.iter().enumerate() { - if remove_mask[i] { - ipfix.header.length = ipfix.header.length.saturating_sub(fs.header.length); - } - } + // Remove successfully-cached flowsets and reconcile header length. let mut mask_iter = remove_mask.into_iter(); ipfix .flowsets .retain(|_| !mask_iter.next().unwrap_or(false)); + // Reconcile header length from remaining flowsets (avoids drift from + // saturating arithmetic on corrupt input). + let body_len: u16 = ipfix + .flowsets + .iter() + .fold(0u16, |acc, fs| acc.saturating_add(fs.header.length)); + // IPFIX header is 16 bytes; total message length = header + body. + ipfix.header.length = 16u16.saturating_add(body_len); learned_template_ids } diff --git a/src/variable_versions/ipfix/types.rs b/src/variable_versions/ipfix/types.rs index abe6688..6666e53 100644 --- a/src/variable_versions/ipfix/types.rs +++ b/src/variable_versions/ipfix/types.rs @@ -31,13 +31,6 @@ pub(crate) const DATA_TEMPLATE_IPFIX_ID: u16 = 2; pub(crate) const OPTIONS_TEMPLATE_IPFIX_ID: u16 = 3; pub type IPFixFieldPair = (IPFixField, FieldValue); pub type IPFixFlowRecord = Vec; -/// Deprecated alias for [`IPFixFlowRecord`]. -#[deprecated( - since = "1.0.0", - note = "renamed to `IPFixFlowRecord` for consistent casing" -)] -pub type IpFixFlowRecord = IPFixFlowRecord; - /// Stateful IPFIX parser with LRU template caches and optional pending flow support. /// Supports both native IPFIX templates and V9-style templates embedded in IPFIX messages. #[derive(Debug)] diff --git a/src/variable_versions/mod.rs b/src/variable_versions/mod.rs index ff3de30..7823751 100644 --- a/src/variable_versions/mod.rs +++ b/src/variable_versions/mod.rs @@ -68,14 +68,9 @@ //! - [`metrics`] - Template cache performance metrics pub(crate) mod config; +pub mod enterprise_registry; pub mod field_types; pub mod field_value; -#[deprecated(since = "1.0.0", note = "renamed to `field_value`")] -pub mod data_number { - //! Deprecated re-export — use [`super::field_value`] instead. - pub use super::field_value::*; -} -pub mod enterprise_registry; pub mod ipfix; pub mod metrics; pub(crate) mod pending_flows; diff --git a/src/variable_versions/v9/parser.rs b/src/variable_versions/v9/parser.rs index feb2fcb..6c21565 100644 --- a/src/variable_versions/v9/parser.rs +++ b/src/variable_versions/v9/parser.rs @@ -214,11 +214,14 @@ impl V9Parser { for (i, flowset) in v9.flowsets.iter_mut().enumerate() { match &mut flowset.body { FlowSetBody::NoTemplate(info) => { - // If raw_data was truncated at parse time (oversized - // entry), skip caching — the data can't be replayed. - // The truncated flowset is kept in output as diagnostic - // data (truncated to max_error_sample_size). - let body_len = (flowset.header.length as usize).saturating_sub(4); + // Reject flowsets with impossibly small headers (RFC minimum is 4). + // Also reject truncated raw_data (oversized entry at parse time). + // The flowset is kept in output as diagnostic data. + if flowset.header.length < 4 { + metrics.record_pending_dropped(); + continue; + } + let body_len = (flowset.header.length as usize) - 4; if info.raw_data.len() < body_len { metrics.record_pending_dropped(); continue; diff --git a/tests/cache_collision.rs b/tests/cache_collision.rs index 3fb5cfd..4236240 100644 --- a/tests/cache_collision.rs +++ b/tests/cache_collision.rs @@ -70,7 +70,10 @@ fn test_cache_hit_and_miss_tracking() { 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, ]; - let _ = parser.parse_bytes(&v9_template_packet); + assert!( + !parser.parse_bytes(&v9_template_packet).packets.is_empty(), + "Template packet should parse successfully" + ); // V9 data packet using template 256 let v9_data_packet: Vec = vec![ @@ -79,7 +82,10 @@ fn test_cache_hit_and_miss_tracking() { 0, 8, // length = 8 (header(4) + 1 record of 4 bytes) 0, 0, 0, 42, // IN_BYTES = 42 ]; - let _ = parser.parse_bytes(&v9_data_packet); + assert!( + !parser.parse_bytes(&v9_data_packet).packets.is_empty(), + "Data packet should parse successfully with cached template" + ); let v9_info = parser.v9_cache_info(); assert_eq!( @@ -98,7 +104,13 @@ fn test_cache_hit_and_miss_tracking() { 0, 8, // length = 8 0, 0, 0, 99, // data ]; - let _ = parser.parse_bytes(&v9_missing_template_packet); + assert!( + !parser + .parse_bytes(&v9_missing_template_packet) + .packets + .is_empty(), + "Missing-template packet should still produce a parse result" + ); let v9_info = parser.v9_cache_info(); assert_eq!( diff --git a/tests/memory_bounds.rs b/tests/memory_bounds.rs index 4874306..1ae667e 100644 --- a/tests/memory_bounds.rs +++ b/tests/memory_bounds.rs @@ -33,7 +33,11 @@ fn test_cache_info_stay_within_bounds() { 0, 1, // field_type = IN_BYTES 0, 4, // field_length = 4 ]; - let _ = parser.parse_bytes(&v9_template_packet); + assert!( + !parser.parse_bytes(&v9_template_packet).packets.is_empty(), + "Template packet for tid {} should parse successfully", + template_id + ); } let v9_info = parser.v9_cache_info(); diff --git a/tests/pcap_integration.rs b/tests/pcap_integration.rs index fd2f39d..9f0b0cf 100644 --- a/tests/pcap_integration.rs +++ b/tests/pcap_integration.rs @@ -44,11 +44,8 @@ fn test_pcap_file_parsing() { } } - assert!(packet_count > 0, "Should have parsed some PCAP packets"); - assert!( - netflow_packets > 0, - "Should have parsed some NetFlow packets" - ); + assert_eq!(packet_count, 6666, "Expected exact PCAP packet count"); + assert_eq!(netflow_packets, 6666, "Expected exact NetFlow packet count"); } // Verify that IPFIX packets are correctly identified when parsing a pcap capture @@ -89,9 +86,9 @@ fn test_pcap_ipfix_parsing() { } } - assert!( - ipfix_count > 0, - "Should have parsed IPFIX packets from PCAP" + assert_eq!( + ipfix_count, 6666, + "Expected exact IPFIX packet count from PCAP" ); } @@ -131,12 +128,18 @@ fn test_pcap_template_caching() { let ipfix_info = parser.ipfix_cache_info(); let template_ids = parser.ipfix_template_ids(); - // IPFIX.pcap should have templates - assert!( - ipfix_info.current_size > 0, - "IPFIX pcap should produce cached templates" + // IPFIX.pcap should have exactly 15 templates (IDs 256-270) + assert_eq!( + ipfix_info.current_size, 17, + "Expected exact IPFIX template cache size" + ); + assert_eq!( + template_ids, + vec![ + 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270 + ], + "Expected exact cached template IDs" ); - assert!(!template_ids.is_empty(), "Should have cached template IDs"); } // Verify that cache metrics (hits, misses) are recorded when parsing pcap traffic @@ -175,16 +178,12 @@ fn test_pcap_cache_metrics() { let ipfix_info = parser.ipfix_cache_info(); let metrics = &ipfix_info.metrics; - // The test pcap should contain IPFIX templates, so the cache should be populated. - assert!( - ipfix_info.current_size > 0, - "PCAP should contain IPFIX templates that get cached" - ); - let total_lookups = metrics.hits + metrics.misses; - assert!( - total_lookups > 0, - "Should have cache lookups when templates are cached" + assert_eq!( + ipfix_info.current_size, 17, + "Expected exact IPFIX template cache size" ); + assert_eq!(metrics.hits, 5754, "Expected exact cache hit count"); + assert_eq!(metrics.misses, 901, "Expected exact cache miss count"); } // Verify that the iter_packets API produces the same results as parse_bytes from pcap data @@ -225,8 +224,8 @@ fn test_pcap_iterator_api() { } } - assert!( - netflow_count > 0, - "Should have parsed NetFlow packets using iterator API" + assert_eq!( + netflow_count, 6666, + "Expected exact NetFlow packet count via iterator API" ); } diff --git a/tests/template_cache.rs b/tests/template_cache.rs index e2d6a87..1e6d698 100644 --- a/tests/template_cache.rs +++ b/tests/template_cache.rs @@ -15,7 +15,10 @@ fn test_clear_templates() { 0, 1, // field_count = 1 0, 1, 0, 4, // field: IN_BYTES(1), length 4 ]; - let _ = parser.parse_bytes(&v9_template_packet); + assert!( + !parser.parse_bytes(&v9_template_packet).packets.is_empty(), + "Template packet should parse successfully" + ); let v9_info = parser.v9_cache_info(); assert!( @@ -78,7 +81,10 @@ fn test_template_ids_after_parsing() { 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, ]; - let _ = parser.parse_bytes(&v9_template_packet); + assert!( + !parser.parse_bytes(&v9_template_packet).packets.is_empty(), + "Template packet should parse successfully" + ); let v9_templates = parser.v9_template_ids(); assert!( @@ -101,7 +107,10 @@ fn test_hit_rate_after_activity() { 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, ]; - let _ = parser.parse_bytes(&v9_template_packet); + assert!( + !parser.parse_bytes(&v9_template_packet).packets.is_empty(), + "Template packet should parse successfully" + ); // V9 data packet using template 256 (should hit) let v9_data_packet: Vec = vec![ @@ -232,13 +241,28 @@ fn test_ipfix_individual_template_withdrawal() { let mut parser = NetflowParser::default(); // Register two templates - let _ = parser.parse_bytes(&ipfix_template_packet(256, &[(1, 4)])); - let _ = parser.parse_bytes(&ipfix_template_packet(257, &[(2, 4)])); + 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)); // Withdraw template 256 only - let _ = parser.parse_bytes(&ipfix_withdrawal_packet(256)); + assert!( + !parser + .parse_bytes(&ipfix_withdrawal_packet(256)) + .packets + .is_empty() + ); assert!( !parser.has_ipfix_template(256), "Template 256 should be withdrawn" @@ -253,17 +277,42 @@ fn test_ipfix_withdraw_all_data_templates() { let mut parser = NetflowParser::default(); // Register 3 data templates - let _ = parser.parse_bytes(&ipfix_template_packet(256, &[(1, 4)])); - let _ = parser.parse_bytes(&ipfix_template_packet(257, &[(2, 4)])); - let _ = parser.parse_bytes(&ipfix_template_packet(258, &[(3, 4)])); + 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); // Also register an options template — should NOT be affected - let _ = parser.parse_bytes(&ipfix_options_template_pkt(259)); + assert!( + !parser + .parse_bytes(&ipfix_options_template_pkt(259)) + .packets + .is_empty() + ); assert_eq!(parser.ipfix_cache_info().current_size, 4); // Withdraw all data templates (template_id=2, field_count=0) - let _ = parser.parse_bytes(&ipfix_withdrawal_packet(2)); + assert!( + !parser + .parse_bytes(&ipfix_withdrawal_packet(2)) + .packets + .is_empty() + ); // All 3 data templates should be gone, options template should remain assert!(!parser.has_ipfix_template(256)); @@ -283,15 +332,35 @@ fn test_ipfix_withdraw_all_options_templates() { let mut parser = NetflowParser::default(); // Register a data template — should NOT be affected - let _ = parser.parse_bytes(&ipfix_template_packet(256, &[(1, 4)])); + assert!( + !parser + .parse_bytes(&ipfix_template_packet(256, &[(1, 4)])) + .packets + .is_empty() + ); // Register 2 options templates - let _ = parser.parse_bytes(&ipfix_options_template_pkt(258)); - let _ = parser.parse_bytes(&ipfix_options_template_pkt(259)); + 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); // Withdraw all options templates (template_id=3, field_count=0) - let _ = parser.parse_bytes(&ipfix_options_withdrawal_packet(3)); + assert!( + !parser + .parse_bytes(&ipfix_options_withdrawal_packet(3)) + .packets + .is_empty() + ); // Options templates should be gone, data template should remain assert!( @@ -322,11 +391,16 @@ fn test_ipfix_withdraw_all_drains_pending_flows() { 0x00, 0x08, // Set Length = 8 0x00, 0x00, 0x00, 0x42, ]; - let _ = parser.parse_bytes(&data_pkt); + let _ = parser.parse_bytes(&data_pkt); // intentional: data without template → pending assert_eq!(parser.ipfix_cache_info().pending_flow_count, 1); // Register the template so it's in the cache - let _ = parser.parse_bytes(&ipfix_template_packet(256, &[(1, 4)])); + assert!( + !parser + .parse_bytes(&ipfix_template_packet(256, &[(1, 4)])) + .packets + .is_empty() + ); // Pending flow was replayed assert_eq!(parser.ipfix_cache_info().pending_flow_count, 0); assert_eq!(parser.ipfix_cache_info().metrics.pending_replayed, 1); @@ -335,12 +409,17 @@ fn test_ipfix_withdraw_all_drains_pending_flows() { let mut data_257 = data_pkt.clone(); data_257[16] = 0x01; data_257[17] = 0x01; // Set ID = 257 - let _ = parser.parse_bytes(&data_257); + let _ = parser.parse_bytes(&data_257); // intentional: data without template → pending assert_eq!(parser.ipfix_cache_info().pending_flow_count, 1); // Withdraw all data templates — should also drain pending flows for 256 // (template 257 pending flow stays because it was never in the template cache) - let _ = parser.parse_bytes(&ipfix_withdrawal_packet(2)); + assert!( + !parser + .parse_bytes(&ipfix_withdrawal_packet(2)) + .packets + .is_empty() + ); assert!(!parser.has_ipfix_template(256)); // The pending flow for 257 should still be there (257 was never a data template) diff --git a/tests/template_hooks.rs b/tests/template_hooks.rs index a210617..9d70f2c 100644 --- a/tests/template_hooks.rs +++ b/tests/template_hooks.rs @@ -270,7 +270,10 @@ fn test_hooks_fire_during_parsing() { 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, ]; - let _ = parser.parse_bytes(&v9_template_packet); + assert!( + !parser.parse_bytes(&v9_template_packet).packets.is_empty(), + "Template packet should parse successfully" + ); let captured = events.lock().unwrap(); assert!(