1use std::collections::HashMap;
29use std::net::SocketAddr;
30use std::sync::RwLock;
31use std::time::{Duration, Instant};
32
33use bytes::Bytes;
34
35use crate::error::{Error, Result};
36use crate::v3::UsmSecurityParams;
37
38pub const TIME_WINDOW: u32 = 150;
40
41pub const MAX_ENGINE_TIME: u32 = 2_147_483_647;
47
48pub const DEFAULT_MSG_MAX_SIZE: u32 = 65507;
50
51#[must_use]
59pub fn compute_engine_boots_time(boots_base: u32, total_elapsed_secs: u64) -> (u32, u32) {
60 let cycle = u64::from(MAX_ENGINE_TIME) + 1;
61 let additional_boots = total_elapsed_secs / cycle;
62 let current_time = (total_elapsed_secs % cycle) as u32;
63 let boots = (u64::from(boots_base) + additional_boots).min(u64::from(MAX_ENGINE_TIME)) as u32;
64 (boots, current_time)
65}
66
67pub const MIN_ENGINE_ID_LEN: usize = 5;
69
70pub const MAX_ENGINE_ID_LEN: usize = 32;
72
73const GENERATED_ENGINE_ID_PEN: u32 = 32473;
79
80const ENGINE_ID_FORMAT_OCTETS: u8 = 5;
83
84const GENERATED_ENGINE_ID_RANDOM_LEN: usize = 12;
86
87#[must_use]
96pub fn generate_engine_id() -> Bytes {
97 let mut id = Vec::with_capacity(5 + GENERATED_ENGINE_ID_RANDOM_LEN);
98 let enterprise = 0x8000_0000_u32 | GENERATED_ENGINE_ID_PEN;
100 id.extend_from_slice(&enterprise.to_be_bytes());
101 id.push(ENGINE_ID_FORMAT_OCTETS);
102 let mut random = [0_u8; GENERATED_ENGINE_ID_RANDOM_LEN];
103 getrandom::fill(&mut random).expect("getrandom failed");
104 id.extend_from_slice(&random);
105 Bytes::from(id)
106}
107
108pub fn validate_engine_id(engine_id: &[u8]) -> Result<()> {
114 let len = engine_id.len();
115 if !(MIN_ENGINE_ID_LEN..=MAX_ENGINE_ID_LEN).contains(&len) {
116 return Err(Error::Config(
117 format!(
118 "engine ID length {len} out of range (must be {MIN_ENGINE_ID_LEN}..={MAX_ENGINE_ID_LEN} octets)"
119 )
120 .into(),
121 )
122 .boxed());
123 }
124 if engine_id.iter().all(|&b| b == 0x00) {
125 return Err(Error::Config("engine ID must not be all zero".into()).boxed());
126 }
127 if engine_id.iter().all(|&b| b == 0xff) {
128 return Err(Error::Config("engine ID must not be all 0xff".into()).boxed());
129 }
130 Ok(())
131}
132
133pub mod report_oids {
135 use crate::Oid;
136 use crate::oid;
137
138 #[must_use]
140 pub fn unsupported_sec_levels() -> Oid {
141 oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 1, 0)
142 }
143
144 #[must_use]
146 pub fn not_in_time_windows() -> Oid {
147 oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 2, 0)
148 }
149
150 #[must_use]
152 pub fn unknown_user_names() -> Oid {
153 oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 3, 0)
154 }
155
156 #[must_use]
158 pub fn unknown_engine_ids() -> Oid {
159 oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 4, 0)
160 }
161
162 #[must_use]
164 pub fn wrong_digests() -> Oid {
165 oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 5, 0)
166 }
167
168 #[must_use]
170 pub fn decryption_errors() -> Oid {
171 oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 6, 0)
172 }
173}
174
175#[derive(Debug, Clone)]
180pub struct TrustedEngineTime {
181 boots: u32,
182 received_time_base: u32,
183 received_at: Instant,
184 latest_received_time: u32,
185}
186
187impl TrustedEngineTime {
188 fn new_at(boots: u32, time: u32, now: Instant) -> Self {
189 Self {
190 boots,
191 received_time_base: time,
192 received_at: now,
193 latest_received_time: time,
194 }
195 }
196
197 #[must_use]
199 pub fn boots(&self) -> u32 {
200 self.boots
201 }
202
203 #[must_use]
205 pub fn received_time_base(&self) -> u32 {
206 self.received_time_base
207 }
208
209 #[must_use]
211 pub fn latest_received_time(&self) -> u32 {
212 self.latest_received_time
213 }
214
215 fn estimated_at(&self, now: Instant) -> (u32, u32) {
216 if self.boots == MAX_ENGINE_TIME {
217 return (
218 MAX_ENGINE_TIME,
219 self.received_time_base.min(MAX_ENGINE_TIME),
220 );
221 }
222
223 let elapsed = now
224 .checked_duration_since(self.received_at)
225 .unwrap_or_default()
226 .as_secs();
227 let total_time = u64::from(self.received_time_base).saturating_add(elapsed);
228 let cycle = u64::from(MAX_ENGINE_TIME) + 1;
229 let additional_boots = total_time / cycle;
230 let engine_time = (total_time % cycle) as u32;
231 let engine_boots =
232 (u64::from(self.boots) + additional_boots).min(u64::from(MAX_ENGINE_TIME)) as u32;
233 (engine_boots, engine_time)
234 }
235
236 fn roll_forward_at(&mut self, now: Instant) {
237 let (estimated_boots, estimated_time) = self.estimated_at(now);
238 if estimated_boots > self.boots {
239 self.boots = estimated_boots;
240 self.received_time_base = estimated_time;
241 self.received_at = now;
242 self.latest_received_time = estimated_time;
243 }
244 }
245
246 fn update_at(&mut self, response_boots: u32, response_time: u32, now: Instant) -> bool {
247 self.roll_forward_at(now);
248 if response_boots > self.boots
249 || (response_boots == self.boots && response_time > self.latest_received_time)
250 {
251 self.boots = response_boots;
252 self.received_time_base = response_time;
253 self.received_at = now;
254 self.latest_received_time = response_time;
255 true
256 } else {
257 false
258 }
259 }
260}
261
262#[derive(Debug, Clone)]
265pub struct EngineState {
266 pub(crate) engine_id: Bytes,
268 pub msg_max_size: u32,
270 trusted_time: Option<TrustedEngineTime>,
271}
272
273impl EngineState {
274 pub fn new(engine_id: Bytes, engine_boots: u32, engine_time: u32) -> Self {
276 Self::with_msg_max_size(engine_id, engine_boots, engine_time, DEFAULT_MSG_MAX_SIZE)
277 }
278
279 #[must_use]
281 pub fn discovered(engine_id: Bytes, msg_max_size: u32) -> Self {
282 Self {
283 engine_id,
284 msg_max_size,
285 trusted_time: None,
286 }
287 }
288
289 pub fn with_msg_max_size(
291 engine_id: Bytes,
292 engine_boots: u32,
293 engine_time: u32,
294 msg_max_size: u32,
295 ) -> Self {
296 Self {
297 engine_id,
298 msg_max_size,
299 trusted_time: Some(TrustedEngineTime::new_at(
300 engine_boots,
301 engine_time,
302 Instant::now(),
303 )),
304 }
305 }
306
307 pub fn with_msg_max_size_capped(
309 engine_id: Bytes,
310 engine_boots: u32,
311 engine_time: u32,
312 reported_msg_max_size: u32,
313 session_max: u32,
314 ) -> Self {
315 Self::with_msg_max_size(
316 engine_id,
317 engine_boots,
318 engine_time,
319 cap_msg_max_size(reported_msg_max_size, session_max),
320 )
321 }
322
323 #[must_use]
325 pub fn engine_id(&self) -> &Bytes {
326 &self.engine_id
327 }
328
329 #[must_use]
331 pub fn trusted_time(&self) -> Option<&TrustedEngineTime> {
332 self.trusted_time.as_ref()
333 }
334
335 #[must_use]
338 pub fn estimated_boots_time(&self) -> (u32, u32) {
339 self.estimated_boots_time_at(Instant::now())
340 }
341
342 pub(crate) fn estimated_boots_time_at(&self, now: Instant) -> (u32, u32) {
343 self.trusted_time
344 .as_ref()
345 .map_or((0, 0), |time| time.estimated_at(now))
346 }
347
348 pub(crate) fn last_trusted_update_at(&self) -> Option<Instant> {
349 self.trusted_time.as_ref().map(|time| time.received_at)
350 }
351
352 #[must_use]
354 pub fn estimated_time(&self) -> u32 {
355 self.estimated_boots_time().1
356 }
357
358 pub fn update_time(&mut self, response_boots: u32, response_time: u32) -> bool {
362 self.update_time_at(response_boots, response_time, Instant::now())
363 }
364
365 fn update_time_at(&mut self, response_boots: u32, response_time: u32, now: Instant) -> bool {
366 match self.trusted_time.as_mut() {
367 Some(time) => time.update_at(response_boots, response_time, now),
368 None => {
369 self.trusted_time = Some(TrustedEngineTime::new_at(
370 response_boots,
371 response_time,
372 now,
373 ));
374 true
375 }
376 }
377 }
378
379 pub(crate) fn merge_from(&mut self, other: &Self) -> bool {
381 if self.engine_id != other.engine_id {
382 return false;
383 }
384 self.msg_max_size = self.msg_max_size.min(other.msg_max_size);
385 let Some(other_time) = &other.trusted_time else {
386 return false;
387 };
388 match self.trusted_time.as_mut() {
389 Some(time) => time.update_at(
390 other_time.boots,
391 other_time.latest_received_time,
392 other_time.received_at,
393 ),
394 None => {
395 self.trusted_time = Some(other_time.clone());
396 true
397 }
398 }
399 }
400
401 pub fn check_and_update_timeliness(&mut self, msg_boots: u32, msg_time: u32) -> bool {
404 self.check_and_update_timeliness_at(msg_boots, msg_time, Instant::now())
405 }
406
407 fn check_and_update_timeliness_at(
408 &mut self,
409 msg_boots: u32,
410 msg_time: u32,
411 now: Instant,
412 ) -> bool {
413 self.update_time_at(msg_boots, msg_time, now);
414 let (local_boots, local_time) = self.estimated_boots_time_at(now);
415 local_boots != MAX_ENGINE_TIME
416 && msg_boots >= local_boots
417 && (msg_boots != local_boots || msg_time >= local_time.saturating_sub(TIME_WINDOW))
418 }
419
420 #[must_use]
422 pub fn is_in_time_window(&self, msg_boots: u32, msg_time: u32) -> bool {
423 let (local_boots, local_time) = self.estimated_boots_time();
424 in_authoritative_time_window(local_boots, local_time, msg_boots, msg_time)
425 }
426}
427
428fn cap_msg_max_size(reported: u32, session_max: u32) -> u32 {
429 if reported > session_max {
430 tracing::debug!(target: "async_snmp::v3", { reported, session_max }, "capping msgMaxSize to session limit");
431 session_max
432 } else {
433 reported
434 }
435}
436
437pub fn in_authoritative_time_window(
449 local_boots: u32,
450 local_time: u32,
451 msg_boots: u32,
452 msg_time: u32,
453) -> bool {
454 local_boots != MAX_ENGINE_TIME
455 && msg_boots == local_boots
456 && msg_time.abs_diff(local_time) <= TIME_WINDOW
457}
458
459const DEFAULT_ENGINE_CACHE_TTL: Duration = Duration::from_secs(300);
467
468#[derive(Debug)]
469struct CachedTarget {
470 engine_id: Bytes,
471 msg_max_size: u32,
472 refreshed_at: Instant,
473}
474
475#[derive(Debug, Default)]
476struct EngineCacheInner {
477 targets: HashMap<SocketAddr, CachedTarget>,
478 trusted_times: HashMap<Bytes, TrustedEngineTime>,
479}
480
481#[derive(Debug)]
537pub struct EngineCache {
538 inner: RwLock<EngineCacheInner>,
539 max_capacity: Option<usize>,
540 ttl: Duration,
541}
542
543impl Default for EngineCache {
544 fn default() -> Self {
545 Self::new()
546 }
547}
548
549impl EngineCache {
550 #[must_use]
552 pub fn new() -> Self {
553 Self {
554 inner: RwLock::new(EngineCacheInner::default()),
555 max_capacity: None,
556 ttl: DEFAULT_ENGINE_CACHE_TTL,
557 }
558 }
559
560 #[must_use]
562 pub fn with_max_capacity(mut self, max_capacity: usize) -> Self {
563 self.max_capacity = Some(max_capacity.max(1));
564 self
565 }
566
567 #[must_use]
570 pub fn with_ttl(mut self, ttl: Duration) -> Self {
571 self.ttl = ttl;
572 self
573 }
574
575 pub fn get(&self, target: &SocketAddr) -> Option<EngineState> {
580 self.get_at(target, Instant::now())
581 }
582
583 fn get_at(&self, target: &SocketAddr, now: Instant) -> Option<EngineState> {
584 let mut inner = self.inner.write().ok()?;
585 let cached = inner.targets.get(target)?;
586 if now
587 .checked_duration_since(cached.refreshed_at)
588 .unwrap_or_default()
589 > self.ttl
590 {
591 let engine_id = cached.engine_id.clone();
592 inner.targets.remove(target);
593 remove_orphaned_time(&mut inner, &engine_id);
594 return None;
595 }
596 compose_cached_state(&inner, target)
597 }
598
599 pub fn insert(&self, target: SocketAddr, state: EngineState) {
604 self.insert_at(target, state, Instant::now());
605 }
606
607 fn insert_at(&self, target: SocketAddr, state: EngineState, now: Instant) {
608 let _ = self.store_at(target, state, now, false);
609 }
610
611 pub(crate) fn replace_target(
620 &self,
621 target: SocketAddr,
622 state: EngineState,
623 ) -> Result<EngineState> {
624 self.store_at(target, state, Instant::now(), true)
625 .ok_or_else(|| Error::Config("engine cache lock poisoned".into()).boxed())
626 }
627
628 fn store_at(
629 &self,
630 target: SocketAddr,
631 state: EngineState,
632 now: Instant,
633 replace_identity: bool,
634 ) -> Option<EngineState> {
635 let mut inner = self.inner.write().ok()?;
636
637 if !replace_identity
638 && let Some(existing) = inner.targets.get(&target)
639 && existing.engine_id != state.engine_id
640 && now
641 .checked_duration_since(existing.refreshed_at)
642 .unwrap_or_default()
643 <= self.ttl
644 {
645 return compose_cached_state(&inner, &target);
646 }
647
648 if let Some(cap) = self.max_capacity
649 && !inner.targets.contains_key(&target)
650 && inner.targets.len() >= cap
651 && let Some((oldest_target, oldest_engine)) = inner
652 .targets
653 .iter()
654 .min_by_key(|(_, cached)| cached.refreshed_at)
655 .map(|(target, cached)| (*target, cached.engine_id.clone()))
656 {
657 inner.targets.remove(&oldest_target);
658 remove_orphaned_time(&mut inner, &oldest_engine);
659 }
660
661 let replaced_engine = inner
662 .targets
663 .get(&target)
664 .filter(|cached| cached.engine_id != state.engine_id)
665 .map(|cached| cached.engine_id.clone());
666 if let Some(trusted) = &state.trusted_time {
667 merge_trusted_time(&mut inner.trusted_times, &state.engine_id, trusted);
668 }
669 inner.targets.insert(
670 target,
671 CachedTarget {
672 engine_id: state.engine_id,
673 msg_max_size: state.msg_max_size,
674 refreshed_at: now,
675 },
676 );
677 if let Some(replaced_engine) = replaced_engine {
678 remove_orphaned_time(&mut inner, &replaced_engine);
679 }
680 compose_cached_state(&inner, &target)
681 }
682
683 pub fn update_time(
688 &self,
689 target: &SocketAddr,
690 response_boots: u32,
691 response_time: u32,
692 ) -> bool {
693 self.update_time_at(target, response_boots, response_time, Instant::now())
694 }
695
696 fn update_time_at(
697 &self,
698 target: &SocketAddr,
699 response_boots: u32,
700 response_time: u32,
701 now: Instant,
702 ) -> bool {
703 let Ok(mut inner) = self.inner.write() else {
704 return false;
705 };
706 let Some(engine_id) = inner
707 .targets
708 .get(target)
709 .map(|cached| cached.engine_id.clone())
710 else {
711 return false;
712 };
713 let changed = match inner.trusted_times.get_mut(&engine_id) {
714 Some(time) => time.update_at(response_boots, response_time, now),
715 None => {
716 inner.trusted_times.insert(
717 engine_id,
718 TrustedEngineTime::new_at(response_boots, response_time, now),
719 );
720 true
721 }
722 };
723 if let Some(cached) = inner.targets.get_mut(target) {
724 cached.refreshed_at = now;
725 }
726 changed
727 }
728
729 pub(crate) fn check_and_update_timeliness(
738 &self,
739 target: &SocketAddr,
740 local_state: &EngineState,
741 engine_id: &[u8],
742 msg_boots: u32,
743 msg_time: u32,
744 ) -> Option<(bool, EngineState)> {
745 self.check_and_update_timeliness_at(
746 target,
747 local_state,
748 engine_id,
749 msg_boots,
750 msg_time,
751 Instant::now(),
752 )
753 }
754
755 pub(crate) fn timeliness_candidate(
762 &self,
763 target: &SocketAddr,
764 local_state: &EngineState,
765 engine_id: &[u8],
766 msg_boots: u32,
767 msg_time: u32,
768 ) -> Option<(bool, EngineState)> {
769 let inner = self.inner.read().ok()?;
770 let cached_engine_id = &inner.targets.get(target)?.engine_id;
771 if cached_engine_id.as_ref() != engine_id || local_state.engine_id.as_ref() != engine_id {
772 return None;
773 }
774 let mut candidate = local_state.clone();
775 candidate.merge_from(&compose_cached_state(&inner, target)?);
776 let timely = candidate.check_and_update_timeliness(msg_boots, msg_time);
777 Some((timely, candidate))
778 }
779
780 fn check_and_update_timeliness_at(
781 &self,
782 target: &SocketAddr,
783 local_state: &EngineState,
784 engine_id: &[u8],
785 msg_boots: u32,
786 msg_time: u32,
787 now: Instant,
788 ) -> Option<(bool, EngineState)> {
789 let mut inner = self.inner.write().ok()?;
790 let cached_engine_id = inner.targets.get(target)?.engine_id.clone();
791 if cached_engine_id.as_ref() != engine_id || local_state.engine_id.as_ref() != engine_id {
792 return None;
793 }
794 if let Some(local_time) = &local_state.trusted_time {
795 merge_trusted_time(&mut inner.trusted_times, &cached_engine_id, local_time);
796 }
797 let time = inner
798 .trusted_times
799 .entry(cached_engine_id.clone())
800 .or_insert_with(|| TrustedEngineTime::new_at(msg_boots, msg_time, now));
801 time.update_at(msg_boots, msg_time, now);
802 let (local_boots, local_time) = time.estimated_at(now);
803 let timely = local_boots != MAX_ENGINE_TIME
804 && msg_boots >= local_boots
805 && (msg_boots != local_boots || msg_time >= local_time.saturating_sub(TIME_WINDOW));
806 if timely {
807 inner.targets.get_mut(target)?.refreshed_at = now;
808 }
809 let state = compose_cached_state(&inner, target)?;
810 Some((timely, state))
811 }
812
813 pub fn remove(&self, target: &SocketAddr) -> Option<EngineState> {
816 let mut inner = self.inner.write().ok()?;
817 let state = compose_cached_state(&inner, target)?;
818 let cached = inner.targets.remove(target)?;
819 remove_orphaned_time(&mut inner, &cached.engine_id);
820 Some(state)
821 }
822
823 pub fn clear(&self) {
825 if let Ok(mut inner) = self.inner.write() {
826 inner.targets.clear();
827 inner.trusted_times.clear();
828 }
829 }
830
831 pub fn len(&self) -> usize {
833 self.inner.read().map_or(0, |inner| inner.targets.len())
834 }
835
836 pub fn is_empty(&self) -> bool {
838 self.len() == 0
839 }
840}
841
842fn compose_cached_state(inner: &EngineCacheInner, target: &SocketAddr) -> Option<EngineState> {
843 let cached = inner.targets.get(target)?;
844 Some(EngineState {
845 engine_id: cached.engine_id.clone(),
846 msg_max_size: cached.msg_max_size,
847 trusted_time: inner.trusted_times.get(&cached.engine_id).cloned(),
848 })
849}
850
851fn merge_trusted_time(
852 trusted_times: &mut HashMap<Bytes, TrustedEngineTime>,
853 engine_id: &Bytes,
854 incoming: &TrustedEngineTime,
855) {
856 match trusted_times.get_mut(engine_id) {
857 Some(current) => {
858 current.update_at(
859 incoming.boots,
860 incoming.latest_received_time,
861 incoming.received_at,
862 );
863 }
864 None => {
865 trusted_times.insert(engine_id.clone(), incoming.clone());
866 }
867 }
868}
869
870fn remove_orphaned_time(inner: &mut EngineCacheInner, engine_id: &Bytes) {
871 if !inner
872 .targets
873 .values()
874 .any(|cached| cached.engine_id == engine_id)
875 {
876 inner.trusted_times.remove(engine_id);
877 }
878}
879
880pub fn parse_discovery_response(security_params: &Bytes) -> Result<EngineState> {
885 parse_discovery_response_with_limits(
886 security_params,
887 DEFAULT_MSG_MAX_SIZE,
888 DEFAULT_MSG_MAX_SIZE,
889 )
890}
891
892pub fn parse_discovery_response_with_limits(
898 security_params: &Bytes,
899 reported_msg_max_size: u32,
900 session_max: u32,
901) -> Result<EngineState> {
902 let usm = UsmSecurityParams::decode(security_params.clone())?;
903
904 if validate_engine_id(&usm.engine_id).is_err() {
909 tracing::debug!(target: "async_snmp::engine", { length = usm.engine_id.len() }, "discovery response contained invalid engine ID");
910 return Err(Error::MalformedResponse {
911 target: SocketAddr::from(([0, 0, 0, 0], 0)),
912 }
913 .boxed());
914 }
915
916 Ok(EngineState::discovered(
917 usm.engine_id,
918 cap_msg_max_size(reported_msg_max_size, session_max),
919 ))
920}
921
922#[cfg(test)]
923mod tests {
924 use super::*;
925
926 #[test]
927 fn test_generate_engine_id_is_valid_and_well_formed() {
928 let id = generate_engine_id();
929
930 assert!((MIN_ENGINE_ID_LEN..=MAX_ENGINE_ID_LEN).contains(&id.len()));
932 validate_engine_id(&id).expect("generated engine ID must validate");
933
934 assert_eq!(id[0] & 0x80, 0x80);
936 let enterprise = u32::from_be_bytes([id[0], id[1], id[2], id[3]]);
938 assert_eq!(enterprise, 0x8000_0000 | GENERATED_ENGINE_ID_PEN);
939 assert_eq!(id[4], ENGINE_ID_FORMAT_OCTETS);
941 assert_eq!(id.len(), 5 + GENERATED_ENGINE_ID_RANDOM_LEN);
943 }
944
945 #[test]
946 fn test_generate_engine_id_distinct_across_generations() {
947 let a = generate_engine_id();
948 let b = generate_engine_id();
949 assert_ne!(a, b, "two generated engine IDs must not collide");
950 }
951
952 #[test]
953 fn test_validate_engine_id_rejects_invalid() {
954 assert!(validate_engine_id(&[0x80, 0x00, 0x00, 0x01]).is_err());
956 assert!(validate_engine_id(&[0x11; MAX_ENGINE_ID_LEN + 1]).is_err());
958 assert!(validate_engine_id(&[0x00; 8]).is_err());
960 assert!(validate_engine_id(&[0xff; 8]).is_err());
962 }
963
964 #[test]
965 fn test_validate_engine_id_accepts_valid() {
966 validate_engine_id(&[0x80, 0x00, 0x00, 0x00, 0x01]).unwrap();
969 validate_engine_id(&[0x22; MAX_ENGINE_ID_LEN]).unwrap();
971 validate_engine_id(b"my-engine").unwrap();
973 }
974
975 #[test]
976 fn test_engine_state_estimated_time() {
977 let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
978
979 let estimated = state.estimated_time();
981 assert!(estimated >= 1000);
982 }
983
984 #[test]
985 fn test_engine_state_update_time() {
986 let mut state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
987
988 assert!(state.update_time(1, 1100));
990 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 1100);
991
992 assert!(!state.update_time(1, 1050));
994 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 1100);
995
996 assert!(state.update_time(2, 500));
998 assert_eq!(state.trusted_time().unwrap().boots(), 2);
999 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 500);
1000 }
1001
1002 #[test]
1008 fn test_anti_replay_rejects_old_time() {
1009 let mut state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
1010 assert!(state.update_time(1, 1500));
1011
1012 assert!(
1015 !state.update_time(1, 1400),
1016 "Should reject replay: time 1400 < latest 1500"
1017 );
1018 assert_eq!(
1019 state.trusted_time().unwrap().latest_received_time(),
1020 1500,
1021 "Latest should not change"
1022 );
1023
1024 assert!(
1026 !state.update_time(1, 1500),
1027 "Should reject replay: time 1500 == latest 1500"
1028 );
1029 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 1500);
1030
1031 assert!(
1033 state.update_time(1, 1501),
1034 "Should accept: time 1501 > latest 1500"
1035 );
1036 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 1501);
1037 }
1038
1039 #[test]
1044 fn test_anti_replay_new_boot_cycle_resets() {
1045 let mut state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
1046 assert!(state.update_time(1, 5000));
1047
1048 assert!(
1051 state.update_time(2, 100),
1052 "New boot cycle should accept even with lower time"
1053 );
1054 assert_eq!(state.trusted_time().unwrap().boots(), 2);
1055 assert_eq!(state.trusted_time().unwrap().received_time_base(), 100);
1056 assert_eq!(
1057 state.trusted_time().unwrap().latest_received_time(),
1058 100,
1059 "Latest should reset to new time"
1060 );
1061
1062 assert!(
1064 !state.update_time(2, 50),
1065 "Should reject older time in same boot cycle"
1066 );
1067 assert!(state.update_time(2, 150), "Should accept newer time");
1068 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 150);
1069 }
1070
1071 #[test]
1075 fn test_anti_replay_rejects_old_boot_cycle() {
1076 let mut state = EngineState::new(Bytes::from_static(b"engine"), 5, 1000);
1077
1078 assert!(
1080 !state.update_time(4, 9999),
1081 "Should reject old boot cycle even with high time"
1082 );
1083 assert_eq!(
1084 state.trusted_time().unwrap().boots(),
1085 5,
1086 "Boots should not change"
1087 );
1088 assert_eq!(
1089 state.trusted_time().unwrap().latest_received_time(),
1090 1000,
1091 "Latest should not change"
1092 );
1093
1094 assert!(!state.update_time(0, 9999), "Should reject boots=0 replay");
1096 }
1097
1098 #[test]
1100 fn test_anti_replay_boundary_values() {
1101 let mut state = EngineState::new(Bytes::from_static(b"engine"), 1, 0);
1102
1103 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 0);
1105
1106 assert!(state.update_time(1, 1));
1108 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 1);
1109
1110 assert!(!state.update_time(1, 0));
1112
1113 assert!(state.update_time(1, MAX_ENGINE_TIME - 1));
1115 assert_eq!(
1116 state.trusted_time().unwrap().latest_received_time(),
1117 MAX_ENGINE_TIME - 1
1118 );
1119
1120 assert!(state.update_time(1, MAX_ENGINE_TIME));
1122 assert_eq!(state.estimated_boots_time(), (1, MAX_ENGINE_TIME));
1123 assert!(!state.update_time(1, MAX_ENGINE_TIME));
1124 }
1125
1126 #[test]
1127 fn test_engine_state_time_window() {
1128 let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
1129
1130 assert!(state.is_in_time_window(1, 1000));
1132 assert!(state.is_in_time_window(1, 1100)); assert!(state.is_in_time_window(1, 900)); assert!(!state.is_in_time_window(2, 1000));
1137 assert!(!state.is_in_time_window(0, 1000));
1138
1139 assert!(!state.is_in_time_window(1, 2000)); }
1142
1143 #[test]
1148 fn test_time_window_150s_exact_boundary() {
1149 let state = EngineState::new(Bytes::from_static(b"engine"), 1, 10000);
1151
1152 assert!(
1157 state.is_in_time_window(1, 10150),
1158 "Message at exactly +150s boundary should be in window"
1159 );
1160
1161 assert!(
1163 !state.is_in_time_window(1, 10151),
1164 "Message at +151s should be outside window"
1165 );
1166
1167 assert!(
1169 state.is_in_time_window(1, 9850),
1170 "Message at exactly -150s boundary should be in window"
1171 );
1172
1173 assert!(
1175 !state.is_in_time_window(1, 9849),
1176 "Message at -151s should be outside window"
1177 );
1178 }
1179
1180 #[test]
1185 fn test_time_window_boots_latched() {
1186 let state = EngineState::new(Bytes::from_static(b"engine"), 2_147_483_647, 1000);
1189
1190 assert!(
1192 !state.is_in_time_window(2_147_483_647, 1000),
1193 "Latched boots should reject all messages"
1194 );
1195
1196 assert!(!state.is_in_time_window(2_147_483_647, 1100));
1198 assert!(!state.is_in_time_window(2_147_483_647, 900));
1199 }
1200
1201 #[test]
1205 fn test_time_window_boots_mismatch() {
1206 let state = EngineState::new(Bytes::from_static(b"engine"), 100, 1000);
1207
1208 assert!(!state.is_in_time_window(101, 1000));
1210 assert!(!state.is_in_time_window(200, 1000));
1211
1212 assert!(!state.is_in_time_window(99, 1000));
1214 assert!(!state.is_in_time_window(0, 1000));
1215 }
1216
1217 #[test]
1220 fn test_check_and_update_timeliness_within_window_accepted() {
1221 let mut state = EngineState::new(Bytes::from_static(b"engine"), 3, 1000);
1222
1223 assert!(state.check_and_update_timeliness(3, 900));
1225 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 1000);
1226
1227 assert!(state.check_and_update_timeliness(3, 850));
1229 }
1230
1231 #[test]
1232 fn test_check_and_update_timeliness_controllable_boundary_without_rollback() {
1233 let now = Instant::now();
1234 let mut at_boundary = EngineState::new(Bytes::from_static(b"engine"), 3, 1000);
1235 at_boundary.trusted_time.as_mut().unwrap().received_at = now;
1236 assert!(at_boundary.check_and_update_timeliness_at(3, 950, now + Duration::from_secs(100)));
1237 assert_eq!(
1238 at_boundary.trusted_time().unwrap().latest_received_time(),
1239 1000,
1240 "an older in-window message must not lower the high-water mark"
1241 );
1242
1243 let mut outside = EngineState::new(Bytes::from_static(b"engine"), 3, 1000);
1244 outside.trusted_time.as_mut().unwrap().received_at = now;
1245 assert!(!outside.check_and_update_timeliness_at(3, 949, now + Duration::from_secs(100)));
1246 assert_eq!(outside.trusted_time().unwrap().latest_received_time(), 1000);
1247 }
1248
1249 #[test]
1250 fn test_check_and_update_timeliness_newer_time_updates_lcd() {
1251 let mut state = EngineState::new(Bytes::from_static(b"engine"), 3, 1000);
1252
1253 assert!(state.check_and_update_timeliness(3, 1200));
1254 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 1200);
1255 assert_eq!(state.trusted_time().unwrap().received_time_base(), 1200);
1256 }
1257
1258 #[test]
1259 fn test_check_and_update_timeliness_stale_time_rejected() {
1260 let mut state = EngineState::new(Bytes::from_static(b"engine"), 3, 1000);
1261
1262 assert!(!state.check_and_update_timeliness(3, 500));
1264 assert!(!state.check_and_update_timeliness(3, 849));
1266 }
1267
1268 #[test]
1269 fn test_check_and_update_timeliness_old_boots_rejected() {
1270 let mut state = EngineState::new(Bytes::from_static(b"engine"), 3, 1000);
1271
1272 assert!(!state.check_and_update_timeliness(2, 5000));
1273 assert_eq!(
1274 state.trusted_time().unwrap().boots(),
1275 3,
1276 "old boot cycle must not update LCD"
1277 );
1278 }
1279
1280 #[test]
1281 fn test_check_and_update_timeliness_reboot_accepted() {
1282 let mut state = EngineState::new(Bytes::from_static(b"engine"), 3, 1000);
1283
1284 assert!(state.check_and_update_timeliness(4, 10));
1286 assert_eq!(state.trusted_time().unwrap().boots(), 4);
1287 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 10);
1288
1289 assert!(!state.check_and_update_timeliness(3, 99999));
1291 }
1292
1293 #[test]
1294 fn test_check_and_update_timeliness_latched_boots_rejected() {
1295 let mut state = EngineState::new(Bytes::from_static(b"engine"), MAX_ENGINE_TIME, 1000);
1296
1297 assert!(!state.check_and_update_timeliness(MAX_ENGINE_TIME, 1000));
1298 }
1299
1300 #[test]
1301 fn test_engine_cache_basic_operations() {
1302 let cache = EngineCache::new();
1303 let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
1304
1305 assert!(cache.is_empty());
1307 assert!(cache.get(&addr).is_none());
1308
1309 let state = EngineState::new(Bytes::from_static(b"engine1"), 1, 1000);
1311 cache.insert(addr, state);
1312
1313 assert_eq!(cache.len(), 1);
1314 assert!(!cache.is_empty());
1315
1316 let retrieved = cache.get(&addr).unwrap();
1318 assert_eq!(retrieved.engine_id.as_ref(), b"engine1");
1319 assert_eq!(retrieved.trusted_time().unwrap().boots(), 1);
1320
1321 assert!(cache.update_time(&addr, 1, 1100));
1323
1324 let removed = cache.remove(&addr).unwrap();
1326 assert_eq!(removed.trusted_time().unwrap().latest_received_time(), 1100);
1327 assert!(cache.is_empty());
1328 }
1329
1330 #[test]
1331 fn test_engine_cache_explicit_replacement_latches_new_identity() {
1332 let cache = EngineCache::new();
1333 let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
1334 let shared_addr: SocketAddr = "192.168.1.2:161".parse().unwrap();
1335 let old = EngineState::discovered(Bytes::from_static(b"old-engine"), 1400);
1336 let new = EngineState::discovered(Bytes::from_static(b"new-engine"), 1500);
1337 let shared = EngineState::new(Bytes::from_static(b"new-engine"), 7, 500);
1338
1339 cache.insert(addr, old.clone());
1340 cache.insert(shared_addr, shared);
1341 let replaced = cache.replace_target(addr, new).unwrap();
1342 cache.insert(addr, old);
1343
1344 assert_eq!(replaced.engine_id().as_ref(), b"new-engine");
1345 let trusted = replaced.trusted_time().unwrap();
1346 assert_eq!((trusted.boots(), trusted.latest_received_time()), (7, 500));
1347
1348 let cached = cache.get(&addr).unwrap();
1349 assert_eq!(cached.engine_id().as_ref(), b"new-engine");
1350 assert_eq!(cached.msg_max_size, 1500);
1351 }
1352
1353 #[test]
1354 fn test_engine_cache_shares_trusted_time_by_engine_id() {
1355 let cache = EngineCache::new();
1356 let addr1: SocketAddr = "192.168.1.1:161".parse().unwrap();
1357 let addr2: SocketAddr = "192.168.1.2:161".parse().unwrap();
1358 let engine_id = Bytes::from_static(b"shared-engine");
1359
1360 cache.insert(addr1, EngineState::discovered(engine_id.clone(), 1400));
1361 cache.insert(addr2, EngineState::discovered(engine_id, 1500));
1362 assert!(cache.update_time(&addr1, 4, 500));
1363
1364 let state2 = cache.get(&addr2).unwrap();
1365 let trusted = state2.trusted_time().unwrap();
1366 assert_eq!((trusted.boots(), trusted.latest_received_time()), (4, 500));
1367 assert_eq!(state2.msg_max_size, 1500);
1368 }
1369
1370 #[test]
1371 fn test_engine_cache_stale_clone_cannot_overwrite_newer_time() {
1372 let cache = EngineCache::new();
1373 let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
1374 let engine_id = Bytes::from_static(b"engine1");
1375
1376 cache.insert(addr, EngineState::new(engine_id.clone(), 7, 500));
1377 cache.insert(addr, EngineState::new(engine_id.clone(), 6, 9000));
1378 cache.insert(addr, EngineState::discovered(engine_id, 1400));
1379
1380 let state = cache.get(&addr).unwrap();
1381 let trusted = state.trusted_time().unwrap();
1382 assert_eq!((trusted.boots(), trusted.latest_received_time()), (7, 500));
1383 }
1384
1385 #[test]
1386 fn test_engine_cache_concurrent_updates_converge_monotonically() {
1387 use std::sync::Arc;
1388
1389 let cache = Arc::new(EngineCache::new());
1390 let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
1391 cache.insert(
1392 addr,
1393 EngineState::discovered(Bytes::from_static(b"engine1"), 1400),
1394 );
1395
1396 let older = Arc::clone(&cache);
1397 let newer = Arc::clone(&cache);
1398 let older_task = std::thread::spawn(move || {
1399 for _ in 0..100 {
1400 older.update_time(&addr, 4, 9000);
1401 }
1402 });
1403 let newer_task = std::thread::spawn(move || {
1404 for _ in 0..100 {
1405 newer.update_time(&addr, 5, 10);
1406 }
1407 });
1408 older_task.join().unwrap();
1409 newer_task.join().unwrap();
1410
1411 let state = cache.get(&addr).unwrap();
1412 let trusted = state.trusted_time().unwrap();
1413 assert_eq!((trusted.boots(), trusted.latest_received_time()), (5, 10));
1414 }
1415
1416 #[test]
1417 fn test_engine_cache_ttl_expiry() {
1418 let cache = EngineCache::new().with_ttl(Duration::from_secs(5));
1419 let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
1420 let now = Instant::now();
1421
1422 let state = EngineState::new(Bytes::from_static(b"engine1"), 1, 1000);
1423 cache.insert_at(addr, state, now);
1424 assert!(cache.get_at(&addr, now + Duration::from_secs(5)).is_some());
1425 assert!(
1426 cache.get_at(&addr, now + Duration::from_secs(6)).is_none(),
1427 "expired entry should return None"
1428 );
1429 assert!(cache.is_empty(), "expired entry should be removed");
1430 }
1431
1432 #[test]
1433 fn test_engine_cache_ttl_refresh_on_every_accepted_authenticated_message() {
1434 let cache = EngineCache::new().with_ttl(Duration::from_secs(5));
1435 let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
1436 let now = Instant::now();
1437 let engine_id = Bytes::from_static(b"engine1");
1438 let local_state = EngineState::new(engine_id.clone(), 1, 1000);
1439
1440 cache.insert_at(addr, local_state.clone(), now);
1441 let (timely, _) = cache
1442 .check_and_update_timeliness_at(
1443 &addr,
1444 &local_state,
1445 &engine_id,
1446 1,
1447 900,
1448 now + Duration::from_secs(4),
1449 )
1450 .unwrap();
1451 assert!(timely, "older in-window input remains acceptable");
1452 assert!(
1453 cache.get_at(&addr, now + Duration::from_secs(8)).is_some(),
1454 "accepted authenticated input must refresh TTL without advancing high-water"
1455 );
1456 }
1457
1458 #[test]
1459 fn test_engine_cache_live_state_prevents_rebuilt_cache_from_accepting_old_boots() {
1460 let cache = EngineCache::new();
1461 let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
1462 let now = Instant::now();
1463 let engine_id = Bytes::from_static(b"engine1");
1464 let mut local_state = EngineState::new(engine_id.clone(), 5, 1000);
1465 local_state.trusted_time.as_mut().unwrap().received_at = now;
1466
1467 cache.insert_at(addr, EngineState::discovered(engine_id.clone(), 1400), now);
1468 let (timely, canonical) = cache
1469 .check_and_update_timeliness_at(
1470 &addr,
1471 &local_state,
1472 &engine_id,
1473 4,
1474 5000,
1475 now + Duration::from_secs(1),
1476 )
1477 .unwrap();
1478
1479 assert!(!timely, "rebuilt cache must not weaken live client state");
1480 let trusted = canonical.trusted_time().unwrap();
1481 assert_eq!((trusted.boots(), trusted.latest_received_time()), (5, 1000));
1482 }
1483
1484 #[test]
1485 fn test_engine_cache_rejected_message_does_not_refresh_existing_entry() {
1486 let cache = EngineCache::new().with_ttl(Duration::from_secs(5));
1487 let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
1488 let now = Instant::now();
1489 let engine_id = Bytes::from_static(b"engine1");
1490 let mut local_state = EngineState::new(engine_id.clone(), 5, 1000);
1491 local_state.trusted_time.as_mut().unwrap().received_at = now;
1492
1493 cache.insert_at(addr, local_state.clone(), now);
1494 let (timely, _) = cache
1495 .check_and_update_timeliness_at(
1496 &addr,
1497 &local_state,
1498 &engine_id,
1499 4,
1500 5000,
1501 now + Duration::from_secs(4),
1502 )
1503 .unwrap();
1504 assert!(!timely);
1505 assert!(cache.get_at(&addr, now + Duration::from_secs(6)).is_none());
1506 }
1507
1508 #[test]
1509 fn test_engine_cache_rejected_message_does_not_resurrect_expired_entry() {
1510 let cache = EngineCache::new().with_ttl(Duration::from_secs(5));
1511 let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
1512 let now = Instant::now();
1513 let engine_id = Bytes::from_static(b"engine1");
1514 let mut local_state = EngineState::new(engine_id.clone(), 5, 1000);
1515 local_state.trusted_time.as_mut().unwrap().received_at = now;
1516
1517 cache.insert_at(addr, local_state.clone(), now);
1518 let (timely, _) = cache
1519 .check_and_update_timeliness_at(
1520 &addr,
1521 &local_state,
1522 &engine_id,
1523 4,
1524 5000,
1525 now + Duration::from_secs(6),
1526 )
1527 .unwrap();
1528 assert!(!timely);
1529 assert!(cache.get_at(&addr, now + Duration::from_secs(6)).is_none());
1530 assert!(cache.is_empty());
1531 }
1532
1533 #[test]
1534 fn test_engine_cache_max_capacity_eviction() {
1535 let cache = EngineCache::new().with_max_capacity(2);
1536 let addr1: SocketAddr = "192.168.1.1:161".parse().unwrap();
1537 let addr2: SocketAddr = "192.168.1.2:161".parse().unwrap();
1538 let addr3: SocketAddr = "192.168.1.3:161".parse().unwrap();
1539
1540 let now = Instant::now();
1541 cache.insert_at(
1542 addr1,
1543 EngineState::new(Bytes::from_static(b"e1"), 1, 100),
1544 now,
1545 );
1546 cache.insert_at(
1547 addr2,
1548 EngineState::new(Bytes::from_static(b"e2"), 1, 200),
1549 now + Duration::from_secs(1),
1550 );
1551
1552 assert_eq!(cache.len(), 2);
1553
1554 cache.insert_at(
1556 addr3,
1557 EngineState::new(Bytes::from_static(b"e3"), 1, 300),
1558 now + Duration::from_secs(2),
1559 );
1560 assert_eq!(cache.len(), 2);
1561 assert!(
1562 cache.get(&addr1).is_none(),
1563 "oldest entry should be evicted"
1564 );
1565 assert!(cache.get(&addr2).is_some());
1566 assert!(cache.get(&addr3).is_some());
1567 }
1568
1569 #[test]
1570 fn test_parse_discovery_response() {
1571 let usm = UsmSecurityParams::new(b"test-engine-id".as_slice(), 42, 12345, b"".as_slice());
1572 let encoded = usm.encode();
1573
1574 let state = parse_discovery_response(&encoded).unwrap();
1575 assert_eq!(state.engine_id.as_ref(), b"test-engine-id");
1576 assert!(state.trusted_time().is_none());
1577 assert_eq!(state.estimated_boots_time(), (0, 0));
1578 }
1579
1580 #[test]
1581 fn test_parse_discovery_response_empty_engine_id() {
1582 let usm = UsmSecurityParams::empty();
1583 let encoded = usm.encode();
1584
1585 let result = parse_discovery_response(&encoded);
1586 assert!(matches!(
1587 *result.unwrap_err(),
1588 Error::MalformedResponse { .. }
1589 ));
1590 }
1591
1592 #[test]
1593 fn test_parse_discovery_response_rejects_invalid_engine_id() {
1594 let usm = UsmSecurityParams::new(b"abcd".as_slice(), 1, 1, b"".as_slice());
1596 assert!(matches!(
1597 *parse_discovery_response(&usm.encode()).unwrap_err(),
1598 Error::MalformedResponse { .. }
1599 ));
1600
1601 let usm = UsmSecurityParams::new([0u8; 8].as_slice(), 1, 1, b"".as_slice());
1603 assert!(matches!(
1604 *parse_discovery_response(&usm.encode()).unwrap_err(),
1605 Error::MalformedResponse { .. }
1606 ));
1607
1608 let usm = UsmSecurityParams::new([0xffu8; 8].as_slice(), 1, 1, b"".as_slice());
1610 assert!(matches!(
1611 *parse_discovery_response(&usm.encode()).unwrap_err(),
1612 Error::MalformedResponse { .. }
1613 ));
1614 }
1615
1616 #[test]
1625 fn test_engine_boots_transition_to_max() {
1626 let mut state = EngineState::new(Bytes::from_static(b"engine"), 2_147_483_646, 1000);
1627
1628 assert!(
1630 state.update_time(2_147_483_647, 100),
1631 "Transition to boots=2_147_483_647 should be accepted"
1632 );
1633 assert_eq!(state.trusted_time().unwrap().boots(), 2_147_483_647);
1634 assert_eq!(state.trusted_time().unwrap().received_time_base(), 100);
1635 }
1636
1637 #[test]
1644 fn test_engine_boots_latched_update_behavior() {
1645 let mut state = EngineState::new(Bytes::from_static(b"engine"), 2_147_483_647, 1000);
1646
1647 assert!(
1649 state.update_time(2_147_483_647, 2000),
1650 "Time tracking updates should still work"
1651 );
1652 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 2000);
1653
1654 assert!(!state.update_time(2_147_483_647, 1500));
1656 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 2000);
1657
1658 assert!(
1660 !state.is_in_time_window(2_147_483_647, 2000),
1661 "Latched state should still reject all messages"
1662 );
1663 }
1664
1665 #[test]
1671 fn test_engine_boots_latched_time_window_always_fails() {
1672 let state = EngineState::new(Bytes::from_static(b"engine"), 2_147_483_647, 1000);
1673
1674 assert!(!state.is_in_time_window(2_147_483_647, 0));
1676 assert!(!state.is_in_time_window(2_147_483_647, 1000));
1677 assert!(!state.is_in_time_window(2_147_483_647, 1001));
1678 assert!(!state.is_in_time_window(2_147_483_647, u32::MAX));
1679
1680 assert!(!state.is_in_time_window(2_147_483_646, 1000));
1682 assert!(!state.is_in_time_window(0, 1000));
1683 }
1684
1685 #[test]
1690 fn test_engine_state_created_latched() {
1691 let state = EngineState::new(Bytes::from_static(b"engine"), 2_147_483_647, 5000);
1692
1693 assert_eq!(state.trusted_time().unwrap().boots(), 2_147_483_647);
1694 assert_eq!(state.trusted_time().unwrap().received_time_base(), 5000);
1695 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 5000);
1696
1697 assert!(
1699 !state.is_in_time_window(2_147_483_647, 5000),
1700 "Newly created latched engine should reject all messages"
1701 );
1702 }
1703
1704 #[test]
1708 fn test_engine_boots_near_max_operates_normally() {
1709 let mut state = EngineState::new(Bytes::from_static(b"engine"), 2_147_483_645, 1000);
1710
1711 assert!(state.is_in_time_window(2_147_483_645, 1000));
1713 assert!(state.is_in_time_window(2_147_483_645, 1100));
1714 assert!(!state.is_in_time_window(2_147_483_645, 1200)); assert!(state.update_time(2_147_483_646, 500));
1718 assert_eq!(state.trusted_time().unwrap().boots(), 2_147_483_646);
1719 assert!(state.is_in_time_window(2_147_483_646, 500));
1720
1721 assert!(state.update_time(2_147_483_647, 100));
1723 assert_eq!(state.trusted_time().unwrap().boots(), 2_147_483_647);
1724
1725 assert!(!state.is_in_time_window(2_147_483_647, 100));
1727 }
1728
1729 #[test]
1732 fn test_engine_boots_high_value_update_logic() {
1733 let mut state = EngineState::new(Bytes::from_static(b"engine"), 2_147_483_640, 1000);
1734
1735 assert!(!state.update_time(2147483639, 9999));
1737 assert!(!state.update_time(0, 9999));
1738
1739 assert!(!state.update_time(2_147_483_640, 500));
1741
1742 assert!(state.update_time(2_147_483_640, 1500));
1744 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 1500);
1745
1746 assert!(state.update_time(2_147_483_641, 100));
1748 assert_eq!(state.trusted_time().unwrap().boots(), 2_147_483_641);
1749 }
1750
1751 #[test]
1756 fn test_engine_cache_latched_engine() {
1757 let cache = EngineCache::new();
1758 let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
1759
1760 cache.insert(
1762 addr,
1763 EngineState::new(Bytes::from_static(b"latched"), 2_147_483_647, 1000),
1764 );
1765
1766 assert!(
1768 cache.update_time(&addr, 2_147_483_647, 2000),
1769 "Time tracking should update even for latched engine"
1770 );
1771
1772 let state = cache.get(&addr).unwrap();
1774 assert_eq!(state.trusted_time().unwrap().latest_received_time(), 2000);
1775
1776 assert!(
1778 !state.is_in_time_window(2_147_483_647, 2000),
1779 "Latched engine should reject all time window checks"
1780 );
1781 }
1782
1783 #[test]
1795 fn test_engine_state_stores_msg_max_size() {
1796 let state = EngineState::with_msg_max_size(Bytes::from_static(b"engine"), 1, 1000, 65507);
1797 assert_eq!(state.msg_max_size, 65507);
1798 }
1799
1800 #[test]
1805 fn test_engine_state_default_msg_max_size() {
1806 let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
1807 assert_eq!(
1808 state.msg_max_size, DEFAULT_MSG_MAX_SIZE,
1809 "Default msg_max_size should be the maximum UDP datagram size"
1810 );
1811 }
1812
1813 #[test]
1819 fn test_engine_state_msg_max_size_capped_to_session_max() {
1820 let state = EngineState::with_msg_max_size_capped(
1822 Bytes::from_static(b"engine"),
1823 1,
1824 1000,
1825 2_000_000_000, 65507, );
1828 assert_eq!(
1829 state.msg_max_size, 65507,
1830 "msg_max_size should be capped to session maximum"
1831 );
1832 }
1833
1834 #[test]
1839 fn test_engine_state_msg_max_size_within_limit_not_capped() {
1840 let state = EngineState::with_msg_max_size_capped(
1841 Bytes::from_static(b"engine"),
1842 1,
1843 1000,
1844 1472, 65507, );
1847 assert_eq!(
1848 state.msg_max_size, 1472,
1849 "msg_max_size within limit should not be capped"
1850 );
1851 }
1852
1853 #[test]
1857 fn test_engine_state_msg_max_size_at_exact_boundary() {
1858 let state = EngineState::with_msg_max_size_capped(
1859 Bytes::from_static(b"engine"),
1860 1,
1861 1000,
1862 65507, 65507, );
1865 assert_eq!(state.msg_max_size, 65507);
1866 }
1867
1868 #[test]
1873 fn test_engine_state_msg_max_size_tcp_limit() {
1874 const TCP_MAX: u32 = 0x7FFF_FFFF; let state = EngineState::with_msg_max_size_capped(
1878 Bytes::from_static(b"engine"),
1879 1,
1880 1000,
1881 TCP_MAX,
1882 TCP_MAX,
1883 );
1884 assert_eq!(state.msg_max_size, TCP_MAX);
1885
1886 let state = EngineState::with_msg_max_size_capped(
1888 Bytes::from_static(b"engine"),
1889 1,
1890 1000,
1891 u32::MAX, TCP_MAX,
1893 );
1894 assert_eq!(
1895 state.msg_max_size, TCP_MAX,
1896 "Values exceeding session max should be capped"
1897 );
1898 }
1899
1900 #[test]
1902 fn test_engine_state_new_uses_default_constant() {
1903 let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
1904
1905 assert_eq!(state.msg_max_size, DEFAULT_MSG_MAX_SIZE);
1907 }
1908
1909 #[test]
1922 fn test_estimated_time_caps_at_max_engine_time() {
1923 let state = EngineState::new(Bytes::from_static(b"engine"), 1, MAX_ENGINE_TIME - 10);
1925
1926 let estimated = state.estimated_time();
1928 assert!(
1929 estimated <= MAX_ENGINE_TIME,
1930 "estimated_time() should never exceed MAX_ENGINE_TIME ({MAX_ENGINE_TIME}), got {estimated}"
1931 );
1932 }
1933
1934 #[test]
1936 fn test_estimated_pair_rolls_after_max_engine_time() {
1937 let now = Instant::now();
1938 let mut state = EngineState::new(Bytes::from_static(b"engine"), 1, 0);
1939 state.trusted_time.as_mut().unwrap().received_at = now;
1940
1941 assert_eq!(
1942 state.estimated_boots_time_at(now + Duration::from_secs(u64::from(MAX_ENGINE_TIME))),
1943 (1, MAX_ENGINE_TIME)
1944 );
1945 assert_eq!(
1946 state
1947 .estimated_boots_time_at(now + Duration::from_secs(u64::from(MAX_ENGINE_TIME) + 1)),
1948 (2, 0)
1949 );
1950 }
1951
1952 #[test]
1953 fn test_max_engine_time_tuple_remains_timely() {
1954 let now = Instant::now();
1955 let mut state = EngineState::new(Bytes::from_static(b"engine"), 1, MAX_ENGINE_TIME);
1956 state.trusted_time.as_mut().unwrap().received_at = now;
1957
1958 assert!(state.check_and_update_timeliness_at(1, MAX_ENGINE_TIME, now));
1959 assert_eq!(state.estimated_boots_time_at(now), (1, MAX_ENGINE_TIME));
1960 }
1961
1962 #[test]
1966 fn test_max_engine_time_constant() {
1967 assert_eq!(MAX_ENGINE_TIME, 2_147_483_647);
1969 assert_eq!(MAX_ENGINE_TIME, i32::MAX as u32);
1970 }
1971
1972 #[test]
1977 fn test_estimated_time_normal_operation() {
1978 let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
1979
1980 let estimated = state.estimated_time();
1982 assert!(
1983 estimated >= 1000,
1984 "estimated_time() should be at least engine_time"
1985 );
1986 assert!(
1988 estimated < MAX_ENGINE_TIME,
1989 "Normal time values should not hit MAX_ENGINE_TIME cap"
1990 );
1991 }
1992}