Skip to content

Commit 89767d1

Browse files
committed
fix: fmt
1 parent c111b1c commit 89767d1

7 files changed

Lines changed: 139 additions & 2 deletions

File tree

RELEASES.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,13 @@
210210
* **`#![forbid(unsafe_code)]` enforced**
211211
- The crate contains zero `unsafe` blocks; this is now enforced at the crate level
212212

213+
* **Cross-variant numeric extraction on `DataNumber` and `FieldValue`**
214+
- `DataNumber::as_u8()`, `as_u16()`, `as_u64()` — try to extract a value from any numeric variant, narrowing or widening as needed (returns `None` if the value doesn't fit)
215+
- `FieldValue::as_u8()` — delegates to `DataNumber` and also converts `ProtocolType` to its numeric value
216+
- `FieldValue::as_u16()` — delegates to `DataNumber`
217+
- `FieldValue::as_u64()` — delegates to `DataNumber` and converts `Duration` variants to milliseconds
218+
- Unlike the existing `TryFrom` impls (which only match the exact variant), these methods work across all numeric widths
219+
213220
* **Expanded root re-exports**
214221
- `Config`, `ConfigError`, `TtlConfig`, `EnterpriseFieldRegistry`, `CacheMetrics`, `NoTemplateInfo`, `DEFAULT_MAX_RECORDS_PER_FLOWSET`, `DEFAULT_MAX_SOURCES` — now available at crate root
215222
- `DataNumber`, `FieldDataType`, `FieldValue` — commonly used field/data types at crate root
@@ -382,6 +389,8 @@
382389
- Split `v9.rs` into `v9/{mod.rs, parser.rs, serializer.rs}`
383390
- Split `ipfix.rs` into `ipfix/{mod.rs, parser.rs, serializer.rs}`
384391
- Renamed `data_number.rs``field_value.rs` (deprecated re-export module preserves backward compatibility)
392+
- Moved `field_types` from crate root to `variable_versions::field_types` (deprecated re-export at `crate::field_types`)
393+
- Moved `template_events` from crate root to `variable_versions::template_events` (deprecated re-export at `crate::template_events`)
385394

386395
* **Code cleanup**
387396
- Removed unused `enterprise_registry` field from `V9Parser` (was `#[allow(dead_code)]`)
@@ -424,6 +433,18 @@
424433
* **`v9_available_template_ids()` and `ipfix_available_template_ids()` now return sorted, deduplicated results**
425434
- Same template ID could previously appear twice (once from templates cache, once from options_templates cache)
426435

436+
* **`clear_v9_pending_flows()` and `clear_ipfix_pending_flows()` now record dropped metrics**
437+
- Previously, clearing pending flows did not update `pending_dropped` counters
438+
- Now records the count of cleared entries via `record_pending_dropped_n()`
439+
440+
* **`Data::with_template_field_lengths()` now validates field count**
441+
- Panics if `template_field_lengths` length doesn't match the field count of the first record
442+
- Prevents silent corrupt serialization from mismatched metadata
443+
444+
* **IPFIX variable-length field serialization rejects zero-length fields**
445+
- `to_be_bytes()` now returns an error for zero-length variable-length fields per RFC 7011 Section 7
446+
- Previously wrote a `0x00` prefix which the parser would reject, breaking round-trip
447+
427448
## Known Limitations
428449

429450
* **IPFIX variable-length field serialization requires `template_field_lengths`**

src/variable_versions/config.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Parser configuration, traits, and constants for V9 and IPFIX parsers.
22
3+
use super::metrics::CacheMetricsInner;
34
use super::pending_flows::{PendingFlowCache, PendingFlowsConfig};
45
use crate::variable_versions::enterprise_registry::EnterpriseFieldRegistry;
56
use crate::variable_versions::ttl::TtlConfig;
@@ -268,6 +269,7 @@ pub(crate) trait ParserFields {
268269
fn set_enterprise_registry(&mut self, _registry: Arc<EnterpriseFieldRegistry>) {}
269270
fn pending_flows(&self) -> &Option<PendingFlowCache>;
270271
fn pending_flows_mut(&mut self) -> &mut Option<PendingFlowCache>;
272+
fn metrics_mut(&mut self) -> &mut CacheMetricsInner;
271273
}
272274

273275
/// Trait for parsers that support template caching and TTL configuration
@@ -360,10 +362,19 @@ pub trait ParserConfig: ParserFields {
360362
.unwrap_or(0)
361363
}
362364

363-
/// Clear all pending flows.
365+
/// Clear all pending flows, recording dropped metrics.
364366
fn clear_pending_flows(&mut self) {
367+
// Count entries before clearing to record metrics.
368+
let count = self
369+
.pending_flows()
370+
.as_ref()
371+
.map(|cache| cache.count())
372+
.unwrap_or(0);
365373
if let Some(cache) = self.pending_flows_mut() {
366374
cache.clear();
367375
}
376+
if count > 0 {
377+
self.metrics_mut().record_pending_dropped_n(count as u64);
378+
}
368379
}
369380
}

