1mod lru;
11
12pub use lru::LruParseCache;
13
14use std::collections::HashMap;
15use std::sync::Arc;
16
17use crate::protocol::{FieldValue, OwnedFieldValue, TunnelType};
18
19#[derive(Clone, Debug)]
24pub struct OwnedParseResult {
25 pub fields: HashMap<&'static str, OwnedFieldValue>,
28 pub error: Option<String>,
30 pub encap_depth: u8,
32 pub tunnel_type: TunnelType,
34 pub tunnel_id: Option<u64>,
36}
37
38impl OwnedParseResult {
39 pub fn from_borrowed(
42 fields: &smallvec::SmallVec<[(&'static str, FieldValue<'_>); 16]>,
43 error: Option<&String>,
44 ) -> Self {
45 Self {
46 fields: fields.iter().map(|(k, v)| (*k, v.to_owned())).collect(),
47 error: error.cloned(),
48 encap_depth: 0,
49 tunnel_type: TunnelType::None,
50 tunnel_id: None,
51 }
52 }
53
54 pub fn from_parse_result(result: &crate::protocol::ParseResult<'_>) -> Self {
56 Self {
57 fields: result
58 .fields
59 .iter()
60 .map(|(k, v)| (*k, v.to_owned()))
61 .collect(),
62 error: result.error.clone(),
63 encap_depth: result.encap_depth,
64 tunnel_type: result.tunnel_type,
65 tunnel_id: result.tunnel_id,
66 }
67 }
68
69 pub fn get(&self, name: &str) -> Option<&OwnedFieldValue> {
71 self.fields.get(name)
72 }
73}
74
75#[derive(Clone, Debug)]
80pub struct CachedParse {
81 pub frame_number: u64,
83 pub protocols: Vec<(&'static str, OwnedParseResult)>,
86}
87
88impl CachedParse {
89 pub fn from_parse_results(
91 frame_number: u64,
92 results: &[(&'static str, crate::protocol::ParseResult<'_>)],
93 ) -> Self {
94 let protocols = results
95 .iter()
96 .map(|(name, result)| (*name, OwnedParseResult::from_parse_result(result)))
97 .collect();
98
99 Self {
100 frame_number,
101 protocols,
102 }
103 }
104
105 pub fn get_protocol(&self, name: &str) -> Option<&OwnedParseResult> {
111 self.protocols
112 .iter()
113 .find(|(n, _)| *n == name)
114 .map(|(_, r)| r)
115 }
116
117 pub fn get_all_protocols<'a>(
122 &'a self,
123 name: &'a str,
124 ) -> impl Iterator<Item = &'a OwnedParseResult> + 'a {
125 self.protocols
126 .iter()
127 .filter(move |(n, _)| *n == name)
128 .map(|(_, r)| r)
129 }
130
131 pub fn count_protocol(&self, name: &str) -> usize {
135 self.protocols.iter().filter(|(n, _)| *n == name).count()
136 }
137
138 pub fn has_protocol(&self, name: &str) -> bool {
140 self.protocols.iter().any(|(n, _)| *n == name)
141 }
142
143 pub fn iter(&self) -> impl Iterator<Item = (&'static str, &OwnedParseResult)> {
145 self.protocols.iter().map(|(n, r)| (*n, r))
146 }
147}
148
149pub trait ParseCache: Send + Sync {
154 fn get(&self, frame_number: u64) -> Option<Arc<CachedParse>>;
156
157 fn put(&self, frame_number: u64, parsed: Arc<CachedParse>);
159
160 fn get_or_insert_with(
162 &self,
163 frame_number: u64,
164 f: Box<dyn FnOnce() -> Arc<CachedParse> + '_>,
165 ) -> (Arc<CachedParse>, bool) {
166 if let Some(cached) = self.get(frame_number) {
168 return (cached, true);
169 }
170 let result = f();
171 self.put(frame_number, result.clone());
172 (result, false)
173 }
174
175 fn reader_passed(&self, reader_id: usize, frame_number: u64);
180
181 fn register_reader(&self) -> usize;
183
184 fn unregister_reader(&self, reader_id: usize);
186
187 fn stats(&self) -> Option<CacheStats> {
189 None
190 }
191
192 fn reset_stats(&self) {}
194}
195
196#[derive(Clone, Debug, Default)]
201pub struct NoCache;
202
203impl ParseCache for NoCache {
204 fn get(&self, _frame_number: u64) -> Option<Arc<CachedParse>> {
205 None
206 }
207
208 fn put(&self, _frame_number: u64, _parsed: Arc<CachedParse>) {
209 }
211
212 fn reader_passed(&self, _reader_id: usize, _frame_number: u64) {
213 }
215
216 fn register_reader(&self) -> usize {
217 0
218 }
219
220 fn unregister_reader(&self, _reader_id: usize) {
221 }
223}
224
225#[derive(Clone, Debug, Default)]
227pub struct CacheStats {
228 pub hits: u64,
230 pub misses: u64,
232 pub entries: usize,
234 pub max_entries: usize,
236
237 pub evictions_lru: u64,
239 pub evictions_reader: u64,
241 pub peak_entries: usize,
243 pub active_readers: usize,
245 pub memory_bytes_estimate: usize,
247}
248
249impl CacheStats {
250 pub fn hit_ratio(&self) -> f64 {
252 let total = self.hits + self.misses;
253 if total == 0 {
254 0.0
255 } else {
256 self.hits as f64 / total as f64
257 }
258 }
259
260 pub fn utilization(&self) -> f64 {
262 if self.max_entries == 0 {
263 0.0
264 } else {
265 self.entries as f64 / self.max_entries as f64
266 }
267 }
268
269 pub fn total_evictions(&self) -> u64 {
271 self.evictions_lru + self.evictions_reader
272 }
273
274 pub fn format_summary(&self) -> String {
276 let hit_pct = self.hit_ratio() * 100.0;
277 let miss_pct = 100.0 - hit_pct;
278 let util_pct = self.utilization() * 100.0;
279
280 format!(
281 "Cache Statistics:\n\
282 \x20 Hits: {:>10} ({:.1}%)\n\
283 \x20 Misses: {:>10} ({:.1}%)\n\
284 \x20 Entries: {:>10} / {} ({:.1}%)\n\
285 \x20 Peak: {:>10}\n\
286 \x20 Evictions: {:>10} (LRU: {}, Reader: {})\n\
287 \x20 Readers: {:>10}\n\
288 \x20 Memory: {:>10}",
289 self.hits,
290 hit_pct,
291 self.misses,
292 miss_pct,
293 self.entries,
294 self.max_entries,
295 util_pct,
296 self.peak_entries,
297 self.total_evictions(),
298 self.evictions_lru,
299 self.evictions_reader,
300 self.active_readers,
301 format_bytes(self.memory_bytes_estimate),
302 )
303 }
304}
305
306fn format_bytes(bytes: usize) -> String {
308 const KB: usize = 1024;
309 const MB: usize = KB * 1024;
310 const GB: usize = MB * 1024;
311
312 if bytes >= GB {
313 format!("{:.2} GB", bytes as f64 / GB as f64)
314 } else if bytes >= MB {
315 format!("{:.2} MB", bytes as f64 / MB as f64)
316 } else if bytes >= KB {
317 format!("{:.2} KB", bytes as f64 / KB as f64)
318 } else {
319 format!("{bytes} B")
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 fn test_no_cache() {
329 let cache = NoCache;
330
331 assert!(cache.get(1).is_none());
332
333 let parsed = Arc::new(CachedParse {
334 frame_number: 1,
335 protocols: vec![],
336 });
337 cache.put(1, parsed);
338
339 assert!(cache.get(1).is_none());
341 }
342
343 #[test]
344 fn test_cached_parse_has_protocol() {
345 let cached = CachedParse {
346 frame_number: 1,
347 protocols: vec![
348 (
349 "ethernet",
350 OwnedParseResult {
351 fields: HashMap::new(),
352 error: None,
353 encap_depth: 0,
354 tunnel_type: TunnelType::None,
355 tunnel_id: None,
356 },
357 ),
358 (
359 "ipv4",
360 OwnedParseResult {
361 fields: HashMap::new(),
362 error: None,
363 encap_depth: 0,
364 tunnel_type: TunnelType::None,
365 tunnel_id: None,
366 },
367 ),
368 ],
369 };
370
371 assert!(cached.has_protocol("ethernet"));
372 assert!(cached.has_protocol("ipv4"));
373 assert!(!cached.has_protocol("tcp"));
374 }
375
376 #[test]
377 fn test_cached_parse_get_all_protocols() {
378 let cached = CachedParse {
380 frame_number: 1,
381 protocols: vec![
382 (
383 "ethernet",
384 OwnedParseResult {
385 fields: HashMap::new(),
386 error: None,
387 encap_depth: 0,
388 tunnel_type: TunnelType::None,
389 tunnel_id: None,
390 },
391 ),
392 (
393 "ipv4",
394 OwnedParseResult {
395 fields: {
396 let mut f = HashMap::new();
397 f.insert(
398 "src_ip",
399 OwnedFieldValue::OwnedString(compact_str::CompactString::new(
400 "10.0.0.1",
401 )),
402 );
403 f
404 },
405 error: None,
406 encap_depth: 0,
407 tunnel_type: TunnelType::None,
408 tunnel_id: None,
409 },
410 ),
411 (
412 "vxlan",
413 OwnedParseResult {
414 fields: {
415 let mut f = HashMap::new();
416 f.insert("vni", OwnedFieldValue::UInt32(100));
417 f
418 },
419 error: None,
420 encap_depth: 0,
421 tunnel_type: TunnelType::None,
422 tunnel_id: None,
423 },
424 ),
425 (
426 "ethernet",
427 OwnedParseResult {
428 fields: HashMap::new(),
429 error: None,
430 encap_depth: 1,
431 tunnel_type: TunnelType::Vxlan,
432 tunnel_id: Some(100),
433 },
434 ),
435 (
436 "ipv4",
437 OwnedParseResult {
438 fields: {
439 let mut f = HashMap::new();
440 f.insert(
441 "src_ip",
442 OwnedFieldValue::OwnedString(compact_str::CompactString::new(
443 "192.168.1.1",
444 )),
445 );
446 f
447 },
448 error: None,
449 encap_depth: 1,
450 tunnel_type: TunnelType::Vxlan,
451 tunnel_id: Some(100),
452 },
453 ),
454 ],
455 };
456
457 let first_ipv4 = cached.get_protocol("ipv4").unwrap();
459 assert_eq!(first_ipv4.encap_depth, 0);
460
461 let all_ipv4: Vec<_> = cached.get_all_protocols("ipv4").collect();
463 assert_eq!(all_ipv4.len(), 2);
464 assert_eq!(all_ipv4[0].encap_depth, 0);
465 assert_eq!(all_ipv4[1].encap_depth, 1);
466 assert_eq!(all_ipv4[1].tunnel_type, TunnelType::Vxlan);
467 assert_eq!(all_ipv4[1].tunnel_id, Some(100));
468
469 assert_eq!(cached.count_protocol("ethernet"), 2);
471 assert_eq!(cached.count_protocol("ipv4"), 2);
472 assert_eq!(cached.count_protocol("vxlan"), 1);
473 assert_eq!(cached.count_protocol("tcp"), 0);
474 }
475
476 #[test]
477 fn test_cache_stats_hit_ratio() {
478 let stats = CacheStats {
479 hits: 75,
480 misses: 25,
481 entries: 100,
482 max_entries: 1000,
483 ..Default::default()
484 };
485
486 assert!((stats.hit_ratio() - 0.75).abs() < 0.001);
487 }
488
489 #[test]
490 fn test_cache_stats_empty() {
491 let stats = CacheStats::default();
492 assert_eq!(stats.hit_ratio(), 0.0);
493 }
494
495 #[test]
496 fn test_owned_parse_result_with_fields() {
497 use std::net::{IpAddr, Ipv4Addr};
498
499 let mut fields = HashMap::new();
500 fields.insert(
501 "src_ip",
502 OwnedFieldValue::IpAddr(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))),
503 );
504 fields.insert("dst_port", OwnedFieldValue::UInt16(443));
505 fields.insert(
506 "payload",
507 OwnedFieldValue::OwnedBytes(vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]),
508 );
509 fields.insert("flags", OwnedFieldValue::UInt8(0x18));
510 fields.insert("is_syn", OwnedFieldValue::Bool(false));
511
512 let result = OwnedParseResult {
513 fields,
514 error: None,
515 encap_depth: 0,
516 tunnel_type: TunnelType::None,
517 tunnel_id: None,
518 };
519
520 assert!(result.get("src_ip").is_some());
522 assert!(result.get("dst_port").is_some());
523 assert!(result.get("nonexistent").is_none());
524
525 match result.get("dst_port") {
527 Some(OwnedFieldValue::UInt16(port)) => assert_eq!(*port, 443),
528 _ => panic!("Expected UInt16 for dst_port"),
529 }
530 }
531
532 #[test]
533 fn test_owned_parse_result_with_error() {
534 let mut fields = HashMap::new();
535 fields.insert("partial_field", OwnedFieldValue::UInt32(42));
536
537 let result = OwnedParseResult {
538 fields,
539 error: Some("Truncated packet: expected 20 bytes, got 12".to_string()),
540 encap_depth: 0,
541 tunnel_type: TunnelType::None,
542 tunnel_id: None,
543 };
544
545 assert!(result.error.is_some());
546 assert!(result.error.as_ref().unwrap().contains("Truncated"));
547 assert!(result.get("partial_field").is_some());
549 }
550
551 #[test]
552 fn test_owned_parse_result_from_borrowed() {
553 use crate::protocol::FieldValue;
554 use smallvec::SmallVec;
555
556 let mut borrowed_fields: SmallVec<[(&'static str, FieldValue); 16]> = SmallVec::new();
557 borrowed_fields.push((
558 "src_mac",
559 FieldValue::MacAddr([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]),
560 ));
561 borrowed_fields.push(("ethertype", FieldValue::UInt16(0x0800)));
562
563 let error_msg = "Some error".to_string();
564 let owned = OwnedParseResult::from_borrowed(&borrowed_fields, Some(&error_msg));
565
566 assert!(owned.get("src_mac").is_some());
568 assert!(owned.get("ethertype").is_some());
569 assert_eq!(owned.error, Some("Some error".to_string()));
570 }
571
572 #[test]
573 fn test_cached_parse_get_protocol() {
574 let cached = CachedParse {
575 frame_number: 42,
576 protocols: vec![
577 (
578 "ethernet",
579 OwnedParseResult {
580 fields: {
581 let mut f = HashMap::new();
582 f.insert("src_mac", OwnedFieldValue::MacAddr([0x00; 6]));
583 f
584 },
585 error: None,
586 encap_depth: 0,
587 tunnel_type: TunnelType::None,
588 tunnel_id: None,
589 },
590 ),
591 (
592 "ipv4",
593 OwnedParseResult {
594 fields: {
595 let mut f = HashMap::new();
596 f.insert("ttl", OwnedFieldValue::UInt8(64));
597 f
598 },
599 error: None,
600 encap_depth: 0,
601 tunnel_type: TunnelType::None,
602 tunnel_id: None,
603 },
604 ),
605 ],
606 };
607
608 let eth = cached.get_protocol("ethernet");
610 assert!(eth.is_some());
611 assert!(eth.unwrap().get("src_mac").is_some());
612
613 let ipv4 = cached.get_protocol("ipv4");
614 assert!(ipv4.is_some());
615 match ipv4.unwrap().get("ttl") {
616 Some(OwnedFieldValue::UInt8(ttl)) => assert_eq!(*ttl, 64),
617 _ => panic!("Expected TTL field"),
618 }
619
620 assert!(cached.get_protocol("tcp").is_none());
622 }
623
624 #[test]
625 fn test_cached_parse_iter() {
626 let cached = CachedParse {
627 frame_number: 1,
628 protocols: vec![
629 (
630 "ethernet",
631 OwnedParseResult {
632 fields: HashMap::new(),
633 error: None,
634 encap_depth: 0,
635 tunnel_type: TunnelType::None,
636 tunnel_id: None,
637 },
638 ),
639 (
640 "ipv4",
641 OwnedParseResult {
642 fields: HashMap::new(),
643 error: None,
644 encap_depth: 0,
645 tunnel_type: TunnelType::None,
646 tunnel_id: None,
647 },
648 ),
649 (
650 "tcp",
651 OwnedParseResult {
652 fields: HashMap::new(),
653 error: None,
654 encap_depth: 0,
655 tunnel_type: TunnelType::None,
656 tunnel_id: None,
657 },
658 ),
659 ],
660 };
661
662 let protocol_names: Vec<&str> = cached.iter().map(|(name, _)| name).collect();
663 assert_eq!(protocol_names, vec!["ethernet", "ipv4", "tcp"]);
664 }
665
666 #[test]
667 fn test_cached_parse_empty_protocols() {
668 let cached = CachedParse {
669 frame_number: 100,
670 protocols: vec![],
671 };
672
673 assert!(!cached.has_protocol("ethernet"));
674 assert!(cached.get_protocol("anything").is_none());
675 assert_eq!(cached.iter().count(), 0);
676 }
677
678 #[test]
679 fn test_no_cache_register_reader() {
680 let cache = NoCache;
681
682 let r1 = cache.register_reader();
684 let r2 = cache.register_reader();
685 assert_eq!(r1, 0);
686 assert_eq!(r2, 0);
687
688 cache.unregister_reader(r1);
690 cache.reader_passed(r1, 100);
691
692 assert!(cache.stats().is_none());
694 }
695
696 #[test]
697 fn test_cache_stats_various_ratios() {
698 let stats_50 = CacheStats {
700 hits: 50,
701 misses: 50,
702 entries: 100,
703 max_entries: 1000,
704 ..Default::default()
705 };
706 assert!((stats_50.hit_ratio() - 0.5).abs() < 0.001);
707
708 let stats_100 = CacheStats {
710 hits: 100,
711 misses: 0,
712 entries: 100,
713 max_entries: 1000,
714 ..Default::default()
715 };
716 assert!((stats_100.hit_ratio() - 1.0).abs() < 0.001);
717
718 let stats_0 = CacheStats {
720 hits: 0,
721 misses: 100,
722 entries: 0,
723 max_entries: 1000,
724 ..Default::default()
725 };
726 assert!((stats_0.hit_ratio() - 0.0).abs() < 0.001);
727 }
728
729 #[test]
730 fn test_cache_stats_utilization() {
731 let stats_0 = CacheStats {
733 entries: 0,
734 max_entries: 100,
735 ..Default::default()
736 };
737 assert!((stats_0.utilization() - 0.0).abs() < 0.001);
738
739 let stats_50 = CacheStats {
741 entries: 50,
742 max_entries: 100,
743 ..Default::default()
744 };
745 assert!((stats_50.utilization() - 0.5).abs() < 0.001);
746
747 let stats_100 = CacheStats {
749 entries: 100,
750 max_entries: 100,
751 ..Default::default()
752 };
753 assert!((stats_100.utilization() - 1.0).abs() < 0.001);
754
755 let stats_zero_max = CacheStats {
757 entries: 0,
758 max_entries: 0,
759 ..Default::default()
760 };
761 assert!((stats_zero_max.utilization() - 0.0).abs() < 0.001);
762 }
763
764 #[test]
765 fn test_cache_stats_total_evictions() {
766 let stats = CacheStats {
767 evictions_lru: 50,
768 evictions_reader: 30,
769 ..Default::default()
770 };
771 assert_eq!(stats.total_evictions(), 80);
772 }
773
774 #[test]
775 fn test_cache_stats_format_summary() {
776 let stats = CacheStats {
777 hits: 75,
778 misses: 25,
779 entries: 500,
780 max_entries: 1000,
781 evictions_lru: 100,
782 evictions_reader: 50,
783 peak_entries: 750,
784 active_readers: 3,
785 memory_bytes_estimate: 512000, };
787
788 let summary = stats.format_summary();
789
790 assert!(summary.contains("Hits:"));
792 assert!(summary.contains("75"));
793 assert!(summary.contains("Misses:"));
794 assert!(summary.contains("25"));
795 assert!(summary.contains("Entries:"));
796 assert!(summary.contains("500"));
797 assert!(summary.contains("1000"));
798 assert!(summary.contains("Peak:"));
799 assert!(summary.contains("750"));
800 assert!(summary.contains("Evictions:"));
801 assert!(summary.contains("150")); assert!(summary.contains("LRU: 100"));
803 assert!(summary.contains("Reader: 50"));
804 assert!(summary.contains("Readers:"));
805 assert!(summary.contains("3"));
806 assert!(summary.contains("Memory:"));
807 assert!(summary.contains("KB"));
808 }
809
810 #[test]
811 fn test_format_bytes() {
812 assert_eq!(format_bytes(0), "0 B");
814 assert_eq!(format_bytes(500), "500 B");
815 assert_eq!(format_bytes(1023), "1023 B");
816
817 assert_eq!(format_bytes(1024), "1.00 KB");
819 assert_eq!(format_bytes(1536), "1.50 KB"); assert_eq!(format_bytes(10240), "10.00 KB");
821
822 assert_eq!(format_bytes(1048576), "1.00 MB"); assert_eq!(format_bytes(5242880), "5.00 MB"); assert_eq!(format_bytes(10485760), "10.00 MB"); assert_eq!(format_bytes(1073741824), "1.00 GB"); assert_eq!(format_bytes(2147483648), "2.00 GB"); }
831}