use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::RwLock;
use std::time::{Duration, Instant};
use bytes::Bytes;
use crate::error::{Error, Result};
use crate::v3::UsmSecurityParams;
pub const TIME_WINDOW: u32 = 150;
pub const MAX_ENGINE_TIME: u32 = 2147483647;
pub const DEFAULT_MSG_MAX_SIZE: u32 = 65507;
pub fn compute_engine_boots_time(boots_base: u32, total_elapsed_secs: u64) -> (u32, u32) {
let max = MAX_ENGINE_TIME as u64;
let additional_boots = total_elapsed_secs / max;
let current_time = (total_elapsed_secs % max) as u32;
let boots = (boots_base as u64 + additional_boots).min(max) as u32;
(boots, current_time)
}
pub mod report_oids {
use crate::Oid;
use crate::oid;
pub fn unsupported_sec_levels() -> Oid {
oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 1, 0)
}
pub fn not_in_time_windows() -> Oid {
oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 2, 0)
}
pub fn unknown_user_names() -> Oid {
oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 3, 0)
}
pub fn unknown_engine_ids() -> Oid {
oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 4, 0)
}
pub fn wrong_digests() -> Oid {
oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 5, 0)
}
pub fn decryption_errors() -> Oid {
oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 6, 0)
}
}
#[derive(Debug, Clone)]
pub struct EngineState {
pub engine_id: Bytes,
pub engine_boots: u32,
pub engine_time: u32,
pub synced_at: Instant,
pub latest_received_engine_time: u32,
pub msg_max_size: u32,
}
impl EngineState {
pub fn new(engine_id: Bytes, engine_boots: u32, engine_time: u32) -> Self {
Self {
engine_id,
engine_boots,
engine_time,
synced_at: Instant::now(),
latest_received_engine_time: engine_time,
msg_max_size: DEFAULT_MSG_MAX_SIZE,
}
}
pub fn with_msg_max_size(
engine_id: Bytes,
engine_boots: u32,
engine_time: u32,
msg_max_size: u32,
) -> Self {
Self {
engine_id,
engine_boots,
engine_time,
synced_at: Instant::now(),
latest_received_engine_time: engine_time,
msg_max_size,
}
}
pub fn with_msg_max_size_capped(
engine_id: Bytes,
engine_boots: u32,
engine_time: u32,
reported_msg_max_size: u32,
session_max: u32,
) -> Self {
let msg_max_size = if reported_msg_max_size > session_max {
tracing::debug!(target: "async_snmp::v3", { reported = reported_msg_max_size, session_max = session_max }, "capping msgMaxSize to session limit");
session_max
} else {
reported_msg_max_size
};
Self {
engine_id,
engine_boots,
engine_time,
synced_at: Instant::now(),
latest_received_engine_time: engine_time,
msg_max_size,
}
}
pub fn estimated_time(&self) -> u32 {
let elapsed = self.synced_at.elapsed().as_secs() as u32;
self.engine_time
.saturating_add(elapsed)
.min(MAX_ENGINE_TIME)
}
pub fn update_time(&mut self, response_boots: u32, response_time: u32) -> bool {
if response_boots > self.engine_boots {
self.engine_boots = response_boots;
self.engine_time = response_time;
self.synced_at = Instant::now();
self.latest_received_engine_time = response_time;
true
} else if response_boots == self.engine_boots
&& response_time > self.latest_received_engine_time
{
self.engine_time = response_time;
self.synced_at = Instant::now();
self.latest_received_engine_time = response_time;
true
} else {
false
}
}
pub fn is_in_time_window(&self, msg_boots: u32, msg_time: u32) -> bool {
if self.engine_boots == 2147483647 {
return false;
}
if msg_boots != self.engine_boots {
return false;
}
let local_time = self.estimated_time();
let diff = msg_time.abs_diff(local_time);
diff <= TIME_WINDOW
}
}
const DEFAULT_ENGINE_CACHE_TTL: Duration = Duration::from_secs(300);
#[derive(Debug)]
pub struct EngineCache {
engines: RwLock<HashMap<SocketAddr, EngineState>>,
max_capacity: Option<usize>,
ttl: Duration,
}
impl Default for EngineCache {
fn default() -> Self {
Self::new()
}
}
impl EngineCache {
pub fn new() -> Self {
Self {
engines: RwLock::new(HashMap::new()),
max_capacity: None,
ttl: DEFAULT_ENGINE_CACHE_TTL,
}
}
pub fn with_max_capacity(mut self, max_capacity: usize) -> Self {
self.max_capacity = Some(max_capacity.max(1));
self
}
pub fn with_ttl(mut self, ttl: Duration) -> Self {
self.ttl = ttl;
self
}
pub fn get(&self, target: &SocketAddr) -> Option<EngineState> {
{
let engines = self.engines.read().ok()?;
match engines.get(target) {
None => return None,
Some(state) if state.synced_at.elapsed() <= self.ttl => {
return Some(state.clone());
}
Some(_) => {} }
}
if let Ok(mut engines) = self.engines.write()
&& let Some(state) = engines.get(target)
&& state.synced_at.elapsed() > self.ttl
{
engines.remove(target);
}
None
}
pub fn insert(&self, target: SocketAddr, state: EngineState) {
if let Ok(mut engines) = self.engines.write() {
if let Some(cap) = self.max_capacity
&& !engines.contains_key(&target)
&& engines.len() >= cap
&& let Some(oldest) = engines
.iter()
.min_by_key(|(_, s)| s.synced_at)
.map(|(k, _)| *k)
{
engines.remove(&oldest);
}
engines.insert(target, state);
}
}
pub fn update_time(
&self,
target: &SocketAddr,
response_boots: u32,
response_time: u32,
) -> bool {
if let Ok(mut engines) = self.engines.write()
&& let Some(state) = engines.get_mut(target)
{
return state.update_time(response_boots, response_time);
}
false
}
pub fn remove(&self, target: &SocketAddr) -> Option<EngineState> {
self.engines.write().ok()?.remove(target)
}
pub fn clear(&self) {
if let Ok(mut engines) = self.engines.write() {
engines.clear();
}
}
pub fn len(&self) -> usize {
self.engines.read().map(|e| e.len()).unwrap_or(0)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub fn parse_discovery_response(security_params: &Bytes) -> Result<EngineState> {
parse_discovery_response_with_limits(
security_params,
DEFAULT_MSG_MAX_SIZE,
DEFAULT_MSG_MAX_SIZE,
)
}
pub fn parse_discovery_response_with_limits(
security_params: &Bytes,
reported_msg_max_size: u32,
session_max: u32,
) -> Result<EngineState> {
let usm = UsmSecurityParams::decode(security_params.clone())?;
if usm.engine_id.is_empty() {
tracing::debug!(target: "async_snmp::engine", "discovery response contained empty engine ID");
return Err(Error::MalformedResponse {
target: SocketAddr::from(([0, 0, 0, 0], 0)),
}
.boxed());
}
Ok(EngineState::with_msg_max_size_capped(
usm.engine_id,
usm.engine_boots,
usm.engine_time,
reported_msg_max_size,
session_max,
))
}
fn pdu_has_report_oid(pdu: &crate::pdu::Pdu, expected_oid: &crate::Oid) -> bool {
use crate::pdu::PduType;
pdu.pdu_type == PduType::Report && pdu.varbinds.iter().any(|vb| &vb.oid == expected_oid)
}
pub fn is_unknown_engine_id_report(pdu: &crate::pdu::Pdu) -> bool {
pdu_has_report_oid(pdu, &report_oids::unknown_engine_ids())
}
pub fn is_not_in_time_window_report(pdu: &crate::pdu::Pdu) -> bool {
pdu_has_report_oid(pdu, &report_oids::not_in_time_windows())
}
pub fn is_wrong_digest_report(pdu: &crate::pdu::Pdu) -> bool {
pdu_has_report_oid(pdu, &report_oids::wrong_digests())
}
pub fn is_unsupported_sec_level_report(pdu: &crate::pdu::Pdu) -> bool {
pdu_has_report_oid(pdu, &report_oids::unsupported_sec_levels())
}
pub fn is_unknown_user_name_report(pdu: &crate::pdu::Pdu) -> bool {
pdu_has_report_oid(pdu, &report_oids::unknown_user_names())
}
pub fn is_decryption_error_report(pdu: &crate::pdu::Pdu) -> bool {
pdu_has_report_oid(pdu, &report_oids::decryption_errors())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_engine_state_estimated_time() {
let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
let estimated = state.estimated_time();
assert!(estimated >= 1000);
}
#[test]
fn test_engine_state_update_time() {
let mut state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
assert!(state.update_time(1, 1100));
assert_eq!(state.latest_received_engine_time, 1100);
assert!(!state.update_time(1, 1050));
assert_eq!(state.latest_received_engine_time, 1100);
assert!(state.update_time(2, 500));
assert_eq!(state.engine_boots, 2);
assert_eq!(state.latest_received_engine_time, 500);
}
#[test]
fn test_anti_replay_rejects_old_time() {
let mut state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
state.latest_received_engine_time = 1500;
assert!(
!state.update_time(1, 1400),
"Should reject replay: time 1400 < latest 1500"
);
assert_eq!(
state.latest_received_engine_time, 1500,
"Latest should not change"
);
assert!(
!state.update_time(1, 1500),
"Should reject replay: time 1500 == latest 1500"
);
assert_eq!(state.latest_received_engine_time, 1500);
assert!(
state.update_time(1, 1501),
"Should accept: time 1501 > latest 1500"
);
assert_eq!(state.latest_received_engine_time, 1501);
}
#[test]
fn test_anti_replay_new_boot_cycle_resets() {
let mut state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
state.latest_received_engine_time = 5000;
assert!(
state.update_time(2, 100),
"New boot cycle should accept even with lower time"
);
assert_eq!(state.engine_boots, 2);
assert_eq!(state.engine_time, 100);
assert_eq!(
state.latest_received_engine_time, 100,
"Latest should reset to new time"
);
assert!(
!state.update_time(2, 50),
"Should reject older time in same boot cycle"
);
assert!(state.update_time(2, 150), "Should accept newer time");
assert_eq!(state.latest_received_engine_time, 150);
}
#[test]
fn test_anti_replay_rejects_old_boot_cycle() {
let mut state = EngineState::new(Bytes::from_static(b"engine"), 5, 1000);
state.latest_received_engine_time = 1000;
assert!(
!state.update_time(4, 9999),
"Should reject old boot cycle even with high time"
);
assert_eq!(state.engine_boots, 5, "Boots should not change");
assert_eq!(
state.latest_received_engine_time, 1000,
"Latest should not change"
);
assert!(!state.update_time(0, 9999), "Should reject boots=0 replay");
}
#[test]
fn test_anti_replay_boundary_values() {
let mut state = EngineState::new(Bytes::from_static(b"engine"), 1, 0);
assert_eq!(state.latest_received_engine_time, 0);
assert!(state.update_time(1, 1));
assert_eq!(state.latest_received_engine_time, 1);
assert!(!state.update_time(1, 0));
assert!(state.update_time(1, u32::MAX - 1));
assert_eq!(state.latest_received_engine_time, u32::MAX - 1);
assert!(state.update_time(1, u32::MAX));
assert_eq!(state.latest_received_engine_time, u32::MAX);
assert!(!state.update_time(1, u32::MAX));
}
#[test]
fn test_engine_state_time_window() {
let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
assert!(state.is_in_time_window(1, 1000));
assert!(state.is_in_time_window(1, 1100)); assert!(state.is_in_time_window(1, 900));
assert!(!state.is_in_time_window(2, 1000));
assert!(!state.is_in_time_window(0, 1000));
assert!(!state.is_in_time_window(1, 2000)); }
#[test]
fn test_time_window_150s_exact_boundary() {
let state = EngineState::new(Bytes::from_static(b"engine"), 1, 10000);
assert!(
state.is_in_time_window(1, 10150),
"Message at exactly +150s boundary should be in window"
);
assert!(
!state.is_in_time_window(1, 10151),
"Message at +151s should be outside window"
);
assert!(
state.is_in_time_window(1, 9850),
"Message at exactly -150s boundary should be in window"
);
assert!(
!state.is_in_time_window(1, 9849),
"Message at -151s should be outside window"
);
}
#[test]
fn test_time_window_boots_latched() {
let state = EngineState::new(Bytes::from_static(b"engine"), 2147483647, 1000);
assert!(
!state.is_in_time_window(2147483647, 1000),
"Latched boots should reject all messages"
);
assert!(!state.is_in_time_window(2147483647, 1100));
assert!(!state.is_in_time_window(2147483647, 900));
}
#[test]
fn test_time_window_boots_mismatch() {
let state = EngineState::new(Bytes::from_static(b"engine"), 100, 1000);
assert!(!state.is_in_time_window(101, 1000));
assert!(!state.is_in_time_window(200, 1000));
assert!(!state.is_in_time_window(99, 1000));
assert!(!state.is_in_time_window(0, 1000));
}
#[test]
fn test_engine_cache_basic_operations() {
let cache = EngineCache::new();
let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
assert!(cache.is_empty());
assert!(cache.get(&addr).is_none());
let state = EngineState::new(Bytes::from_static(b"engine1"), 1, 1000);
cache.insert(addr, state);
assert_eq!(cache.len(), 1);
assert!(!cache.is_empty());
let retrieved = cache.get(&addr).unwrap();
assert_eq!(retrieved.engine_id.as_ref(), b"engine1");
assert_eq!(retrieved.engine_boots, 1);
assert!(cache.update_time(&addr, 1, 1100));
let removed = cache.remove(&addr).unwrap();
assert_eq!(removed.latest_received_engine_time, 1100);
assert!(cache.is_empty());
}
#[test]
fn test_engine_cache_ttl_expiry() {
let cache = EngineCache::new().with_ttl(Duration::from_millis(50));
let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
let state = EngineState::new(Bytes::from_static(b"engine1"), 1, 1000);
cache.insert(addr, state);
assert!(cache.get(&addr).is_some());
std::thread::sleep(Duration::from_millis(200));
assert!(
cache.get(&addr).is_none(),
"expired entry should return None"
);
assert!(cache.is_empty(), "expired entry should be removed");
}
#[test]
fn test_engine_cache_ttl_refresh_on_time_update() {
let cache = EngineCache::new().with_ttl(Duration::from_millis(500));
let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
let state = EngineState::new(Bytes::from_static(b"engine1"), 1, 1000);
cache.insert(addr, state);
std::thread::sleep(Duration::from_millis(300));
assert!(cache.update_time(&addr, 1, 1050));
std::thread::sleep(Duration::from_millis(300));
assert!(
cache.get(&addr).is_some(),
"refreshed entry should still be alive"
);
}
#[test]
fn test_engine_cache_max_capacity_eviction() {
let cache = EngineCache::new().with_max_capacity(2);
let addr1: SocketAddr = "192.168.1.1:161".parse().unwrap();
let addr2: SocketAddr = "192.168.1.2:161".parse().unwrap();
let addr3: SocketAddr = "192.168.1.3:161".parse().unwrap();
cache.insert(addr1, EngineState::new(Bytes::from_static(b"e1"), 1, 100));
std::thread::sleep(Duration::from_millis(10));
cache.insert(addr2, EngineState::new(Bytes::from_static(b"e2"), 1, 200));
std::thread::sleep(Duration::from_millis(10));
assert_eq!(cache.len(), 2);
cache.insert(addr3, EngineState::new(Bytes::from_static(b"e3"), 1, 300));
assert_eq!(cache.len(), 2);
assert!(
cache.get(&addr1).is_none(),
"oldest entry should be evicted"
);
assert!(cache.get(&addr2).is_some());
assert!(cache.get(&addr3).is_some());
}
#[test]
fn test_parse_discovery_response() {
let usm = UsmSecurityParams::new(b"test-engine-id".as_slice(), 42, 12345, b"".as_slice());
let encoded = usm.encode();
let state = parse_discovery_response(&encoded).unwrap();
assert_eq!(state.engine_id.as_ref(), b"test-engine-id");
assert_eq!(state.engine_boots, 42);
assert_eq!(state.engine_time, 12345);
}
#[test]
fn test_parse_discovery_response_empty_engine_id() {
let usm = UsmSecurityParams::empty();
let encoded = usm.encode();
let result = parse_discovery_response(&encoded);
assert!(matches!(
*result.unwrap_err(),
Error::MalformedResponse { .. }
));
}
#[test]
fn test_is_unknown_engine_id_report() {
use crate::Value;
use crate::VarBind;
use crate::pdu::{Pdu, PduType};
let mut pdu = Pdu {
pdu_type: PduType::Report,
request_id: 1,
error_status: 0,
error_index: 0,
varbinds: vec![VarBind {
oid: report_oids::unknown_engine_ids(),
value: Value::Counter32(1),
}],
};
assert!(is_unknown_engine_id_report(&pdu));
pdu.varbinds[0].oid = report_oids::not_in_time_windows();
assert!(!is_unknown_engine_id_report(&pdu));
pdu.pdu_type = PduType::Response;
assert!(!is_unknown_engine_id_report(&pdu));
}
#[test]
fn test_engine_boots_transition_to_max() {
let mut state = EngineState::new(Bytes::from_static(b"engine"), 2147483646, 1000);
assert!(
state.update_time(2147483647, 100),
"Transition to boots=2147483647 should be accepted"
);
assert_eq!(state.engine_boots, 2147483647);
assert_eq!(state.engine_time, 100);
}
#[test]
fn test_engine_boots_latched_update_behavior() {
let mut state = EngineState::new(Bytes::from_static(b"engine"), 2147483647, 1000);
assert!(
state.update_time(2147483647, 2000),
"Time tracking updates should still work"
);
assert_eq!(state.latest_received_engine_time, 2000);
assert!(!state.update_time(2147483647, 1500));
assert_eq!(state.latest_received_engine_time, 2000);
assert!(
!state.is_in_time_window(2147483647, 2000),
"Latched state should still reject all messages"
);
}
#[test]
fn test_engine_boots_latched_time_window_always_fails() {
let state = EngineState::new(Bytes::from_static(b"engine"), 2147483647, 1000);
assert!(!state.is_in_time_window(2147483647, 0));
assert!(!state.is_in_time_window(2147483647, 1000));
assert!(!state.is_in_time_window(2147483647, 1001));
assert!(!state.is_in_time_window(2147483647, u32::MAX));
assert!(!state.is_in_time_window(2147483646, 1000));
assert!(!state.is_in_time_window(0, 1000));
}
#[test]
fn test_engine_state_created_latched() {
let state = EngineState::new(Bytes::from_static(b"engine"), 2147483647, 5000);
assert_eq!(state.engine_boots, 2147483647);
assert_eq!(state.engine_time, 5000);
assert_eq!(state.latest_received_engine_time, 5000);
assert!(
!state.is_in_time_window(2147483647, 5000),
"Newly created latched engine should reject all messages"
);
}
#[test]
fn test_engine_boots_near_max_operates_normally() {
let mut state = EngineState::new(Bytes::from_static(b"engine"), 2147483645, 1000);
assert!(state.is_in_time_window(2147483645, 1000));
assert!(state.is_in_time_window(2147483645, 1100));
assert!(!state.is_in_time_window(2147483645, 1200));
assert!(state.update_time(2147483646, 500));
assert_eq!(state.engine_boots, 2147483646);
assert!(state.is_in_time_window(2147483646, 500));
assert!(state.update_time(2147483647, 100));
assert_eq!(state.engine_boots, 2147483647);
assert!(!state.is_in_time_window(2147483647, 100));
}
#[test]
fn test_engine_boots_high_value_update_logic() {
let mut state = EngineState::new(Bytes::from_static(b"engine"), 2147483640, 1000);
assert!(!state.update_time(2147483639, 9999));
assert!(!state.update_time(0, 9999));
assert!(!state.update_time(2147483640, 500));
assert!(state.update_time(2147483640, 1500));
assert_eq!(state.latest_received_engine_time, 1500);
assert!(state.update_time(2147483641, 100));
assert_eq!(state.engine_boots, 2147483641);
}
#[test]
fn test_engine_cache_latched_engine() {
let cache = EngineCache::new();
let addr: SocketAddr = "192.168.1.1:161".parse().unwrap();
cache.insert(
addr,
EngineState::new(Bytes::from_static(b"latched"), 2147483647, 1000),
);
assert!(
cache.update_time(&addr, 2147483647, 2000),
"Time tracking should update even for latched engine"
);
let state = cache.get(&addr).unwrap();
assert_eq!(state.latest_received_engine_time, 2000);
assert!(
!state.is_in_time_window(2147483647, 2000),
"Latched engine should reject all time window checks"
);
}
#[test]
fn test_engine_state_stores_msg_max_size() {
let state = EngineState::with_msg_max_size(Bytes::from_static(b"engine"), 1, 1000, 65507);
assert_eq!(state.msg_max_size, 65507);
}
#[test]
fn test_engine_state_default_msg_max_size() {
let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
assert_eq!(
state.msg_max_size, DEFAULT_MSG_MAX_SIZE,
"Default msg_max_size should be the maximum UDP datagram size"
);
}
#[test]
fn test_engine_state_msg_max_size_capped_to_session_max() {
let state = EngineState::with_msg_max_size_capped(
Bytes::from_static(b"engine"),
1,
1000,
2_000_000_000, 65507, );
assert_eq!(
state.msg_max_size, 65507,
"msg_max_size should be capped to session maximum"
);
}
#[test]
fn test_engine_state_msg_max_size_within_limit_not_capped() {
let state = EngineState::with_msg_max_size_capped(
Bytes::from_static(b"engine"),
1,
1000,
1472, 65507, );
assert_eq!(
state.msg_max_size, 1472,
"msg_max_size within limit should not be capped"
);
}
#[test]
fn test_engine_state_msg_max_size_at_exact_boundary() {
let state = EngineState::with_msg_max_size_capped(
Bytes::from_static(b"engine"),
1,
1000,
65507, 65507, );
assert_eq!(state.msg_max_size, 65507);
}
#[test]
fn test_engine_state_msg_max_size_tcp_limit() {
const TCP_MAX: u32 = 0x7FFFFFFF;
let state = EngineState::with_msg_max_size_capped(
Bytes::from_static(b"engine"),
1,
1000,
TCP_MAX,
TCP_MAX,
);
assert_eq!(state.msg_max_size, TCP_MAX);
let state = EngineState::with_msg_max_size_capped(
Bytes::from_static(b"engine"),
1,
1000,
u32::MAX, TCP_MAX,
);
assert_eq!(
state.msg_max_size, TCP_MAX,
"Values exceeding session max should be capped"
);
}
#[test]
fn test_engine_state_new_uses_default_constant() {
let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
assert_eq!(state.msg_max_size, DEFAULT_MSG_MAX_SIZE);
}
#[test]
fn test_estimated_time_caps_at_max_engine_time() {
let state = EngineState::new(Bytes::from_static(b"engine"), 1, MAX_ENGINE_TIME - 10);
let estimated = state.estimated_time();
assert!(
estimated <= MAX_ENGINE_TIME,
"estimated_time() should never exceed MAX_ENGINE_TIME ({}), got {}",
MAX_ENGINE_TIME,
estimated
);
}
#[test]
fn test_estimated_time_at_max_stays_at_max() {
let state = EngineState::new(Bytes::from_static(b"engine"), 1, MAX_ENGINE_TIME);
let estimated = state.estimated_time();
assert_eq!(
estimated, MAX_ENGINE_TIME,
"estimated_time() at max should stay at MAX_ENGINE_TIME"
);
}
#[test]
fn test_max_engine_time_constant() {
assert_eq!(MAX_ENGINE_TIME, 2147483647);
assert_eq!(MAX_ENGINE_TIME, i32::MAX as u32);
}
#[test]
fn test_estimated_time_normal_operation() {
let state = EngineState::new(Bytes::from_static(b"engine"), 1, 1000);
let estimated = state.estimated_time();
assert!(
estimated >= 1000,
"estimated_time() should be at least engine_time"
);
assert!(
estimated < MAX_ENGINE_TIME,
"Normal time values should not hit MAX_ENGINE_TIME cap"
);
}
}