src/variable_versions/field_value.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,4 +1230,92 @@ mod field_value_tests {
12301230
FieldValue::Ip6Addr(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1))
12311231
);
12321232
}
1233+
1234+
#[test]
1235+
fn test_data_number_as_u8() {
1236+
assert_eq!(DataNumber::U8(42).as_u8(), Some(42));
1237+
assert_eq!(DataNumber::U16(255).as_u8(), Some(255));
1238+
assert_eq!(DataNumber::U16(256).as_u8(), None);
1239+
assert_eq!(DataNumber::U32(100).as_u8(), Some(100));
1240+
assert_eq!(DataNumber::U64(300).as_u8(), None);
1241+
assert_eq!(DataNumber::I8(-1).as_u8(), None);
1242+
assert_eq!(DataNumber::I8(127).as_u8(), Some(127));
1243+
assert_eq!(DataNumber::I16(-1).as_u8(), None);
1244+
assert_eq!(DataNumber::I32(200).as_u8(), Some(200));
1245+
assert_eq!(DataNumber::U128(255).as_u8(), Some(255));
1246+
assert_eq!(DataNumber::U128(256).as_u8(), None);
1247+
assert_eq!(DataNumber::Vec(vec![1, 2]).as_u8(), None);
1248+
}
1249+
1250+
#[test]
1251+
fn test_data_number_as_u16() {
1252+
assert_eq!(DataNumber::U8(42).as_u16(), Some(42));
1253+
assert_eq!(DataNumber::U16(65535).as_u16(), Some(65535));
1254+
assert_eq!(DataNumber::U32(65536).as_u16(), None);
1255+
assert_eq!(DataNumber::U32(1000).as_u16(), Some(1000));
1256+
assert_eq!(DataNumber::I8(-1).as_u16(), None);
1257+
assert_eq!(DataNumber::I16(32000).as_u16(), Some(32000));
1258+
assert_eq!(DataNumber::I16(-1).as_u16(), None);
1259+
assert_eq!(DataNumber::U64(70000).as_u16(), None);
1260+
assert_eq!(DataNumber::Vec(vec![1]).as_u16(), None);
1261+
}
1262+
1263+
#[test]
1264+
fn test_data_number_as_u64() {
1265+
assert_eq!(DataNumber::U8(42).as_u64(), Some(42));
1266+
assert_eq!(DataNumber::U16(1000).as_u64(), Some(1000));
1267+
assert_eq!(DataNumber::U32(100_000).as_u64(), Some(100_000));
1268+
assert_eq!(DataNumber::U64(u64::MAX).as_u64(), Some(u64::MAX));
1269+
assert_eq!(DataNumber::I8(-1).as_u64(), None);
1270+
assert_eq!(DataNumber::I64(1_000_000).as_u64(), Some(1_000_000));
1271+
assert_eq!(DataNumber::I64(-1).as_u64(), None);
1272+
assert_eq!(DataNumber::U128(u64::MAX as u128).as_u64(), Some(u64::MAX));
1273+
assert_eq!(DataNumber::U128(u64::MAX as u128 + 1).as_u64(), None);
1274+
assert_eq!(DataNumber::Vec(vec![1]).as_u64(), None);
1275+
}
1276+
1277+
#[test]
1278+
fn test_field_value_as_u8() {
1279+
assert_eq!(FieldValue::DataNumber(DataNumber::U8(6)).as_u8(), Some(6));
1280+
assert_eq!(
1281+
FieldValue::ProtocolType(ProtocolTypes::Tcp).as_u8(),
1282+
Some(6)
1283+
);
1284+
assert_eq!(FieldValue::Float64(1.0).as_u8(), None);
1285+
assert_eq!(FieldValue::Ip4Addr(Ipv4Addr::LOCALHOST).as_u8(), None);
1286+
}
1287+
1288+
#[test]
1289+
fn test_field_value_as_u16() {
1290+
assert_eq!(
1291+
FieldValue::DataNumber(DataNumber::U16(80)).as_u16(),
1292+
Some(80)
1293+
);
1294+
assert_eq!(FieldValue::Float64(1.0).as_u16(), None);
1295+
}
1296+
1297+
#[test]
1298+
fn test_field_value_as_u64() {
1299+
assert_eq!(
1300+
FieldValue::DataNumber(DataNumber::U32(1000)).as_u64(),
1301+
Some(1000)
1302+
);
1303+
assert_eq!(
1304+
FieldValue::Duration(DurationValue::Millis {
1305+
value: 5000,
1306+
width: 4
1307+
})
1308+
.as_u64(),
1309+
Some(5000)
1310+
);
1311+
assert_eq!(
1312+
FieldValue::Duration(DurationValue::Seconds {
1313+
value: 10,
1314+
width: 4
1315+
})
1316+
.as_u64(),
1317+
Some(10_000)
1318+
);
1319+
assert_eq!(FieldValue::Float64(1.0).as_u64(), None);
1320+
}
12331321
}

