1use std::collections::HashMap;
28use std::net::SocketAddr;
29use std::sync::RwLock;
30use std::time::Instant;
31
32use bytes::Bytes;
33
34use crate::error::{Error, Result};
35use crate::v3::UsmSecurityParams;
36
37pub const TIME_WINDOW: u32 = 150;
39
40pub const MAX_ENGINE_TIME: u32 = 2147483647;
46
47pub const DEFAULT_MSG_MAX_SIZE: u32 = 65507;
49
50pub mod report_oids {
52 use crate::Oid;
53 use crate::oid;
54
55 pub fn unsupported_sec_levels() -> Oid {
57 oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 1, 0)
58 }
59
60 pub fn not_in_time_windows() -> Oid {
62 oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 2, 0)
63 }
64
65 pub fn unknown_user_names() -> Oid {
67 oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 3, 0)
68 }
69
70 pub fn unknown_engine_ids() -> Oid {
72 oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 4, 0)
73 }
74
75 pub fn wrong_digests() -> Oid {
77 oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 5, 0)
78 }
79
80 pub fn decryption_errors() -> Oid {
82 oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 6, 0)
83 }
84}
85
86#[derive(Debug, Clone)]
88pub struct EngineState {
89 pub engine_id: Bytes,
91 pub engine_boots: u32,
93 pub engine_time: u32,
95 pub synced_at: Instant,
97 pub latest_received_engine_time: u32,
99 pub msg_max_size: u32,
101}
102
103impl EngineState {
104 pub fn new(engine_id: Bytes, engine_boots: u32, engine_time: u32) -> Self {
106 Self {
107 engine_id,
108 engine_boots,
109 engine_time,
110 synced_at: Instant::now(),
111 latest_received_engine_time: engine_time,
112 msg_max_size: DEFAULT_MSG_MAX_SIZE,
113 }
114 }
115
116 pub fn with_msg_max_size(
118 engine_id: Bytes,
119 engine_boots: u32,
120 engine_time: u32,
121 msg_max_size: u32,
122 ) -> Self {
123 Self {
124 engine_id,
125 engine_boots,
126 engine_time,
127 synced_at: Instant::now(),
128 latest_received_engine_time: engine_time,
129 msg_max_size,
130 }
131 }
132
133 pub fn with_msg_max_size_capped(
138 engine_id: Bytes,
139 engine_boots: u32,
140 engine_time: u32,
141 reported_msg_max_size: u32,
142 session_max: u32,
143 ) -> Self {
144 let msg_max_size = if reported_msg_max_size > session_max {
145 tracing::debug!(target: "async_snmp::v3", { reported = reported_msg_max_size, session_max = session_max }, "capping msgMaxSize to session limit");
146 session_max
147 } else {
148 reported_msg_max_size
149 };
150
151 Self {
152 engine_id,
153 engine_boots,
154 engine_time,
155 synced_at: Instant::now(),
156 latest_received_engine_time: engine_time,
157 msg_max_size,
158 }
159 }
160
161 pub fn estimated_time(&self) -> u32 {
166 let elapsed = self.synced_at.elapsed().as_secs() as u32;
167 self.engine_time
168 .saturating_add(elapsed)
169 .min(MAX_ENGINE_TIME)
170 }
171
172 pub fn update_time(&mut self, response_boots: u32, response_time: u32) -> bool {
178 if response_boots > self.engine_boots {
179 self.engine_boots = response_boots;
181 self.engine_time = response_time;
182 self.synced_at = Instant::now();
183 self.latest_received_engine_time = response_time;
184 true
185 } else if response_boots == self.engine_boots
186 && response_time > self.latest_received_engine_time
187 {
188 self.engine_time = response_time;
190 self.synced_at = Instant::now();
191 self.latest_received_engine_time = response_time;
192 true
193 } else {
194 false
195 }
196 }
197
198 pub fn is_in_time_window(&self, msg_boots: u32, msg_time: u32) -> bool {
205 if self.engine_boots == 2147483647 {
207 return false;
208 }
209
210 if msg_boots != self.engine_boots {
212 return false;
213 }
214
215 let local_time = self.estimated_time();
217 let diff = msg_time.abs_diff(local_time);
218
219 diff <= TIME_WINDOW
220 }
221}
222
223#[derive(Debug, Default)]
248pub struct EngineCache {
249 engines: RwLock<HashMap<SocketAddr, EngineState>>,
250}
251
252impl EngineCache {
253 pub fn new() -> Self {
255 Self {
256 engines: RwLock::new(HashMap::new()),
257 }
258 }
259
260 pub fn get(&self, target: &SocketAddr) -> Option<EngineState> {
262 self.engines.read().ok()?.get(target).cloned()
263 }
264
265 pub fn insert(&self, target: SocketAddr, state: EngineState) {
267 if let Ok(mut engines) = self.engines.write() {
268 engines.insert(target, state);
269 }
270 }
271
272 pub fn update_time(
276 &self,
277 target: &SocketAddr,
278 response_boots: u32,
279 response_time: u32,
280 ) -> bool {
281 if let Ok(mut engines) = self.engines.write()
282 && let Some(state) = engines.get_mut(target)
283 {
284 return state.update_time(response_boots, response_time);
285 }
286 false
287 }
288
289 pub fn remove(&self, target: &SocketAddr) -> Option<EngineState> {
291 self.engines.write().ok()?.remove(target)
292 }
293
294 pub fn clear(&self) {
296 if let Ok(mut engines) = self.engines.write() {
297 engines.clear();
298 }
299 }
300
301 pub fn len(&self) -> usize {
303 self.engines.read().map(|e| e.len()).unwrap_or(0)
304 }
305
306 pub fn is_empty(&self) -> bool {
308 self.len() == 0
309 }
310}
311
312impl Clone for EngineCache {
313 fn clone(&self) -> Self {
314 let engines = self.engines.read().map(|e| e.clone()).unwrap_or_default();
316 Self {
317 engines: RwLock::new(engines),
318 }
319 }
320}
321
322pub fn parse_discovery_response(security_params: &Bytes) -> Result<EngineState> {
327 parse_discovery_response_with_limits(
328 security_params,
329 DEFAULT_MSG_MAX_SIZE,
330 DEFAULT_MSG_MAX_SIZE,
331 )
332}
333
334pub fn parse_discovery_response_with_limits(
340 security_params: &Bytes,
341 reported_msg_max_size: u32,
342 session_max: u32,
343) -> Result<EngineState> {
344 let usm = UsmSecurityParams::decode(security_params.clone())?;
345
346 if usm.engine_id.is_empty() {
347 tracing::debug!(target: "async_snmp::engine", "discovery response contained empty engine ID");
348 return Err(Error::MalformedResponse {
349 target: SocketAddr::from(([0, 0, 0, 0], 0)),
350 }
351 .boxed());
352 }
353
354 Ok(EngineState::with_msg_max_size_capped(
355 usm.engine_id,
356 usm.engine_boots,
357 usm.engine_time,
358 reported_msg_max_size,
359 session_max,
360 ))
361}
362
363pub fn is_unknown_engine_id_report(pdu: &crate::pdu::Pdu) -> bool {
367 use crate::pdu::PduType;
368
369 if pdu.pdu_type != PduType::Report {
370 return false;
371 }
372
373 let unknown_engine_ids_oid = report_oids::unknown_engine_ids();
374 pdu.varbinds
375 .iter()
376 .any(|vb| vb.oid == unknown_engine_ids_oid)
377}
378
379pub fn is_not_in_time_window_report(pdu: &crate::pdu::Pdu) -> bool {
383 use crate::pdu::PduType;
384
385 if pdu.pdu_type != PduType::Report {
386 return false;
387 }
388
389 let not_in_time_windows_oid = report_oids::not_in_time_windows();
390 pdu.varbinds
391 .iter()
392 .any(|vb| vb.oid == not_in_time_windows_oid)
393}
394
395pub fn is_wrong_digest_report(pdu: &crate::pdu::Pdu) -> bool {
399 use crate::pdu::PduType;
400
401 if pdu.pdu_type != PduType::Report {
402 return false;
403 }
404
405 let wrong_digests_oid = report_oids::wrong_digests();
406 pdu.varbinds.iter().any(|vb| vb.oid == wrong_digests_oid)
407}
408
409pub fn is_unsupported_sec_level_report(pdu: &crate::pdu::Pdu) -> bool {
413 use crate::pdu::PduType;
414
415 if pdu.pdu_type != PduType::Report {
416 return false;
417 }
418
419 let oid = report_oids::unsupported_sec_levels();
420 pdu.varbinds.iter().any(|vb| vb.oid == oid)
421}
422
423pub fn is_unknown_user_name_report(pdu: &crate::pdu::Pdu) -> bool {
427 use crate::pdu::PduType;
428
429 if pdu.pdu_type != PduType::Report {
430 return false;
431 }
432
433 let oid = report_oids::unknown_user_names();
434 pdu.varbinds.iter().any(|vb| vb.oid == oid)
435}
436
437pub fn is_decryption_error_report(pdu: &crate::pdu::Pdu) -> bool {
441 use crate::pdu::PduType;
442
443 if pdu.pdu_type != PduType::Report {
444 return false;
445 }
446
447 let oid = report_oids::decryption_errors();
448 pdu.varbinds.iter().any(|vb| vb.oid == oid)
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454
455 #[test]
456 fn test_engine_state_estimated_time() {
457 let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
458
459 let estimated = state.estimated_time();
461 assert!(estimated >= 1000);
462 }
463
464 #[test]
465 fn test_engine_state_update_time() {
466 let mut state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
467
468 assert!(state.update_time(1, 1100));
470 assert_eq!(state.latest_received_engine_time, 1100);
471
472 assert!(!state.update_time(1, 1050));
474 assert_eq!(state.latest_received_engine_time, 1100);
475
476 assert!(state.update_time(2, 500));
478 assert_eq!(state.engine_boots, 2);
479 assert_eq!(state.latest_received_engine_time, 500);
480 }
481
482 #[test]
488 fn test_anti_replay_rejects_old_time() {
489 let mut state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
490 state.latest_received_engine_time = 1500; assert!(
495 !state.update_time(1, 1400),
496 "Should reject replay: time 1400 < latest 1500"
497 );
498 assert_eq!(
499 state.latest_received_engine_time, 1500,
500 "Latest should not change"
501 );
502
503 assert!(
505 !state.update_time(1, 1500),
506 "Should reject replay: time 1500 == latest 1500"
507 );
508 assert_eq!(state.latest_received_engine_time, 1500);
509
510 assert!(
512 state.update_time(1, 1501),
513 "Should accept: time 1501 > latest 1500"
514 );
515 assert_eq!(state.latest_received_engine_time, 1501);
516 }
517
518 #[test]
523 fn test_anti_replay_new_boot_cycle_resets() {
524 let mut state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
525 state.latest_received_engine_time = 5000; assert!(
530 state.update_time(2, 100),
531 "New boot cycle should accept even with lower time"
532 );
533 assert_eq!(state.engine_boots, 2);
534 assert_eq!(state.engine_time, 100);
535 assert_eq!(
536 state.latest_received_engine_time, 100,
537 "Latest should reset to new time"
538 );
539
540 assert!(
542 !state.update_time(2, 50),
543 "Should reject older time in same boot cycle"
544 );
545 assert!(state.update_time(2, 150), "Should accept newer time");
546 assert_eq!(state.latest_received_engine_time, 150);
547 }
548
549 #[test]
553 fn test_anti_replay_rejects_old_boot_cycle() {
554 let mut state = EngineState::new(Bytes::from_static(b"engine"), 5, 1000);
555 state.latest_received_engine_time = 1000;
556
557 assert!(
559 !state.update_time(4, 9999),
560 "Should reject old boot cycle even with high time"
561 );
562 assert_eq!(state.engine_boots, 5, "Boots should not change");
563 assert_eq!(
564 state.latest_received_engine_time, 1000,
565 "Latest should not change"
566 );
567
568 assert!(!state.update_time(0, 9999), "Should reject boots=0 replay");
570 }
571
572 #[test]
574 fn test_anti_replay_boundary_values() {
575 let mut state = EngineState::new(Bytes::from_static(b"engine"), 1, 0);
576
577 assert_eq!(state.latest_received_engine_time, 0);
579
580 assert!(state.update_time(1, 1));
582 assert_eq!(state.latest_received_engine_time, 1);
583
584 assert!(!state.update_time(1, 0));
586
587 assert!(state.update_time(1, u32::MAX - 1));
589 assert_eq!(state.latest_received_engine_time, u32::MAX - 1);
590
591 assert!(state.update_time(1, u32::MAX));
593 assert_eq!(state.latest_received_engine_time, u32::MAX);
594
595 assert!(!state.update_time(1, u32::MAX));
597 }
598
599 #[test]
600 fn test_engine_state_time_window() {
601 let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
602
603 assert!(state.is_in_time_window(1, 1000));
605 assert!(state.is_in_time_window(1, 1100)); assert!(state.is_in_time_window(1, 900)); assert!(!state.is_in_time_window(2, 1000));
610 assert!(!state.is_in_time_window(0, 1000));
611
612 assert!(!state.is_in_time_window(1, 2000)); }
615
616 #[test]
621 fn test_time_window_150s_exact_boundary() {
622 let state = EngineState::new(Bytes::from_static(b"engine"), 1, 10000);
624
625 assert!(
630 state.is_in_time_window(1, 10150),
631 "Message at exactly +150s boundary should be in window"
632 );
633
634 assert!(
636 !state.is_in_time_window(1, 10151),
637 "Message at +151s should be outside window"
638 );
639
640 assert!(
642 state.is_in_time_window(1, 9850),
643 "Message at exactly -150s boundary should be in window"
644 );
645
646 assert!(
648 !state.is_in_time_window(1, 9849),
649 "Message at -151s should be outside window"
650 );
651 }
652
653 #[test]
658 fn test_time_window_boots_latched() {
659 let state = EngineState::new(Bytes::from_static(b"engine"), 2147483647, 1000);
662
663 assert!(
665 !state.is_in_time_window(2147483647, 1000),
666 "Latched boots should reject all messages"
667 );
668
669 assert!(!state.is_in_time_window(2147483647, 1100));
671 assert!(!state.is_in_time_window(2147483647, 900));
672 }
673
674 #[test]
678 fn test_time_window_boots_mismatch() {
679 let state = EngineState::new(Bytes::from_static(b"engine"), 100, 1000);
680
681 assert!(!state.is_in_time_window(101, 1000));
683 assert!(!state.is_in_time_window(200, 1000));
684
685 assert!(!state.is_in_time_window(99, 1000));
687 assert!(!state.is_in_time_window(0, 1000));
688 }
689
690 #[test]
691 fn test_engine_cache_basic_operations() {
692 let cache = EngineCache::new();
693 let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
694
695 assert!(cache.is_empty());
697 assert!(cache.get(&addr).is_none());
698
699 let state = EngineState::new(Bytes::from_static(b"engine1"), 1, 1000);
701 cache.insert(addr, state);
702
703 assert_eq!(cache.len(), 1);
704 assert!(!cache.is_empty());
705
706 let retrieved = cache.get(&addr).unwrap();
708 assert_eq!(retrieved.engine_id.as_ref(), b"engine1");
709 assert_eq!(retrieved.engine_boots, 1);
710
711 assert!(cache.update_time(&addr, 1, 1100));
713
714 let removed = cache.remove(&addr).unwrap();
716 assert_eq!(removed.latest_received_engine_time, 1100);
717 assert!(cache.is_empty());
718 }
719
720 #[test]
721 fn test_engine_cache_clone() {
722 let cache1 = EngineCache::new();
723 let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
724
725 cache1.insert(
726 addr,
727 EngineState::new(Bytes::from_static(b"engine1"), 1, 1000),
728 );
729
730 let cache2 = cache1.clone();
732 assert_eq!(cache2.len(), 1);
733 assert!(cache2.get(&addr).is_some());
734
735 cache2.clear();
737 assert_eq!(cache1.len(), 1);
738 assert_eq!(cache2.len(), 0);
739 }
740
741 #[test]
742 fn test_parse_discovery_response() {
743 let usm = UsmSecurityParams::new(b"test-engine-id".as_slice(), 42, 12345, b"".as_slice());
744 let encoded = usm.encode();
745
746 let state = parse_discovery_response(&encoded).unwrap();
747 assert_eq!(state.engine_id.as_ref(), b"test-engine-id");
748 assert_eq!(state.engine_boots, 42);
749 assert_eq!(state.engine_time, 12345);
750 }
751
752 #[test]
753 fn test_parse_discovery_response_empty_engine_id() {
754 let usm = UsmSecurityParams::empty();
755 let encoded = usm.encode();
756
757 let result = parse_discovery_response(&encoded);
758 assert!(matches!(
759 *result.unwrap_err(),
760 Error::MalformedResponse { .. }
761 ));
762 }
763
764 #[test]
765 fn test_is_unknown_engine_id_report() {
766 use crate::Value;
767 use crate::VarBind;
768 use crate::pdu::{Pdu, PduType};
769
770 let mut pdu = Pdu {
772 pdu_type: PduType::Report,
773 request_id: 1,
774 error_status: 0,
775 error_index: 0,
776 varbinds: vec![VarBind {
777 oid: report_oids::unknown_engine_ids(),
778 value: Value::Counter32(1),
779 }],
780 };
781
782 assert!(is_unknown_engine_id_report(&pdu));
783
784 pdu.varbinds[0].oid = report_oids::not_in_time_windows();
786 assert!(!is_unknown_engine_id_report(&pdu));
787
788 pdu.pdu_type = PduType::Response;
790 assert!(!is_unknown_engine_id_report(&pdu));
791 }
792
793 #[test]
802 fn test_engine_boots_transition_to_max() {
803 let mut state = EngineState::new(Bytes::from_static(b"engine"), 2147483646, 1000);
804
805 assert!(
807 state.update_time(2147483647, 100),
808 "Transition to boots=2147483647 should be accepted"
809 );
810 assert_eq!(state.engine_boots, 2147483647);
811 assert_eq!(state.engine_time, 100);
812 }
813
814 #[test]
821 fn test_engine_boots_latched_update_behavior() {
822 let mut state = EngineState::new(Bytes::from_static(b"engine"), 2147483647, 1000);
823
824 assert!(
826 state.update_time(2147483647, 2000),
827 "Time tracking updates should still work"
828 );
829 assert_eq!(state.latest_received_engine_time, 2000);
830
831 assert!(!state.update_time(2147483647, 1500));
833 assert_eq!(state.latest_received_engine_time, 2000);
834
835 assert!(
837 !state.is_in_time_window(2147483647, 2000),
838 "Latched state should still reject all messages"
839 );
840 }
841
842 #[test]
848 fn test_engine_boots_latched_time_window_always_fails() {
849 let state = EngineState::new(Bytes::from_static(b"engine"), 2147483647, 1000);
850
851 assert!(!state.is_in_time_window(2147483647, 0));
853 assert!(!state.is_in_time_window(2147483647, 1000));
854 assert!(!state.is_in_time_window(2147483647, 1001));
855 assert!(!state.is_in_time_window(2147483647, u32::MAX));
856
857 assert!(!state.is_in_time_window(2147483646, 1000));
859 assert!(!state.is_in_time_window(0, 1000));
860 }
861
862 #[test]
867 fn test_engine_state_created_latched() {
868 let state = EngineState::new(Bytes::from_static(b"engine"), 2147483647, 5000);
869
870 assert_eq!(state.engine_boots, 2147483647);
871 assert_eq!(state.engine_time, 5000);
872 assert_eq!(state.latest_received_engine_time, 5000);
873
874 assert!(
876 !state.is_in_time_window(2147483647, 5000),
877 "Newly created latched engine should reject all messages"
878 );
879 }
880
881 #[test]
885 fn test_engine_boots_near_max_operates_normally() {
886 let mut state = EngineState::new(Bytes::from_static(b"engine"), 2147483645, 1000);
887
888 assert!(state.is_in_time_window(2147483645, 1000));
890 assert!(state.is_in_time_window(2147483645, 1100));
891 assert!(!state.is_in_time_window(2147483645, 1200)); assert!(state.update_time(2147483646, 500));
895 assert_eq!(state.engine_boots, 2147483646);
896 assert!(state.is_in_time_window(2147483646, 500));
897
898 assert!(state.update_time(2147483647, 100));
900 assert_eq!(state.engine_boots, 2147483647);
901
902 assert!(!state.is_in_time_window(2147483647, 100));
904 }
905
906 #[test]
909 fn test_engine_boots_high_value_update_logic() {
910 let mut state = EngineState::new(Bytes::from_static(b"engine"), 2147483640, 1000);
911
912 assert!(!state.update_time(2147483639, 9999));
914 assert!(!state.update_time(0, 9999));
915
916 assert!(!state.update_time(2147483640, 500));
918
919 assert!(state.update_time(2147483640, 1500));
921 assert_eq!(state.latest_received_engine_time, 1500);
922
923 assert!(state.update_time(2147483641, 100));
925 assert_eq!(state.engine_boots, 2147483641);
926 }
927
928 #[test]
933 fn test_engine_cache_latched_engine() {
934 let cache = EngineCache::new();
935 let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
936
937 cache.insert(
939 addr,
940 EngineState::new(Bytes::from_static(b"latched"), 2147483647, 1000),
941 );
942
943 assert!(
945 cache.update_time(&addr, 2147483647, 2000),
946 "Time tracking should update even for latched engine"
947 );
948
949 let state = cache.get(&addr).unwrap();
951 assert_eq!(state.latest_received_engine_time, 2000);
952
953 assert!(
955 !state.is_in_time_window(2147483647, 2000),
956 "Latched engine should reject all time window checks"
957 );
958 }
959
960 #[test]
972 fn test_engine_state_stores_msg_max_size() {
973 let state = EngineState::with_msg_max_size(Bytes::from_static(b"engine"), 1, 1000, 65507);
974 assert_eq!(state.msg_max_size, 65507);
975 }
976
977 #[test]
982 fn test_engine_state_default_msg_max_size() {
983 let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
984 assert_eq!(
985 state.msg_max_size, DEFAULT_MSG_MAX_SIZE,
986 "Default msg_max_size should be the maximum UDP datagram size"
987 );
988 }
989
990 #[test]
996 fn test_engine_state_msg_max_size_capped_to_session_max() {
997 let state = EngineState::with_msg_max_size_capped(
999 Bytes::from_static(b"engine"),
1000 1,
1001 1000,
1002 2_000_000_000, 65507, );
1005 assert_eq!(
1006 state.msg_max_size, 65507,
1007 "msg_max_size should be capped to session maximum"
1008 );
1009 }
1010
1011 #[test]
1016 fn test_engine_state_msg_max_size_within_limit_not_capped() {
1017 let state = EngineState::with_msg_max_size_capped(
1018 Bytes::from_static(b"engine"),
1019 1,
1020 1000,
1021 1472, 65507, );
1024 assert_eq!(
1025 state.msg_max_size, 1472,
1026 "msg_max_size within limit should not be capped"
1027 );
1028 }
1029
1030 #[test]
1034 fn test_engine_state_msg_max_size_at_exact_boundary() {
1035 let state = EngineState::with_msg_max_size_capped(
1036 Bytes::from_static(b"engine"),
1037 1,
1038 1000,
1039 65507, 65507, );
1042 assert_eq!(state.msg_max_size, 65507);
1043 }
1044
1045 #[test]
1050 fn test_engine_state_msg_max_size_tcp_limit() {
1051 const TCP_MAX: u32 = 0x7FFFFFFF; let state = EngineState::with_msg_max_size_capped(
1055 Bytes::from_static(b"engine"),
1056 1,
1057 1000,
1058 TCP_MAX,
1059 TCP_MAX,
1060 );
1061 assert_eq!(state.msg_max_size, TCP_MAX);
1062
1063 let state = EngineState::with_msg_max_size_capped(
1065 Bytes::from_static(b"engine"),
1066 1,
1067 1000,
1068 u32::MAX, TCP_MAX,
1070 );
1071 assert_eq!(
1072 state.msg_max_size, TCP_MAX,
1073 "Values exceeding session max should be capped"
1074 );
1075 }
1076
1077 #[test]
1079 fn test_engine_state_new_uses_default_constant() {
1080 let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
1081
1082 assert_eq!(state.msg_max_size, DEFAULT_MSG_MAX_SIZE);
1084 }
1085
1086 #[test]
1099 fn test_estimated_time_caps_at_max_engine_time() {
1100 let state = EngineState::new(Bytes::from_static(b"engine"), 1, MAX_ENGINE_TIME - 10);
1102
1103 let estimated = state.estimated_time();
1105 assert!(
1106 estimated <= MAX_ENGINE_TIME,
1107 "estimated_time() should never exceed MAX_ENGINE_TIME ({}), got {}",
1108 MAX_ENGINE_TIME,
1109 estimated
1110 );
1111 }
1112
1113 #[test]
1118 fn test_estimated_time_at_max_stays_at_max() {
1119 let state = EngineState::new(Bytes::from_static(b"engine"), 1, MAX_ENGINE_TIME);
1120
1121 let estimated = state.estimated_time();
1123 assert_eq!(
1124 estimated, MAX_ENGINE_TIME,
1125 "estimated_time() at max should stay at MAX_ENGINE_TIME"
1126 );
1127 }
1128
1129 #[test]
1133 fn test_max_engine_time_constant() {
1134 assert_eq!(MAX_ENGINE_TIME, 2147483647);
1136 assert_eq!(MAX_ENGINE_TIME, i32::MAX as u32);
1137 }
1138
1139 #[test]
1144 fn test_estimated_time_normal_operation() {
1145 let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
1146
1147 let estimated = state.estimated_time();
1149 assert!(
1150 estimated >= 1000,
1151 "estimated_time() should be at least engine_time"
1152 );
1153 assert!(
1155 estimated < MAX_ENGINE_TIME,
1156 "Normal time values should not hit MAX_ENGINE_TIME cap"
1157 );
1158 }
1159}