src/variable_versions/ipfix/parser.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ impl ParserFields for IPFixParser {
124124
fn pending_flows_mut(&mut self) -> &mut Option<PendingFlowCache> {
125125
&mut self.pending_flows
126126
}
127+
fn metrics_mut(&mut self) -> &mut CacheMetricsInner {
128+
&mut self.metrics
129+
}
127130
}
128131

129132
impl ParserConfig for IPFixParser {

src/variable_versions/ipfix/serializer.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ fn write_varlen_prefix(
1515
buf: &mut Vec<u8>,
1616
value_len: usize,
1717
) -> Result<(), Box<dyn std::error::Error>> {
18+
if value_len == 0 {
19+
return Err(
20+
"IPFIX variable-length field cannot have zero length (RFC 7011 Section 7)".into(),
21+
);
22+
}
1823
if value_len < 255 {
1924
buf.push(value_len as u8);
2025
} else {

src/variable_versions/pending_flows.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,10 @@ impl PendingFlowCache {
156156
return Err(ConfigError::InvalidEntrySize(config.max_entry_size_bytes));
157157
}
158158
if config.max_total_bytes == 0 {
159-
return Err(ConfigError::InvalidPendingCacheSize(0));
159+
return Err(ConfigError::InvalidPendingTotalBytes {
160+
max_total_bytes: 0,
161+
max_entry_size_bytes: config.max_entry_size_bytes,
162+
});
160163
}
161164
if config.max_total_bytes < config.max_entry_size_bytes {
162165
return Err(ConfigError::InvalidPendingTotalBytes {
@@ -410,6 +413,9 @@ impl PendingFlowCache {
410413
}
411414

412415
/// Clear all pending flows.
416+
///
417+
/// Callers are responsible for recording dropped metrics via
418+
/// `metrics.record_pending_dropped_n(cache.count())` before calling this.
413419
pub(crate) fn clear(&mut self) {
414420
self.cache.clear();
415421
self.total_bytes = 0;

src/variable_versions/v9/parser.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ impl ParserFields for V9Parser {
130130
fn pending_flows_mut(&mut self) -> &mut Option<PendingFlowCache> {
131131
&mut self.pending_flows
132132
}
133+
fn metrics_mut(&mut self) -> &mut CacheMetricsInner {
134+
&mut self.metrics
135+
}
133136
}
134137

135138
impl ParserConfig for V9Parser {

0 commit comments

Comments
 (0)