1mod builtins;
71mod notification;
72mod request;
73mod response;
74mod set_handler;
75pub mod vacm;
76
77pub use notification::{NotificationOutcome, SinkOutcome};
78pub use vacm::{SecurityModel, VacmBuilder, VacmConfig, View, ViewCheckResult, ViewSubtree};
79
80use std::collections::{HashMap, HashSet};
81use std::net::SocketAddr;
82use std::sync::Arc;
83use std::sync::atomic::{AtomicU32, Ordering};
84use std::time::{Duration, Instant};
85
86use bytes::Bytes;
87use subtle::ConstantTimeEq;
88use tokio::net::UdpSocket;
89use tokio::sync::Semaphore;
90use tokio_util::sync::CancellationToken;
91use tracing::instrument;
92
93use std::io::IoSliceMut;
94
95use quinn_udp::{RecvMeta, Transmit, UdpSockRef, UdpSocketState};
96
97use crate::error::{Error, ErrorStatus, Result};
98use crate::handler::{GetNextResult, GetResult, HandlerResult, MibHandler, RequestContext};
99use crate::notification::UsmConfig;
100use crate::oid;
101use crate::oid::Oid;
102use crate::pdu::{Pdu, PduType};
103use crate::util::bind_udp_socket;
104use crate::v3::process::UsmStats;
105use crate::v3::{SaltCounter, compute_engine_boots_time};
106use crate::value::Value;
107use crate::varbind::VarBind;
108use crate::version::Version;
109
110const DEFAULT_MAX_MESSAGE_SIZE: usize = 1472;
112
113const RESPONSE_OVERHEAD: usize = 100;
120
121const V3_AUTH_OVERHEAD: usize = 48;
124
125const V3_PRIV_OVERHEAD: usize = 20;
129
130const MAX_VACM_SKIP_ITERATIONS: usize = 1000;
136
137fn v1_rejects_counter64(version: Version, value: &Value) -> bool {
141 version == Version::V1 && matches!(value, Value::Counter64(_))
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
151pub enum BuiltinMib {
152 SnmpEngine,
157 UsmStats,
163 MpdStats,
167}
168
169pub(crate) struct RegisteredHandler {
171 pub(crate) prefix: Oid,
172 pub(crate) handler: Arc<dyn MibHandler>,
173}
174
175pub struct AgentBuilder {
220 bind_addr: String,
221 communities: Vec<Vec<u8>>,
222 usm_users: HashMap<Bytes, UsmConfig>,
223 handlers: Vec<RegisteredHandler>,
224 engine_id: Option<Vec<u8>>,
225 engine_boots: u32,
226 max_message_size: usize,
227 max_concurrent_requests: Option<usize>,
228 recv_buffer_size: Option<usize>,
229 vacm: Option<VacmConfig>,
230 cancel: Option<CancellationToken>,
231 trap_sinks: Vec<(String, crate::client::Auth)>,
232 inform_timeout: Duration,
233 inform_retry: crate::client::Retry,
234 disabled_builtins: HashSet<BuiltinMib>,
235}
236
237impl AgentBuilder {
238 #[must_use]
248 pub fn new() -> Self {
249 Self {
250 bind_addr: "0.0.0.0:161".to_string(),
251 communities: Vec::new(),
252 usm_users: HashMap::new(),
253 handlers: Vec::new(),
254 engine_id: None,
255 engine_boots: 1,
256 max_message_size: DEFAULT_MAX_MESSAGE_SIZE,
257 max_concurrent_requests: Some(1000),
258 recv_buffer_size: Some(4 * 1024 * 1024), vacm: None,
260 cancel: None,
261 trap_sinks: Vec::new(),
262 inform_timeout: Duration::from_secs(5),
263 inform_retry: crate::client::Retry::default(),
264 disabled_builtins: HashSet::new(),
265 }
266 }
267
268 #[must_use]
306 pub fn bind(mut self, addr: impl Into<String>) -> Self {
307 self.bind_addr = addr.into();
308 self
309 }
310
311 #[must_use]
332 pub fn community(mut self, community: &[u8]) -> Self {
333 self.communities.push(community.to_vec());
334 self
335 }
336
337 #[must_use]
355 pub fn communities<I, C>(mut self, communities: I) -> Self
356 where
357 I: IntoIterator<Item = C>,
358 C: AsRef<[u8]>,
359 {
360 for c in communities {
361 self.communities.push(c.as_ref().to_vec());
362 }
363 self
364 }
365
366 #[must_use]
401 pub fn usm_user<F>(mut self, username: impl Into<Bytes>, configure: F) -> Self
402 where
403 F: FnOnce(UsmConfig) -> UsmConfig,
404 {
405 let username_bytes: Bytes = username.into();
406 let config = configure(UsmConfig::new(username_bytes.clone()));
407 self.usm_users.insert(username_bytes, config);
408 self
409 }
410
411 #[must_use]
432 pub fn engine_id(mut self, engine_id: impl Into<Vec<u8>>) -> Self {
433 self.engine_id = Some(engine_id.into());
434 self
435 }
436
437 #[must_use]
462 pub fn engine_boots(mut self, boots: u32) -> Self {
463 self.engine_boots = boots;
464 self
465 }
466
467 #[must_use]
475 pub fn max_message_size(mut self, size: usize) -> Self {
476 self.max_message_size = size;
477 self
478 }
479
480 #[must_use]
491 pub fn max_concurrent_requests(mut self, limit: Option<usize>) -> Self {
492 self.max_concurrent_requests = limit;
493 self
494 }
495
496 #[must_use]
503 pub fn recv_buffer_size(mut self, size: Option<usize>) -> Self {
504 self.recv_buffer_size = size;
505 self
506 }
507
508 #[must_use]
549 pub fn handler(mut self, prefix: Oid, handler: Arc<dyn MibHandler>) -> Self {
550 self.handlers.push(RegisteredHandler { prefix, handler });
551 self
552 }
553
554 #[must_use]
593 pub fn vacm<F>(mut self, configure: F) -> Self
594 where
595 F: FnOnce(VacmBuilder) -> VacmBuilder,
596 {
597 let builder = VacmBuilder::new();
598 self.vacm = Some(configure(builder).build());
599 self
600 }
601
602 #[must_use]
606 pub fn cancel(mut self, token: CancellationToken) -> Self {
607 self.cancel = Some(token);
608 self
609 }
610
611 #[must_use]
636 pub fn trap_sink(
637 mut self,
638 dest: impl Into<String>,
639 auth: impl Into<crate::client::Auth>,
640 ) -> Self {
641 self.trap_sinks.push((dest.into(), auth.into()));
642 self
643 }
644
645 #[must_use]
649 pub fn inform_timeout(mut self, timeout: Duration) -> Self {
650 self.inform_timeout = timeout;
651 self
652 }
653
654 #[must_use]
659 pub fn inform_retry(mut self, retry: crate::client::Retry) -> Self {
660 self.inform_retry = retry;
661 self
662 }
663
664 #[must_use]
670 pub fn without_builtin_handler(mut self, mib: BuiltinMib) -> Self {
671 self.disabled_builtins.insert(mib);
672 self
673 }
674
675 #[must_use]
681 pub fn without_builtin_handlers(mut self) -> Self {
682 self.disabled_builtins.insert(BuiltinMib::SnmpEngine);
683 self.disabled_builtins.insert(BuiltinMib::UsmStats);
684 self.disabled_builtins.insert(BuiltinMib::MpdStats);
685 self
686 }
687
688 pub async fn build(mut self) -> Result<Agent> {
690 for config in self.usm_users.values_mut() {
694 config.validate()?;
695 config.precompute_master_keys();
696 }
697
698 let bind_addr: std::net::SocketAddr = self.bind_addr.parse().map_err(|_| {
699 Error::Config(format!("invalid bind address: {}", self.bind_addr).into())
700 })?;
701
702 let socket = bind_udp_socket(bind_addr, self.recv_buffer_size, None, false)
703 .await
704 .map_err(|e| Error::Network {
705 target: bind_addr,
706 source: e,
707 })?;
708
709 let local_addr = socket.local_addr().map_err(|e| Error::Network {
710 target: bind_addr,
711 source: e,
712 })?;
713
714 let socket_state =
715 UdpSocketState::new(UdpSockRef::from(&socket)).map_err(|e| Error::Network {
716 target: bind_addr,
717 source: e,
718 })?;
719
720 let engine_id: Bytes = match self.engine_id {
722 Some(id) => {
723 crate::v3::validate_engine_id(&id)?;
724 Bytes::from(id)
725 }
726 None => crate::v3::generate_engine_id(),
727 };
728
729 let cancel = self.cancel.unwrap_or_default();
730
731 if self.max_concurrent_requests == Some(0) {
734 return Err(
735 Error::Config("max_concurrent_requests must be greater than 0".into()).into(),
736 );
737 }
738 let concurrency_limit = self
739 .max_concurrent_requests
740 .map(|n| Arc::new(Semaphore::new(n)));
741
742 let mut trap_sinks = Vec::with_capacity(self.trap_sinks.len());
744 for (dest_str, auth) in self.trap_sinks {
745 let dest: SocketAddr = dest_str.parse().map_err(|_| {
746 Error::Config(format!("invalid trap sink address: {dest_str}").into())
747 })?;
748 trap_sinks.push(notification::TrapSink::new(
749 dest,
750 auth,
751 self.inform_timeout,
752 self.inform_retry.clone(),
753 ));
754 }
755
756 let state = Arc::new(AgentState {
757 engine_id,
758 engine_boots: AtomicU32::new(self.engine_boots),
759 engine_time: AtomicU32::new(0),
760 engine_start: Instant::now(),
761 engine_boots_base: self.engine_boots,
762 max_message_size: self.max_message_size,
763 snmp_invalid_msgs: AtomicU32::new(0),
764 snmp_unknown_security_models: AtomicU32::new(0),
765 snmp_silent_drops: AtomicU32::new(0),
766 snmp_unknown_contexts: AtomicU32::new(0),
767 usm_stats: UsmStats::default(),
768 });
769
770 if !self.disabled_builtins.contains(&BuiltinMib::SnmpEngine) {
772 self.handlers.push(RegisteredHandler {
773 prefix: oid!(1, 3, 6, 1, 6, 3, 10, 2, 1),
774 handler: Arc::new(builtins::SnmpEngineHandler {
775 state: Arc::clone(&state),
776 }),
777 });
778 }
779 if !self.disabled_builtins.contains(&BuiltinMib::UsmStats) {
780 self.handlers.push(RegisteredHandler {
781 prefix: oid!(1, 3, 6, 1, 6, 3, 15, 1, 1),
782 handler: Arc::new(builtins::UsmStatsHandler {
783 state: Arc::clone(&state),
784 }),
785 });
786 }
787 if !self.disabled_builtins.contains(&BuiltinMib::MpdStats) {
788 self.handlers.push(RegisteredHandler {
789 prefix: oid!(1, 3, 6, 1, 6, 3, 11, 2, 1),
790 handler: Arc::new(builtins::MpdStatsHandler {
791 state: Arc::clone(&state),
792 }),
793 });
794 }
795
796 self.handlers
798 .sort_by_key(|h| std::cmp::Reverse(h.prefix.len()));
799
800 Ok(Agent {
801 inner: Arc::new(AgentInner {
802 socket: Arc::new(socket),
803 socket_state,
804 local_addr,
805 communities: self.communities,
806 usm_users: self.usm_users,
807 handlers: self.handlers,
808 state,
809 salt_counter: SaltCounter::new(),
810 concurrency_limit,
811 vacm: self.vacm,
812 cancel,
813 trap_sinks,
814 notification_id: std::sync::atomic::AtomicI32::new(1),
815 }),
816 })
817 }
818}
819
820impl Default for AgentBuilder {
821 fn default() -> Self {
822 Self::new()
823 }
824}
825
826pub(crate) struct AgentState {
828 pub(crate) engine_id: Bytes,
829 pub(crate) engine_boots: AtomicU32,
830 pub(crate) engine_time: AtomicU32,
831 pub(crate) engine_start: Instant,
832 pub(crate) engine_boots_base: u32,
834 pub(crate) max_message_size: usize,
835 pub(crate) snmp_invalid_msgs: AtomicU32,
839 pub(crate) snmp_unknown_security_models: AtomicU32,
842 pub(crate) snmp_silent_drops: AtomicU32,
845 pub(crate) snmp_unknown_contexts: AtomicU32,
848 pub(crate) usm_stats: UsmStats,
850}
851
852pub(crate) struct AgentInner {
854 pub(crate) socket: Arc<UdpSocket>,
855 pub(crate) socket_state: UdpSocketState,
856 pub(crate) local_addr: SocketAddr,
857 pub(crate) communities: Vec<Vec<u8>>,
858 pub(crate) usm_users: HashMap<Bytes, UsmConfig>,
859 pub(crate) handlers: Vec<RegisteredHandler>,
860 pub(crate) state: Arc<AgentState>,
861 pub(crate) salt_counter: SaltCounter,
862 pub(crate) concurrency_limit: Option<Arc<Semaphore>>,
863 pub(crate) vacm: Option<VacmConfig>,
864 pub(crate) cancel: CancellationToken,
866 pub(crate) trap_sinks: Vec<notification::TrapSink>,
868 pub(crate) notification_id: std::sync::atomic::AtomicI32,
870}
871
872pub struct Agent {
893 pub(crate) inner: Arc<AgentInner>,
894}
895
896impl Agent {
897 #[must_use]
899 pub fn builder() -> AgentBuilder {
900 AgentBuilder::new()
901 }
902
903 #[must_use]
905 pub fn local_addr(&self) -> SocketAddr {
906 self.inner.local_addr
907 }
908
909 #[must_use]
911 pub fn engine_id(&self) -> &[u8] {
912 &self.inner.state.engine_id
913 }
914
915 #[must_use]
921 pub fn engine_boots(&self) -> u32 {
922 self.inner.state.engine_boots.load(Ordering::Relaxed)
923 }
924
925 #[must_use]
927 pub fn engine_time(&self) -> u32 {
928 self.inner.state.engine_time.load(Ordering::Relaxed)
929 }
930
931 #[must_use]
935 pub fn cancel(&self) -> CancellationToken {
936 self.inner.cancel.clone()
937 }
938
939 #[must_use]
946 pub fn snmp_invalid_msgs(&self) -> u32 {
947 self.inner.state.snmp_invalid_msgs.load(Ordering::Relaxed)
948 }
949
950 #[must_use]
957 pub fn snmp_unknown_security_models(&self) -> u32 {
958 self.inner
959 .state
960 .snmp_unknown_security_models
961 .load(Ordering::Relaxed)
962 }
963
964 #[must_use]
973 pub fn snmp_silent_drops(&self) -> u32 {
974 self.inner.state.snmp_silent_drops.load(Ordering::Relaxed)
975 }
976
977 #[must_use]
986 pub fn snmp_unknown_contexts(&self) -> u32 {
987 self.inner
988 .state
989 .snmp_unknown_contexts
990 .load(Ordering::Relaxed)
991 }
992
993 #[must_use]
1001 pub fn usm_unknown_engine_ids(&self) -> u32 {
1002 self.inner
1003 .state
1004 .usm_stats
1005 .unknown_engine_ids
1006 .load(Ordering::Relaxed)
1007 }
1008
1009 #[must_use]
1017 pub fn usm_unknown_usernames(&self) -> u32 {
1018 self.inner
1019 .state
1020 .usm_stats
1021 .unknown_usernames
1022 .load(Ordering::Relaxed)
1023 }
1024
1025 #[must_use]
1032 pub fn usm_wrong_digests(&self) -> u32 {
1033 self.inner
1034 .state
1035 .usm_stats
1036 .wrong_digests
1037 .load(Ordering::Relaxed)
1038 }
1039
1040 #[must_use]
1050 pub fn usm_not_in_time_windows(&self) -> u32 {
1051 self.inner
1052 .state
1053 .usm_stats
1054 .not_in_time_windows
1055 .load(Ordering::Relaxed)
1056 }
1057
1058 #[must_use]
1066 pub fn usm_unsupported_sec_levels(&self) -> u32 {
1067 self.inner
1068 .state
1069 .usm_stats
1070 .unsupported_sec_levels
1071 .load(Ordering::Relaxed)
1072 }
1073
1074 #[must_use]
1082 pub fn usm_decryption_errors(&self) -> u32 {
1083 self.inner
1084 .state
1085 .usm_stats
1086 .decryption_errors
1087 .load(Ordering::Relaxed)
1088 }
1089
1090 #[must_use]
1095 pub fn uptime_hundredths(&self) -> u32 {
1096 let elapsed = self.inner.state.engine_start.elapsed();
1097 let centisecs = elapsed.as_millis() / 10;
1098 centisecs.min(u128::from(u32::MAX)) as u32
1099 }
1100
1101 #[instrument(skip(self), err, fields(snmp.local_addr = %self.local_addr()))]
1107 pub async fn run(&self) -> Result<()> {
1108 let mut buf = vec![0u8; 65535];
1109
1110 loop {
1111 let recv_meta = tokio::select! {
1112 result = self.recv_packet(&mut buf) => {
1113 result?
1114 }
1115 () = self.inner.cancel.cancelled() => {
1116 tracing::info!(target: "async_snmp::agent", "agent shutdown requested");
1117 return Ok(());
1118 }
1119 };
1120
1121 let data = Bytes::copy_from_slice(&buf[..recv_meta.len]);
1122 let agent = self.clone();
1123
1124 let permit = if let Some(ref sem) = self.inner.concurrency_limit {
1125 tokio::select! {
1126 result = sem.clone().acquire_owned() => {
1127 Some(result.expect("semaphore closed"))
1128 }
1129 () = self.inner.cancel.cancelled() => {
1130 tracing::info!(target: "async_snmp::agent", "agent shutdown requested");
1131 return Ok(());
1132 }
1133 }
1134 } else {
1135 None
1136 };
1137
1138 tokio::spawn(async move {
1139 agent.update_engine_time();
1140
1141 match agent.handle_request(data, recv_meta.addr).await {
1142 Ok(Some(response_bytes)) => {
1143 if response_bytes.len() > agent.inner.state.max_message_size {
1150 agent
1151 .inner
1152 .state
1153 .snmp_silent_drops
1154 .fetch_add(1, Ordering::Relaxed);
1155 tracing::debug!(target: "async_snmp::agent", { snmp.source = %recv_meta.addr, response_size = response_bytes.len(), max_size = agent.inner.state.max_message_size }, "response exceeds max message size, silently dropped");
1156 } else if let Err(e) =
1157 agent.send_response(&response_bytes, &recv_meta).await
1158 {
1159 tracing::warn!(target: "async_snmp::agent", { snmp.source = %recv_meta.addr, error = %e }, "failed to send response");
1160 }
1161 }
1162 Ok(None) => {}
1163 Err(e) => {
1164 tracing::warn!(target: "async_snmp::agent", { snmp.source = %recv_meta.addr, error = %e }, "error handling request");
1165 }
1166 }
1167
1168 drop(permit);
1169 });
1170 }
1171 }
1172
1173 async fn recv_packet(&self, buf: &mut [u8]) -> Result<RecvMeta> {
1174 let mut iov = [IoSliceMut::new(buf)];
1175 let mut meta = [RecvMeta::default()];
1176
1177 loop {
1178 self.inner
1179 .socket
1180 .readable()
1181 .await
1182 .map_err(|e| Error::Network {
1183 target: self.inner.local_addr,
1184 source: e,
1185 })?;
1186
1187 let result = self.inner.socket.try_io(tokio::io::Interest::READABLE, || {
1188 let sref = UdpSockRef::from(&*self.inner.socket);
1189 self.inner.socket_state.recv(sref, &mut iov, &mut meta)
1190 });
1191
1192 match result {
1193 Ok(n) if n > 0 => return Ok(meta[0]),
1194 Ok(_) => { }
1195 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { }
1197 Err(e) => {
1198 return Err(Error::Network {
1199 target: self.inner.local_addr,
1200 source: e,
1201 }
1202 .boxed());
1203 }
1204 }
1205 }
1206 }
1207
1208 async fn send_response(&self, data: &[u8], recv_meta: &RecvMeta) -> std::io::Result<()> {
1209 let transmit = Transmit {
1210 destination: recv_meta.addr,
1211 ecn: None,
1212 contents: data,
1213 segment_size: None,
1214 src_ip: recv_meta.dst_ip,
1215 };
1216
1217 loop {
1218 self.inner.socket.writable().await?;
1219
1220 let result = self.inner.socket.try_io(tokio::io::Interest::WRITABLE, || {
1221 let sref = UdpSockRef::from(&*self.inner.socket);
1222 self.inner.socket_state.try_send(sref, &transmit)
1223 });
1224
1225 match result {
1226 Ok(()) => return Ok(()),
1227 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { }
1229 Err(e) => return Err(e),
1230 }
1231 }
1232 }
1233
1234 async fn handle_request(&self, data: Bytes, source: SocketAddr) -> Result<Option<Bytes>> {
1238 match crate::message::peek_version(data.clone(), source)? {
1239 Version::V1 => self.handle_v1(data, source).await,
1240 Version::V2c => self.handle_v2c(data, source).await,
1241 Version::V3 => self.handle_v3(data, source).await,
1242 }
1243 }
1244
1245 fn update_engine_time(&self) {
1253 let total_secs = self.inner.state.engine_start.elapsed().as_secs();
1254 let (boots, time) =
1255 compute_engine_boots_time(self.inner.state.engine_boots_base, total_secs);
1256
1257 if boots != self.inner.state.engine_boots.load(Ordering::Relaxed)
1258 && boots > self.inner.state.engine_boots_base
1259 {
1260 tracing::warn!(
1261 target: "async_snmp::agent",
1262 engine_boots = boots,
1263 "engine time wrapped past MAX_ENGINE_TIME, incrementing engine boots"
1264 );
1265 }
1266
1267 self.inner
1268 .state
1269 .engine_boots
1270 .store(boots, Ordering::Relaxed);
1271 self.inner.state.engine_time.store(time, Ordering::Relaxed);
1272 }
1273
1274 pub(crate) fn validate_community(&self, community: &[u8]) -> bool {
1279 if self.inner.communities.is_empty() {
1280 return false;
1282 }
1283 let mut valid = false;
1287 for configured in &self.inner.communities {
1288 if configured.len() == community.len()
1290 && bool::from(configured.as_slice().ct_eq(community))
1291 {
1292 valid = true;
1293 }
1294 }
1295 valid
1296 }
1297
1298 async fn dispatch_request(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
1300 match pdu.pdu_type {
1301 PduType::GetRequest => self.handle_get(ctx, pdu).await,
1302 PduType::GetNextRequest => self.handle_get_next(ctx, pdu).await,
1303 PduType::GetBulkRequest => {
1304 if ctx.version == Version::V1 {
1306 return Ok(pdu.to_error_response(ErrorStatus::GenErr, 0));
1307 }
1308 self.handle_get_bulk(ctx, pdu).await
1309 }
1310 PduType::SetRequest => self.handle_set(ctx, pdu).await,
1311 PduType::InformRequest => self.handle_inform(ctx, pdu),
1312 _ => {
1313 Ok(pdu.to_error_response(ErrorStatus::GenErr, 0))
1315 }
1316 }
1317 }
1318
1319 #[allow(
1325 clippy::unnecessary_wraps,
1326 reason = "TODO store received informs, which may be a fallible operation"
1327 )]
1328 fn handle_inform(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
1329 if !Self::response_fits(
1338 &pdu.varbinds,
1339 self.response_overhead(ctx),
1340 self.effective_max_size(ctx),
1341 ) {
1342 return Ok(Self::too_big_response(ctx.version, pdu));
1343 }
1344
1345 Ok(pdu.to_response())
1346 }
1347
1348 fn effective_max_size(&self, ctx: &RequestContext) -> usize {
1352 let agent_max = self.inner.state.max_message_size;
1353 match ctx.msg_max_size {
1354 Some(client_max) => agent_max.min(client_max as usize),
1355 None => agent_max,
1356 }
1357 }
1358
1359 fn response_overhead(&self, ctx: &RequestContext) -> usize {
1373 if ctx.version != Version::V3 {
1374 return RESPONSE_OVERHEAD + ctx.security_name.len();
1379 }
1380 let mut overhead = RESPONSE_OVERHEAD
1381 + 2 * self.inner.state.engine_id.len()
1382 + ctx.security_name.len()
1383 + ctx.context_name.len();
1384 if ctx.security_level.requires_auth() {
1385 overhead += V3_AUTH_OVERHEAD;
1386 }
1387 if ctx.security_level.requires_priv() {
1388 overhead += V3_PRIV_OVERHEAD;
1389 }
1390 overhead
1391 }
1392
1393 fn response_fits(varbinds: &[VarBind], overhead: usize, max_size: usize) -> bool {
1397 let size = overhead + varbinds.iter().map(VarBind::encoded_size).sum::<usize>();
1398 size <= max_size
1399 }
1400
1401 pub(super) fn too_big_response(version: Version, pdu: &Pdu) -> Pdu {
1409 let varbinds = if version == Version::V1 {
1410 pdu.varbinds.clone()
1411 } else {
1412 Vec::new()
1413 };
1414 Pdu {
1415 pdu_type: PduType::Response,
1416 request_id: pdu.request_id,
1417 error_status: ErrorStatus::TooBig.as_i32(),
1418 error_index: 0,
1419 varbinds,
1420 }
1421 }
1422
1423 async fn handle_get(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
1425 let mut response_varbinds = Vec::with_capacity(pdu.varbinds.len());
1426
1427 for (index, vb) in pdu.varbinds.iter().enumerate() {
1428 if let Some(ref vacm) = self.inner.vacm
1430 && !vacm.check_access(ctx.read_view.as_ref(), &vb.oid)
1431 {
1432 if ctx.version == Version::V1 {
1434 return Ok(pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32));
1435 }
1436 response_varbinds.push(VarBind::new(vb.oid.clone(), Value::NoSuchObject));
1438 continue;
1439 }
1440
1441 let result = if let Some(handler) = self.find_handler(&vb.oid) {
1442 match handler.handler.get(ctx, &vb.oid).await {
1443 Ok(result) => result,
1444 Err(err) => {
1445 tracing::warn!(
1448 target: "async_snmp::agent",
1449 oid = %vb.oid,
1450 error = %err,
1451 "handler GET failed; responding genErr"
1452 );
1453 return Ok(pdu.to_error_response(ErrorStatus::GenErr, (index + 1) as i32));
1454 }
1455 }
1456 } else {
1457 GetResult::NoSuchObject
1458 };
1459
1460 let response_value = match result {
1461 GetResult::Value(v) => {
1462 if v1_rejects_counter64(ctx.version, &v) {
1463 return Ok(
1464 pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32)
1465 );
1466 }
1467 v
1468 }
1469 GetResult::NoSuchObject => {
1470 if ctx.version == Version::V1 {
1472 return Ok(
1473 pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32)
1474 );
1475 }
1476 Value::NoSuchObject
1477 }
1478 GetResult::NoSuchInstance => {
1479 if ctx.version == Version::V1 {
1481 return Ok(
1482 pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32)
1483 );
1484 }
1485 Value::NoSuchInstance
1486 }
1487 };
1488
1489 response_varbinds.push(VarBind::new(vb.oid.clone(), response_value));
1490 }
1491
1492 if !Self::response_fits(
1495 &response_varbinds,
1496 self.response_overhead(ctx),
1497 self.effective_max_size(ctx),
1498 ) {
1499 return Ok(Self::too_big_response(ctx.version, pdu));
1500 }
1501
1502 Ok(Pdu {
1503 pdu_type: PduType::Response,
1504 request_id: pdu.request_id,
1505 error_status: 0,
1506 error_index: 0,
1507 varbinds: response_varbinds,
1508 })
1509 }
1510
1511 async fn handle_get_next(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
1513 let mut response_varbinds = Vec::with_capacity(pdu.varbinds.len());
1514
1515 for (index, vb) in pdu.varbinds.iter().enumerate() {
1516 let next = match self.get_next_accessible_oid(ctx, &vb.oid).await {
1520 Ok(next) => next,
1521 Err(err) => {
1522 tracing::warn!(
1523 target: "async_snmp::agent",
1524 oid = %vb.oid,
1525 error = %err,
1526 "handler GETNEXT failed; responding genErr"
1527 );
1528 return Ok(pdu.to_error_response(ErrorStatus::GenErr, (index + 1) as i32));
1529 }
1530 };
1531
1532 if let Some(next_vb) = next {
1533 response_varbinds.push(next_vb);
1534 } else {
1535 if ctx.version == Version::V1 {
1537 return Ok(pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32));
1538 }
1539 response_varbinds.push(VarBind::new(vb.oid.clone(), Value::EndOfMibView));
1540 }
1541 }
1542
1543 if !Self::response_fits(
1546 &response_varbinds,
1547 self.response_overhead(ctx),
1548 self.effective_max_size(ctx),
1549 ) {
1550 return Ok(Self::too_big_response(ctx.version, pdu));
1551 }
1552
1553 Ok(Pdu {
1554 pdu_type: PduType::Response,
1555 request_id: pdu.request_id,
1556 error_status: 0,
1557 error_index: 0,
1558 varbinds: response_varbinds,
1559 })
1560 }
1561
1562 async fn handle_get_bulk(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
1567 let non_repeaters = pdu.error_status.try_into().unwrap_or(0);
1569 let max_repetitions = pdu.error_index.max(0);
1570
1571 let mut response_varbinds = Vec::new();
1572 let mut current_size: usize = self.response_overhead(ctx);
1573 let max_size = self.effective_max_size(ctx);
1574
1575 let can_add = |vb: &VarBind, current_size: usize| -> bool {
1577 current_size + vb.encoded_size() <= max_size
1578 };
1579
1580 for (index, vb) in pdu.varbinds.iter().take(non_repeaters).enumerate() {
1582 let next = match self.get_next_accessible_oid(ctx, &vb.oid).await {
1583 Ok(next) => next,
1584 Err(err) => {
1585 tracing::warn!(
1588 target: "async_snmp::agent",
1589 oid = %vb.oid,
1590 error = %err,
1591 "handler GETBULK failed; responding genErr"
1592 );
1593 return Ok(pdu.to_error_response(ErrorStatus::GenErr, (index + 1) as i32));
1594 }
1595 };
1596
1597 let next_vb = match next {
1598 Some(next_vb) => next_vb,
1599 None => VarBind::new(vb.oid.clone(), Value::EndOfMibView),
1600 };
1601
1602 if !can_add(&next_vb, current_size) {
1603 if response_varbinds.is_empty() {
1605 return Ok(Self::too_big_response(ctx.version, pdu));
1606 }
1607 return Ok(Pdu {
1615 pdu_type: PduType::Response,
1616 request_id: pdu.request_id,
1617 error_status: 0,
1618 error_index: 0,
1619 varbinds: response_varbinds,
1620 });
1621 }
1622
1623 current_size += next_vb.encoded_size();
1624 response_varbinds.push(next_vb);
1625 }
1626
1627 if non_repeaters < pdu.varbinds.len() {
1629 let repeaters = &pdu.varbinds[non_repeaters..];
1630 let mut current_oids: Vec<Oid> = repeaters.iter().map(|vb| vb.oid.clone()).collect();
1631 let mut all_done = vec![false; repeaters.len()];
1632
1633 'outer: for _ in 0..max_repetitions {
1634 let mut row_complete = true;
1635 for (i, oid) in current_oids.iter_mut().enumerate() {
1636 let next_vb = if all_done[i] {
1637 VarBind::new(oid.clone(), Value::EndOfMibView)
1638 } else {
1639 let next = match self.get_next_accessible_oid(ctx, oid).await {
1640 Ok(next) => next,
1641 Err(err) => {
1642 tracing::warn!(
1647 target: "async_snmp::agent",
1648 oid = %oid,
1649 error = %err,
1650 "handler GETBULK failed; responding genErr"
1651 );
1652 return Ok(pdu.to_error_response(
1653 ErrorStatus::GenErr,
1654 (non_repeaters + i + 1) as i32,
1655 ));
1656 }
1657 };
1658
1659 if let Some(next_vb) = next {
1660 *oid = next_vb.oid.clone();
1661 row_complete = false;
1662 next_vb
1663 } else {
1664 all_done[i] = true;
1665 VarBind::new(oid.clone(), Value::EndOfMibView)
1666 }
1667 };
1668
1669 if !can_add(&next_vb, current_size) {
1671 if response_varbinds.is_empty() {
1680 return Ok(Self::too_big_response(ctx.version, pdu));
1681 }
1682 break 'outer;
1684 }
1685
1686 current_size += next_vb.encoded_size();
1687 response_varbinds.push(next_vb);
1688 }
1689
1690 if row_complete {
1691 break;
1692 }
1693 }
1694 }
1695
1696 Ok(Pdu {
1697 pdu_type: PduType::Response,
1698 request_id: pdu.request_id,
1699 error_status: 0,
1700 error_index: 0,
1701 varbinds: response_varbinds,
1702 })
1703 }
1704
1705 pub(crate) fn find_handler(&self, oid: &Oid) -> Option<&RegisteredHandler> {
1707 self.inner
1709 .handlers
1710 .iter()
1711 .find(|&handler| handler.handler.handles(&handler.prefix, oid))
1712 .map(|v| v as _)
1713 }
1714
1715 async fn get_next_accessible_oid(
1720 &self,
1721 ctx: &RequestContext,
1722 from_oid: &Oid,
1723 ) -> HandlerResult<Option<VarBind>> {
1724 let mut search_from = from_oid.clone();
1725 for _ in 0..MAX_VACM_SKIP_ITERATIONS {
1726 let candidate = self.get_next_oid(ctx, &search_from).await?;
1727 match candidate {
1728 None => return Ok(None),
1729 Some(ref next_vb) => {
1730 if next_vb.oid <= search_from {
1731 tracing::error!(
1732 target: "async_snmp::agent",
1733 from = %search_from,
1734 got = %next_vb.oid,
1735 "handler returned non-increasing OID in GETNEXT"
1736 );
1737 return Ok(None);
1738 }
1739 if v1_rejects_counter64(ctx.version, &next_vb.value) {
1740 search_from = next_vb.oid.clone();
1741 continue;
1742 }
1743 if let Some(ref vacm) = self.inner.vacm {
1744 if vacm.check_access(ctx.read_view.as_ref(), &next_vb.oid) {
1745 return Ok(candidate);
1746 }
1747 search_from = next_vb.oid.clone();
1748 } else {
1749 return Ok(candidate);
1750 }
1751 }
1752 }
1753 }
1754 tracing::warn!(
1757 target: "async_snmp::agent",
1758 from = %from_oid,
1759 cap = MAX_VACM_SKIP_ITERATIONS,
1760 "VACM skip cap reached in GETNEXT; ending scan for this varbind"
1761 );
1762 Ok(None)
1763 }
1764
1765 async fn get_next_oid(
1767 &self,
1768 ctx: &RequestContext,
1769 oid: &Oid,
1770 ) -> HandlerResult<Option<VarBind>> {
1771 let mut best_result: Option<VarBind> = None;
1780
1781 for handler in &self.inner.handlers {
1782 let prefix = &handler.prefix;
1783 if prefix <= oid && !oid.starts_with(prefix) {
1784 continue;
1785 }
1786 if let GetNextResult::Value(next) = handler.handler.get_next(ctx, oid).await? {
1787 if next.oid > *oid {
1789 match &best_result {
1790 None => best_result = Some(next),
1791 Some(current) if next.oid < current.oid => best_result = Some(next),
1792 _ => {}
1793 }
1794 }
1795 }
1796 }
1797
1798 Ok(best_result)
1799 }
1800}
1801
1802impl Clone for Agent {
1803 fn clone(&self) -> Self {
1804 Self {
1805 inner: Arc::clone(&self.inner),
1806 }
1807 }
1808}
1809
1810#[cfg(test)]
1811mod tests {
1812 use super::*;
1813 use crate::handler::{
1814 BoxFuture, GetNextResult, GetResult, HandlerError, HandlerResult, MibHandler,
1815 RequestContext, SecurityModel, SetResult,
1816 };
1817 use crate::message::SecurityLevel;
1818 use crate::oid;
1819
1820 struct TestHandler;
1821
1822 impl MibHandler for TestHandler {
1823 fn get<'a>(
1824 &'a self,
1825 _ctx: &'a RequestContext,
1826 oid: &'a Oid,
1827 ) -> BoxFuture<'a, HandlerResult<GetResult>> {
1828 Box::pin(async move {
1829 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
1830 return Ok(GetResult::Value(Value::Integer(42)));
1831 }
1832 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0) {
1833 return Ok(GetResult::Value(Value::OctetString(Bytes::from_static(
1834 b"test",
1835 ))));
1836 }
1837 Ok(GetResult::NoSuchObject)
1838 })
1839 }
1840
1841 fn get_next<'a>(
1842 &'a self,
1843 _ctx: &'a RequestContext,
1844 oid: &'a Oid,
1845 ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
1846 Box::pin(async move {
1847 let oid1 = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
1848 let oid2 = oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0);
1849
1850 if oid < &oid1 {
1851 return Ok(GetNextResult::Value(VarBind::new(oid1, Value::Integer(42))));
1852 }
1853 if oid < &oid2 {
1854 return Ok(GetNextResult::Value(VarBind::new(
1855 oid2,
1856 Value::OctetString(Bytes::from_static(b"test")),
1857 )));
1858 }
1859 Ok(GetNextResult::EndOfMibView)
1860 })
1861 }
1862 }
1863
1864 fn test_ctx() -> RequestContext {
1865 RequestContext {
1866 source: "127.0.0.1:12345".parse().unwrap(),
1867 version: Version::V2c,
1868 security_model: SecurityModel::V2c,
1869 security_name: Bytes::from_static(b"public"),
1870 security_level: SecurityLevel::NoAuthNoPriv,
1871 context_name: Bytes::new(),
1872 request_id: 1,
1873 pdu_type: PduType::GetRequest,
1874 group_name: None,
1875 read_view: None,
1876 write_view: None,
1877 msg_max_size: None,
1878 }
1879 }
1880
1881 #[test]
1882 fn test_agent_builder_defaults() {
1883 let builder = AgentBuilder::new();
1884 assert_eq!(builder.bind_addr, "0.0.0.0:161");
1885 assert!(builder.communities.is_empty());
1886 assert!(builder.usm_users.is_empty());
1887 assert!(builder.handlers.is_empty());
1888 }
1889
1890 #[test]
1891 fn test_agent_builder_community() {
1892 let builder = AgentBuilder::new()
1893 .community(b"public")
1894 .community(b"private");
1895 assert_eq!(builder.communities.len(), 2);
1896 }
1897
1898 #[test]
1899 fn test_agent_builder_communities() {
1900 let builder = AgentBuilder::new().communities(["public", "private"]);
1901 assert_eq!(builder.communities.len(), 2);
1902 }
1903
1904 #[test]
1905 fn test_agent_builder_handler() {
1906 let builder =
1907 AgentBuilder::new().handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(TestHandler));
1908 assert_eq!(builder.handlers.len(), 1);
1909 }
1910
1911 #[tokio::test]
1912 async fn test_agent_builder_rejects_privacy_without_auth() {
1913 let result = AgentBuilder::new()
1914 .bind("127.0.0.1:0")
1915 .usm_user("noauth", |u| {
1916 u.privacy(crate::v3::PrivProtocol::Aes128, b"privpass")
1917 })
1918 .build()
1919 .await;
1920 match result {
1921 Err(err) => assert!(
1922 matches!(*err, Error::Config(_)),
1923 "expected Config error, got {err:?}"
1924 ),
1925 Ok(_) => panic!("privacy without auth must be rejected"),
1926 }
1927 }
1928
1929 #[tokio::test]
1930 async fn test_mib_handler_default_set() {
1931 let handler = TestHandler;
1932 let mut ctx = test_ctx();
1933 ctx.pdu_type = PduType::SetRequest;
1934
1935 let result = handler
1936 .test_set(&ctx, &oid!(1, 3, 6, 1), &Value::Integer(1))
1937 .await;
1938 assert_eq!(result, SetResult::NotWritable);
1939 }
1940
1941 #[test]
1942 fn test_mib_handler_handles() {
1943 let handler = TestHandler;
1944 let prefix = oid!(1, 3, 6, 1, 4, 1, 99_999);
1945
1946 assert!(handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 99_999, 1, 0)));
1948
1949 assert!(handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 99_999)));
1951
1952 assert!(!handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 99_998)));
1955
1956 assert!(!handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 100_000)));
1958 }
1959
1960 #[tokio::test]
1961 async fn test_test_handler_get() {
1962 let handler = TestHandler;
1963 let ctx = test_ctx();
1964
1965 let result = handler
1967 .get(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0))
1968 .await
1969 .unwrap();
1970 assert!(matches!(result, GetResult::Value(Value::Integer(42))));
1971
1972 let result = handler
1974 .get(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 99, 0))
1975 .await
1976 .unwrap();
1977 assert!(matches!(result, GetResult::NoSuchObject));
1978 }
1979
1980 #[tokio::test]
1981 async fn test_test_handler_get_next() {
1982 let handler = TestHandler;
1983 let mut ctx = test_ctx();
1984 ctx.pdu_type = PduType::GetNextRequest;
1985
1986 let next = handler
1988 .get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999))
1989 .await
1990 .unwrap();
1991 assert!(next.is_value());
1992 if let GetNextResult::Value(vb) = next {
1993 assert_eq!(vb.oid, oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0));
1994 }
1995
1996 let next = handler
1998 .get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0))
1999 .await
2000 .unwrap();
2001 assert!(next.is_value());
2002 if let GetNextResult::Value(vb) = next {
2003 assert_eq!(vb.oid, oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0));
2004 }
2005
2006 let next = handler
2008 .get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0))
2009 .await
2010 .unwrap();
2011 assert!(next.is_end_of_mib_view());
2012 }
2013
2014 struct FailingBackendHandler;
2017
2018 impl MibHandler for FailingBackendHandler {
2019 fn get<'a>(
2020 &'a self,
2021 _ctx: &'a RequestContext,
2022 oid: &'a Oid,
2023 ) -> BoxFuture<'a, HandlerResult<GetResult>> {
2024 Box::pin(async move {
2025 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
2026 return Ok(GetResult::Value(Value::Integer(1)));
2027 }
2028 Err(HandlerError::new("backing store unavailable"))
2029 })
2030 }
2031
2032 fn get_next<'a>(
2033 &'a self,
2034 _ctx: &'a RequestContext,
2035 oid: &'a Oid,
2036 ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
2037 Box::pin(async move {
2038 let first = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
2039 if oid < &first {
2040 return Ok(GetNextResult::Value(VarBind::new(first, Value::Integer(1))));
2041 }
2042 Err(HandlerError::new("backing store unavailable"))
2043 })
2044 }
2045 }
2046
2047 async fn failing_backend_agent() -> Agent {
2048 AgentBuilder::new()
2049 .bind("127.0.0.1:0")
2050 .community(b"public")
2051 .handler(
2052 oid!(1, 3, 6, 1, 4, 1, 99999),
2053 Arc::new(FailingBackendHandler),
2054 )
2055 .build()
2056 .await
2057 .unwrap()
2058 }
2059
2060 #[tokio::test]
2061 async fn test_get_handler_error_maps_to_generr() {
2062 let agent = failing_backend_agent().await;
2063 let ctx = test_ctx();
2064
2065 let pdu = Pdu {
2069 pdu_type: PduType::GetRequest,
2070 request_id: 1,
2071 error_status: 0,
2072 error_index: 0,
2073 varbinds: vec![
2074 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0), Value::Null),
2075 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0), Value::Null),
2076 ],
2077 };
2078
2079 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2080 assert_eq!(response.error_status, ErrorStatus::GenErr.as_i32());
2081 assert_eq!(response.error_index, 2);
2082 assert_eq!(response.varbinds.len(), 2);
2083 assert_eq!(response.varbinds[0].oid, pdu.varbinds[0].oid);
2084 }
2085
2086 #[tokio::test]
2087 async fn test_get_v1_handler_error_maps_to_generr() {
2088 let agent = failing_backend_agent().await;
2089 let mut ctx = test_ctx();
2090 ctx.version = Version::V1;
2091
2092 let pdu = Pdu {
2093 pdu_type: PduType::GetRequest,
2094 request_id: 2,
2095 error_status: 0,
2096 error_index: 0,
2097 varbinds: vec![VarBind::new(
2098 oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0),
2099 Value::Null,
2100 )],
2101 };
2102
2103 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2104 assert_eq!(response.error_status, ErrorStatus::GenErr.as_i32());
2105 assert_eq!(response.error_index, 1);
2106 }
2107
2108 #[tokio::test]
2109 async fn test_getnext_handler_error_maps_to_generr() {
2110 let agent = failing_backend_agent().await;
2111 let mut ctx = test_ctx();
2112 ctx.pdu_type = PduType::GetNextRequest;
2113
2114 let pdu = Pdu {
2115 pdu_type: PduType::GetNextRequest,
2116 request_id: 3,
2117 error_status: 0,
2118 error_index: 0,
2119 varbinds: vec![VarBind::new(
2120 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
2121 Value::Null,
2122 )],
2123 };
2124
2125 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2126 assert_eq!(response.error_status, ErrorStatus::GenErr.as_i32());
2127 assert_eq!(response.error_index, 1);
2128 }
2129
2130 #[tokio::test]
2131 async fn test_getbulk_handler_error_maps_to_generr() {
2132 let agent = failing_backend_agent().await;
2133 let mut ctx = test_ctx();
2134 ctx.pdu_type = PduType::GetBulkRequest;
2135
2136 let pdu = Pdu {
2140 pdu_type: PduType::GetBulkRequest,
2141 request_id: 4,
2142 error_status: 1, error_index: 5, varbinds: vec![
2145 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null),
2146 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0), Value::Null),
2147 ],
2148 };
2149
2150 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2151 assert_eq!(response.error_status, ErrorStatus::GenErr.as_i32());
2152 assert_eq!(response.error_index, 2);
2153 }
2154
2155 struct FiveOidHandler;
2157
2158 impl MibHandler for FiveOidHandler {
2159 fn get<'a>(
2160 &'a self,
2161 _ctx: &'a RequestContext,
2162 oid: &'a Oid,
2163 ) -> BoxFuture<'a, HandlerResult<GetResult>> {
2164 Box::pin(async move {
2165 for i in 1u16..=5 {
2166 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, i.into(), 0) {
2167 return Ok(GetResult::Value(Value::Integer(i.into())));
2168 }
2169 }
2170 Ok(GetResult::NoSuchObject)
2171 })
2172 }
2173
2174 fn get_next<'a>(
2175 &'a self,
2176 _ctx: &'a RequestContext,
2177 oid: &'a Oid,
2178 ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
2179 Box::pin(async move {
2180 for i in 1u32..=5 {
2181 let candidate = oid!(1, 3, 6, 1, 4, 1, 99999, i, 0);
2182 if oid < &candidate {
2183 return Ok(GetNextResult::Value(VarBind::new(
2184 candidate,
2185 Value::Integer(i as i32),
2186 )));
2187 }
2188 }
2189 Ok(GetNextResult::EndOfMibView)
2190 })
2191 }
2192 }
2193
2194 async fn test_agent_with_restricted_vacm() -> Agent {
2198 Agent::builder()
2199 .bind("127.0.0.1:0")
2200 .community(b"public")
2201 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(FiveOidHandler))
2202 .vacm(|v| {
2203 v.group("public", SecurityModel::V2c, "readers")
2204 .access("readers", |a| a.read_view("restricted"))
2205 .view("restricted", |v| {
2206 v.include(oid!(1, 3, 6, 1, 4, 1, 99999, 2))
2207 .include(oid!(1, 3, 6, 1, 4, 1, 99999, 4))
2208 })
2209 })
2210 .build()
2211 .await
2212 .unwrap()
2213 }
2214
2215 #[tokio::test]
2216 async fn test_getbulk_vacm_filters_inaccessible_oids() {
2217 let agent = test_agent_with_restricted_vacm().await;
2218
2219 let mut ctx = test_ctx();
2220 ctx.pdu_type = PduType::GetBulkRequest;
2221 ctx.read_view = Some(Bytes::from_static(b"restricted"));
2222
2223 let pdu = Pdu {
2227 pdu_type: PduType::GetBulkRequest,
2228 request_id: 1,
2229 error_status: 0, error_index: 10, varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2232 };
2233
2234 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2235
2236 let returned_oids: Vec<&Oid> = response
2238 .varbinds
2239 .iter()
2240 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
2241 .map(|vb| &vb.oid)
2242 .collect();
2243
2244 assert!(
2246 returned_oids.contains(&&oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0)),
2247 "expected .99999.2.0 in response, got: {returned_oids:?}"
2248 );
2249 assert!(
2250 returned_oids.contains(&&oid!(1, 3, 6, 1, 4, 1, 99999, 4, 0)),
2251 "expected .99999.4.0 in response (walk must continue past denied OIDs), got: {returned_oids:?}"
2252 );
2253
2254 for &oid in &[
2256 &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
2257 &oid!(1, 3, 6, 1, 4, 1, 99999, 3, 0),
2258 &oid!(1, 3, 6, 1, 4, 1, 99999, 5, 0),
2259 ] {
2260 assert!(
2261 !returned_oids.contains(&oid),
2262 "GETBULK returned OID outside read view: {oid:?}"
2263 );
2264 }
2265 }
2266
2267 #[tokio::test]
2268 async fn test_getbulk_non_repeaters_vacm_filtered() {
2269 let agent = test_agent_with_restricted_vacm().await;
2270
2271 let mut ctx = test_ctx();
2272 ctx.pdu_type = PduType::GetBulkRequest;
2273 ctx.read_view = Some(Bytes::from_static(b"restricted"));
2274
2275 let pdu = Pdu {
2281 pdu_type: PduType::GetBulkRequest,
2282 request_id: 2,
2283 error_status: 2, error_index: 0, varbinds: vec![
2286 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null),
2287 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 4, 0), Value::Null),
2288 ],
2289 };
2290
2291 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2292
2293 assert_eq!(
2295 response.varbinds[0].oid,
2296 oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0)
2297 );
2298 assert!(matches!(response.varbinds[0].value, Value::Integer(2)));
2299
2300 assert_eq!(response.varbinds[1].value, Value::EndOfMibView);
2302 }
2303
2304 struct CountingRangeHandler {
2309 calls: Arc<std::sync::atomic::AtomicUsize>,
2310 }
2311
2312 impl MibHandler for CountingRangeHandler {
2313 fn get<'a>(
2314 &'a self,
2315 _ctx: &'a RequestContext,
2316 _oid: &'a Oid,
2317 ) -> BoxFuture<'a, HandlerResult<GetResult>> {
2318 Box::pin(async move { Ok(GetResult::NoSuchObject) })
2319 }
2320
2321 fn get_next<'a>(
2322 &'a self,
2323 _ctx: &'a RequestContext,
2324 _oid: &'a Oid,
2325 ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
2326 Box::pin(async move {
2327 let n = self.calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;
2331 let next = Oid::from_slice(&[1, 3, 6, 1, 4, 1, 99999, 1]).child(n as u32);
2332 Ok(GetNextResult::Value(VarBind::new(next, Value::Integer(1))))
2333 })
2334 }
2335 }
2336
2337 #[tokio::test]
2342 async fn test_getnext_vacm_denied_range_is_capped() {
2343 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2344 let agent = Agent::builder()
2345 .bind("127.0.0.1:0")
2346 .community(b"public")
2347 .handler(
2348 oid!(1, 3, 6, 1, 4, 1, 99999),
2349 Arc::new(CountingRangeHandler {
2350 calls: calls.clone(),
2351 }),
2352 )
2353 .vacm(|v| {
2356 v.group("public", SecurityModel::V2c, "readers")
2357 .access("readers", |a| a.read_view("restricted"))
2358 .view("restricted", |v| v.include(oid!(1, 3, 6, 1, 4, 1, 88888)))
2359 })
2360 .build()
2361 .await
2362 .unwrap();
2363
2364 let mut ctx = test_ctx();
2365 ctx.pdu_type = PduType::GetNextRequest;
2366 ctx.read_view = Some(Bytes::from_static(b"restricted"));
2367
2368 let pdu = Pdu {
2369 pdu_type: PduType::GetNextRequest,
2370 request_id: 1,
2371 error_status: 0,
2372 error_index: 0,
2373 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2374 };
2375
2376 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2377
2378 assert_eq!(response.varbinds.len(), 1);
2380 assert_eq!(response.varbinds[0].value, Value::EndOfMibView);
2381
2382 let total = calls.load(std::sync::atomic::Ordering::SeqCst);
2384 assert!(
2385 total <= MAX_VACM_SKIP_ITERATIONS,
2386 "handler called {total} times, expected <= {MAX_VACM_SKIP_ITERATIONS}"
2387 );
2388 }
2389
2390 struct ThreeOidHandler;
2392
2393 impl MibHandler for ThreeOidHandler {
2394 fn get<'a>(
2395 &'a self,
2396 _ctx: &'a RequestContext,
2397 oid: &'a Oid,
2398 ) -> BoxFuture<'a, HandlerResult<GetResult>> {
2399 Box::pin(async move {
2400 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
2401 return Ok(GetResult::Value(Value::Integer(1)));
2402 }
2403 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0) {
2404 return Ok(GetResult::Value(Value::Integer(2)));
2405 }
2406 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 3, 0) {
2407 return Ok(GetResult::Value(Value::Integer(3)));
2408 }
2409 Ok(GetResult::NoSuchObject)
2410 })
2411 }
2412
2413 fn get_next<'a>(
2414 &'a self,
2415 _ctx: &'a RequestContext,
2416 oid: &'a Oid,
2417 ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
2418 Box::pin(async move {
2419 let oid1 = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
2420 let oid2 = oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0);
2421 let oid3 = oid!(1, 3, 6, 1, 4, 1, 99999, 3, 0);
2422
2423 if oid < &oid1 {
2424 return Ok(GetNextResult::Value(VarBind::new(oid1, Value::Integer(1))));
2425 }
2426 if oid < &oid2 {
2427 return Ok(GetNextResult::Value(VarBind::new(oid2, Value::Integer(2))));
2428 }
2429 if oid < &oid3 {
2430 return Ok(GetNextResult::Value(VarBind::new(oid3, Value::Integer(3))));
2431 }
2432 Ok(GetNextResult::EndOfMibView)
2433 })
2434 }
2435 }
2436
2437 async fn test_agent_with_gap_vacm() -> Agent {
2440 Agent::builder()
2441 .bind("127.0.0.1:0")
2442 .community(b"public")
2443 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(ThreeOidHandler))
2444 .vacm(|v| {
2445 v.group("public", SecurityModel::V2c, "readers")
2446 .access("readers", |a| a.read_view("gap"))
2447 .view("gap", |v| {
2448 v.include(oid!(1, 3, 6, 1, 4, 1, 99999, 1))
2449 .include(oid!(1, 3, 6, 1, 4, 1, 99999, 3))
2450 })
2451 })
2452 .build()
2453 .await
2454 .unwrap()
2455 }
2456
2457 #[tokio::test]
2458 async fn test_getnext_vacm_skips_inaccessible_continues_walk() {
2459 let agent = test_agent_with_gap_vacm().await;
2463
2464 let mut ctx = test_ctx();
2465 ctx.pdu_type = PduType::GetNextRequest;
2466 ctx.read_view = Some(Bytes::from_static(b"gap"));
2467
2468 let pdu = Pdu {
2469 pdu_type: PduType::GetNextRequest,
2470 request_id: 1,
2471 error_status: 0,
2472 error_index: 0,
2473 varbinds: vec![VarBind::new(
2474 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
2475 Value::Null,
2476 )],
2477 };
2478
2479 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2480 assert_eq!(response.varbinds.len(), 1);
2481 assert_eq!(
2482 response.varbinds[0].oid,
2483 oid!(1, 3, 6, 1, 4, 1, 99999, 3, 0),
2484 "GETNEXT should skip denied .99999.2.0 and return accessible .99999.3.0"
2485 );
2486 assert!(matches!(response.varbinds[0].value, Value::Integer(3)));
2487 }
2488
2489 #[tokio::test]
2490 async fn test_getnext_vacm_all_remaining_denied_returns_end_of_mib() {
2491 let agent = test_agent_with_restricted_vacm().await;
2495
2496 let mut ctx = test_ctx();
2497 ctx.pdu_type = PduType::GetNextRequest;
2498 ctx.read_view = Some(Bytes::from_static(b"restricted"));
2499
2500 let pdu = Pdu {
2501 pdu_type: PduType::GetNextRequest,
2502 request_id: 1,
2503 error_status: 0,
2504 error_index: 0,
2505 varbinds: vec![VarBind::new(
2506 oid!(1, 3, 6, 1, 4, 1, 99999, 4, 0),
2507 Value::Null,
2508 )],
2509 };
2510
2511 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2512 assert_eq!(response.varbinds.len(), 1);
2513 assert_eq!(
2514 response.varbinds[0].value,
2515 Value::EndOfMibView,
2516 "GETNEXT should return EndOfMibView when all remaining OIDs are denied"
2517 );
2518 }
2519
2520 #[tokio::test]
2521 async fn test_getbulk_without_vacm_returns_all_oids() {
2522 let agent = Agent::builder()
2524 .bind("127.0.0.1:0")
2525 .community(b"public")
2526 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(TestHandler))
2527 .build()
2528 .await
2529 .unwrap();
2530
2531 let mut ctx = test_ctx();
2532 ctx.pdu_type = PduType::GetBulkRequest;
2533
2534 let pdu = Pdu {
2535 pdu_type: PduType::GetBulkRequest,
2536 request_id: 1,
2537 error_status: 0,
2538 error_index: 10,
2539 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2540 };
2541
2542 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2543
2544 assert!(
2546 response
2547 .varbinds
2548 .iter()
2549 .any(|vb| vb.oid == oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0))
2550 );
2551 assert!(
2552 response
2553 .varbinds
2554 .iter()
2555 .any(|vb| vb.oid == oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0))
2556 );
2557 }
2558
2559 #[tokio::test]
2560 async fn test_v1_getbulk_rejected() {
2561 let agent = Agent::builder()
2563 .bind("127.0.0.1:0")
2564 .community(b"public")
2565 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(TestHandler))
2566 .build()
2567 .await
2568 .unwrap();
2569
2570 let mut ctx = test_ctx();
2571 ctx.version = Version::V1;
2572 ctx.security_model = SecurityModel::V1;
2573 ctx.pdu_type = PduType::GetBulkRequest;
2574
2575 let pdu = Pdu {
2576 pdu_type: PduType::GetBulkRequest,
2577 request_id: 1,
2578 error_status: 0,
2579 error_index: 10,
2580 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2581 };
2582
2583 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2584 assert_eq!(
2585 ErrorStatus::from_i32(response.error_status),
2586 ErrorStatus::GenErr,
2587 "v1 GETBULK should be rejected"
2588 );
2589 }
2590
2591 struct Counter64Handler;
2593
2594 impl MibHandler for Counter64Handler {
2595 fn get<'a>(
2596 &'a self,
2597 _ctx: &'a RequestContext,
2598 oid: &'a Oid,
2599 ) -> BoxFuture<'a, HandlerResult<GetResult>> {
2600 Box::pin(async move {
2601 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
2602 return Ok(GetResult::Value(Value::Counter64(1_000_000_000_000)));
2603 }
2604 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0) {
2605 return Ok(GetResult::Value(Value::Integer(42)));
2606 }
2607 Ok(GetResult::NoSuchObject)
2608 })
2609 }
2610
2611 fn get_next<'a>(
2612 &'a self,
2613 _ctx: &'a RequestContext,
2614 oid: &'a Oid,
2615 ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
2616 Box::pin(async move {
2617 let oid1 = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
2618 let oid2 = oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0);
2619
2620 if oid < &oid1 {
2621 return Ok(GetNextResult::Value(VarBind::new(
2622 oid1,
2623 Value::Counter64(1_000_000_000_000),
2624 )));
2625 }
2626 if oid < &oid2 {
2627 return Ok(GetNextResult::Value(VarBind::new(oid2, Value::Integer(42))));
2628 }
2629 Ok(GetNextResult::EndOfMibView)
2630 })
2631 }
2632 }
2633
2634 async fn test_agent_with_counter64() -> Agent {
2635 Agent::builder()
2636 .bind("127.0.0.1:0")
2637 .community(b"public")
2638 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(Counter64Handler))
2639 .build()
2640 .await
2641 .unwrap()
2642 }
2643
2644 #[tokio::test]
2645 async fn test_v1_get_filters_counter64() {
2646 let agent = test_agent_with_counter64().await;
2649
2650 let mut ctx = test_ctx();
2651 ctx.version = Version::V1;
2652 ctx.security_model = SecurityModel::V1;
2653 ctx.pdu_type = PduType::GetRequest;
2654
2655 let pdu = Pdu {
2656 pdu_type: PduType::GetRequest,
2657 request_id: 1,
2658 error_status: 0,
2659 error_index: 0,
2660 varbinds: vec![VarBind::new(
2661 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
2662 Value::Null,
2663 )],
2664 };
2665
2666 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2667 assert_eq!(
2668 ErrorStatus::from_i32(response.error_status),
2669 ErrorStatus::NoSuchName,
2670 "v1 GET of Counter64 should return noSuchName"
2671 );
2672 }
2673
2674 #[tokio::test]
2675 async fn test_v2c_get_allows_counter64() {
2676 let agent = test_agent_with_counter64().await;
2678
2679 let ctx = test_ctx(); let pdu = Pdu {
2682 pdu_type: PduType::GetRequest,
2683 request_id: 1,
2684 error_status: 0,
2685 error_index: 0,
2686 varbinds: vec![VarBind::new(
2687 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
2688 Value::Null,
2689 )],
2690 };
2691
2692 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2693 assert_eq!(response.error_status, 0);
2694 assert!(matches!(response.varbinds[0].value, Value::Counter64(_)));
2695 }
2696
2697 #[tokio::test]
2698 async fn test_getbulk_respects_v3_msg_max_size() {
2699 let agent = Agent::builder()
2704 .bind("127.0.0.1:0")
2705 .community(b"public")
2706 .max_message_size(65507) .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(FiveOidHandler))
2708 .build()
2709 .await
2710 .unwrap();
2711
2712 let mut ctx_unlimited = test_ctx();
2714 ctx_unlimited.pdu_type = PduType::GetBulkRequest;
2715 ctx_unlimited.msg_max_size = None;
2716
2717 let pdu = Pdu {
2718 pdu_type: PduType::GetBulkRequest,
2719 request_id: 1,
2720 error_status: 0, error_index: 10, varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2723 };
2724
2725 let full_response = agent.dispatch_request(&ctx_unlimited, &pdu).await.unwrap();
2726 let full_count = full_response
2727 .varbinds
2728 .iter()
2729 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
2730 .count();
2731 assert!(
2732 full_count >= 3,
2733 "expected at least 3 data varbinds without limit, got {full_count}"
2734 );
2735
2736 let mut ctx_limited = test_ctx();
2741 ctx_limited.pdu_type = PduType::GetBulkRequest;
2742 ctx_limited.msg_max_size = Some(150); let limited_response = agent.dispatch_request(&ctx_limited, &pdu).await.unwrap();
2745 let limited_count = limited_response
2746 .varbinds
2747 .iter()
2748 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
2749 .count();
2750
2751 assert!(
2752 limited_count < full_count,
2753 "V3 msg_max_size should limit response: got {limited_count} varbinds (unlimited: {full_count})"
2754 );
2755 assert!(
2756 limited_count > 0,
2757 "should still return at least one varbind"
2758 );
2759 }
2760
2761 #[tokio::test]
2762 async fn test_response_overhead_scales_with_v3_security_level() {
2763 let engine_id = vec![0x11u8; 17];
2765 let agent = Agent::builder()
2766 .bind("127.0.0.1:0")
2767 .community(b"public")
2768 .engine_id(engine_id.clone())
2769 .build()
2770 .await
2771 .unwrap();
2772
2773 let v2c = test_ctx();
2776 assert_eq!(
2777 agent.response_overhead(&v2c),
2778 RESPONSE_OVERHEAD + v2c.security_name.len()
2779 );
2780
2781 let username = Bytes::from_static(b"user");
2782 let variable = 2 * engine_id.len() + username.len(); let mut noauth = test_ctx();
2785 noauth.version = Version::V3;
2786 noauth.security_level = SecurityLevel::NoAuthNoPriv;
2787 noauth.security_name = username.clone();
2788 assert_eq!(
2789 agent.response_overhead(&noauth),
2790 RESPONSE_OVERHEAD + variable
2791 );
2792
2793 let mut authnopriv = noauth.clone();
2794 authnopriv.security_level = SecurityLevel::AuthNoPriv;
2795 assert_eq!(
2796 agent.response_overhead(&authnopriv),
2797 RESPONSE_OVERHEAD + variable + V3_AUTH_OVERHEAD
2798 );
2799
2800 let mut authpriv = noauth.clone();
2801 authpriv.security_level = SecurityLevel::AuthPriv;
2802 assert_eq!(
2803 agent.response_overhead(&authpriv),
2804 RESPONSE_OVERHEAD + variable + V3_AUTH_OVERHEAD + V3_PRIV_OVERHEAD
2805 );
2806
2807 assert!(agent.response_overhead(&v2c) < agent.response_overhead(&noauth));
2809 assert!(agent.response_overhead(&noauth) < agent.response_overhead(&authnopriv));
2810 assert!(agent.response_overhead(&authnopriv) < agent.response_overhead(&authpriv));
2811 }
2812
2813 #[tokio::test]
2814 async fn test_response_overhead_counts_community_length() {
2815 let agent = Agent::builder()
2816 .bind("127.0.0.1:0")
2817 .community(b"public")
2818 .build()
2819 .await
2820 .unwrap();
2821
2822 let short = test_ctx();
2827 let mut long = test_ctx();
2828 long.security_name = Bytes::from(vec![b'x'; 200]);
2829
2830 assert_eq!(
2831 agent.response_overhead(&long) - agent.response_overhead(&short),
2832 long.security_name.len() - short.security_name.len()
2833 );
2834
2835 let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::Integer(0));
2838 let max = RESPONSE_OVERHEAD + short.security_name.len() + vb.encoded_size();
2839 assert!(Agent::response_fits(
2840 std::slice::from_ref(&vb),
2841 agent.response_overhead(&short),
2842 max
2843 ));
2844 assert!(!Agent::response_fits(
2845 std::slice::from_ref(&vb),
2846 agent.response_overhead(&long),
2847 max
2848 ));
2849 }
2850
2851 #[tokio::test]
2852 async fn test_getbulk_authpriv_budgets_for_wrapper() {
2853 let agent = Agent::builder()
2858 .bind("127.0.0.1:0")
2859 .community(b"public")
2860 .max_message_size(65507)
2861 .engine_id(vec![0x11u8; 17])
2862 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(FiveOidHandler))
2863 .build()
2864 .await
2865 .unwrap();
2866
2867 let pdu = Pdu {
2868 pdu_type: PduType::GetBulkRequest,
2869 request_id: 1,
2870 error_status: 0, error_index: 10, varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2873 };
2874
2875 let limit = 200;
2878
2879 let mut v2c = test_ctx();
2880 v2c.pdu_type = PduType::GetBulkRequest;
2881 v2c.msg_max_size = Some(limit);
2882 let v2c_count = agent
2883 .dispatch_request(&v2c, &pdu)
2884 .await
2885 .unwrap()
2886 .varbinds
2887 .iter()
2888 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
2889 .count();
2890
2891 let mut authpriv = test_ctx();
2892 authpriv.version = Version::V3;
2893 authpriv.security_level = SecurityLevel::AuthPriv;
2894 authpriv.security_name = Bytes::from_static(b"user");
2895 authpriv.pdu_type = PduType::GetBulkRequest;
2896 authpriv.msg_max_size = Some(limit);
2897 let authpriv_count = agent
2898 .dispatch_request(&authpriv, &pdu)
2899 .await
2900 .unwrap()
2901 .varbinds
2902 .iter()
2903 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
2904 .count();
2905
2906 assert!(
2907 authpriv_count < v2c_count,
2908 "authPriv should budget fewer varbinds than v2c for the same \
2909 msgMaxSize: authpriv={authpriv_count}, v2c={v2c_count}"
2910 );
2911 }
2912
2913 struct MixedSizeHandler;
2916
2917 impl MibHandler for MixedSizeHandler {
2918 fn get<'a>(
2919 &'a self,
2920 _ctx: &'a RequestContext,
2921 oid: &'a Oid,
2922 ) -> BoxFuture<'a, HandlerResult<GetResult>> {
2923 Box::pin(async move {
2924 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0)
2925 || oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0)
2926 {
2927 return Ok(GetResult::Value(Value::OctetString(Bytes::from(vec![
2928 0xAB;
2929 200
2930 ]))));
2931 }
2932 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 9, 0) {
2933 return Ok(GetResult::Value(Value::Integer(7)));
2934 }
2935 Ok(GetResult::NoSuchObject)
2936 })
2937 }
2938
2939 fn get_next<'a>(
2940 &'a self,
2941 _ctx: &'a RequestContext,
2942 oid: &'a Oid,
2943 ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
2944 Box::pin(async move {
2945 let big1 = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
2946 let big2 = oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0);
2947 let small = oid!(1, 3, 6, 1, 4, 1, 99999, 9, 0);
2948 if oid < &big1 {
2949 return Ok(GetNextResult::Value(VarBind::new(
2950 big1,
2951 Value::OctetString(Bytes::from(vec![0xAB; 200])),
2952 )));
2953 }
2954 if oid < &big2 {
2955 return Ok(GetNextResult::Value(VarBind::new(
2956 big2,
2957 Value::OctetString(Bytes::from(vec![0xAB; 200])),
2958 )));
2959 }
2960 if oid < &small {
2961 return Ok(GetNextResult::Value(VarBind::new(small, Value::Integer(7))));
2962 }
2963 Ok(GetNextResult::EndOfMibView)
2964 })
2965 }
2966 }
2967
2968 #[tokio::test]
2969 async fn test_getbulk_dropped_non_repeater_omits_repeaters() {
2970 let agent = Agent::builder()
2976 .bind("127.0.0.1:0")
2977 .community(b"public")
2978 .max_message_size(65507)
2979 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(MixedSizeHandler))
2980 .without_builtin_handlers()
2981 .build()
2982 .await
2983 .unwrap();
2984
2985 let big_vb = VarBind::new(
2988 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
2989 Value::OctetString(Bytes::from(vec![0xAB; 200])),
2990 );
2991 let small_vb = VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 9, 0), Value::Integer(7));
2992 let max = RESPONSE_OVERHEAD + big_vb.encoded_size() + small_vb.encoded_size();
2993
2994 let mut ctx = test_ctx();
2995 ctx.pdu_type = PduType::GetBulkRequest;
2996 ctx.msg_max_size = Some(max as u32);
2997
2998 let pdu = Pdu {
2999 pdu_type: PduType::GetBulkRequest,
3000 request_id: 1,
3001 error_status: 2, error_index: 2, varbinds: vec![
3004 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 1), Value::Null),
3005 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 2), Value::Null),
3006 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 9), Value::Null),
3007 ],
3008 };
3009
3010 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3011
3012 assert_eq!(
3014 response.varbinds.len(),
3015 1,
3016 "expected exactly the non-repeater prefix, got {:?}",
3017 response
3018 .varbinds
3019 .iter()
3020 .map(|vb| &vb.oid)
3021 .collect::<Vec<_>>()
3022 );
3023 assert_eq!(
3024 response.varbinds[0].oid,
3025 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0)
3026 );
3027 assert!(
3029 !response
3030 .varbinds
3031 .iter()
3032 .any(|vb| vb.oid == oid!(1, 3, 6, 1, 4, 1, 99999, 9, 0)),
3033 "repeater varbind leaked into response after a dropped non-repeater"
3034 );
3035 }
3036
3037 #[tokio::test]
3038 async fn test_getbulk_too_big_has_empty_varbinds() {
3039 let agent = Agent::builder()
3042 .bind("127.0.0.1:0")
3043 .community(b"public")
3044 .max_message_size(65507)
3045 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(MixedSizeHandler))
3046 .without_builtin_handlers()
3047 .build()
3048 .await
3049 .unwrap();
3050
3051 let mut ctx = test_ctx();
3052 ctx.pdu_type = PduType::GetBulkRequest;
3053 ctx.msg_max_size = Some((RESPONSE_OVERHEAD - 1) as u32);
3055
3056 let pdu = Pdu {
3057 pdu_type: PduType::GetBulkRequest,
3058 request_id: 1,
3059 error_status: 2, error_index: 2, varbinds: vec![
3062 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 1), Value::Null),
3063 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 2), Value::Null),
3064 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 9), Value::Null),
3065 ],
3066 };
3067
3068 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3069
3070 assert_eq!(response.error_status, ErrorStatus::TooBig.as_i32());
3071 assert!(
3072 response.varbinds.is_empty(),
3073 "tooBig Response must have empty varbinds, got {}",
3074 response.varbinds.len()
3075 );
3076 }
3077
3078 #[tokio::test]
3079 async fn test_getbulk_too_big_zero_non_repeaters_first_repeater_oversized() {
3080 let agent = Agent::builder()
3087 .bind("127.0.0.1:0")
3088 .community(b"public")
3089 .max_message_size(65507)
3090 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(MixedSizeHandler))
3091 .without_builtin_handlers()
3092 .build()
3093 .await
3094 .unwrap();
3095
3096 let big_vb = VarBind::new(
3101 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
3102 Value::OctetString(Bytes::from(vec![0xAB; 200])),
3103 );
3104 let max = RESPONSE_OVERHEAD + big_vb.encoded_size() - 1;
3105
3106 let mut ctx = test_ctx();
3107 ctx.pdu_type = PduType::GetBulkRequest;
3108 ctx.msg_max_size = Some(max as u32);
3109
3110 let pdu = Pdu {
3111 pdu_type: PduType::GetBulkRequest,
3112 request_id: 1,
3113 error_status: 0, error_index: 5, varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 1), Value::Null)],
3116 };
3117
3118 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3119
3120 assert_eq!(
3121 response.error_status,
3122 ErrorStatus::TooBig.as_i32(),
3123 "first oversized repeater varbind (non_repeaters == 0) must yield tooBig"
3124 );
3125 assert!(
3126 response.varbinds.is_empty(),
3127 "tooBig Response must have empty varbinds, got {}",
3128 response.varbinds.len()
3129 );
3130 }
3131
3132 #[tokio::test]
3133 async fn test_getbulk_msg_max_size_none_uses_agent_max() {
3134 let agent = Agent::builder()
3137 .bind("127.0.0.1:0")
3138 .community(b"public")
3139 .max_message_size(65507)
3140 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(FiveOidHandler))
3141 .without_builtin_handlers()
3142 .build()
3143 .await
3144 .unwrap();
3145
3146 let mut ctx = test_ctx();
3147 ctx.pdu_type = PduType::GetBulkRequest;
3148 ctx.msg_max_size = None; let pdu = Pdu {
3151 pdu_type: PduType::GetBulkRequest,
3152 request_id: 1,
3153 error_status: 0,
3154 error_index: 10,
3155 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
3156 };
3157
3158 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3159 let data_count = response
3160 .varbinds
3161 .iter()
3162 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
3163 .count();
3164 assert_eq!(
3165 data_count, 5,
3166 "all 5 OIDs should be returned without msg_max_size limit"
3167 );
3168 }
3169
3170 #[tokio::test]
3171 async fn test_v1_getnext_skips_counter64() {
3172 let agent = test_agent_with_counter64().await;
3176
3177 let mut ctx = test_ctx();
3178 ctx.version = Version::V1;
3179 ctx.security_model = SecurityModel::V1;
3180 ctx.pdu_type = PduType::GetNextRequest;
3181
3182 let pdu = Pdu {
3183 pdu_type: PduType::GetNextRequest,
3184 request_id: 1,
3185 error_status: 0,
3186 error_index: 0,
3187 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
3188 };
3189
3190 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3191 assert_eq!(response.error_status, 0, "should succeed");
3192 assert_eq!(
3193 response.varbinds[0].oid,
3194 oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0),
3195 "should skip Counter64 and return next non-Counter64 OID"
3196 );
3197 assert!(matches!(response.varbinds[0].value, Value::Integer(42)));
3198 }
3199
3200 #[test]
3201 fn test_engine_time_no_overflow() {
3202 let (boots, time) = crate::v3::compute_engine_boots_time(1, 1000);
3204 assert_eq!(boots, 1);
3205 assert_eq!(time, 1000);
3206 }
3207
3208 #[test]
3209 fn test_engine_time_zero_elapsed() {
3210 let (boots, time) = crate::v3::compute_engine_boots_time(1, 0);
3211 assert_eq!(boots, 1);
3212 assert_eq!(time, 0);
3213 }
3214
3215 #[test]
3216 fn test_engine_time_just_below_max() {
3217 let max = crate::v3::MAX_ENGINE_TIME;
3218 let (boots, time) = crate::v3::compute_engine_boots_time(1, u64::from(max) - 1);
3219 assert_eq!(boots, 1);
3220 assert_eq!(time, max - 1);
3221 }
3222
3223 #[test]
3224 fn test_engine_time_at_max_wraps() {
3225 let max = crate::v3::MAX_ENGINE_TIME;
3227 let (boots, time) = crate::v3::compute_engine_boots_time(1, u64::from(max));
3228 assert_eq!(
3229 boots, 2,
3230 "boots should increment when elapsed reaches MAX_ENGINE_TIME"
3231 );
3232 assert_eq!(time, 0, "time should wrap to 0");
3233 }
3234
3235 #[test]
3236 fn test_engine_time_past_max() {
3237 let max = crate::v3::MAX_ENGINE_TIME;
3239 let (boots, time) = crate::v3::compute_engine_boots_time(1, u64::from(max) + 500);
3240 assert_eq!(boots, 2);
3241 assert_eq!(time, 500);
3242 }
3243
3244 #[test]
3245 fn test_engine_time_multiple_wraps() {
3246 let max = crate::v3::MAX_ENGINE_TIME;
3248 let elapsed = u64::from(max) * 3 + 42;
3249 let (boots, time) = crate::v3::compute_engine_boots_time(1, elapsed);
3250 assert_eq!(boots, 4, "base 1 + 3 wraps = 4");
3251 assert_eq!(time, 42);
3252 }
3253
3254 #[test]
3255 fn test_engine_time_boots_capped_at_max() {
3256 let max = crate::v3::MAX_ENGINE_TIME;
3258 let elapsed = u64::from(max) * u64::from(max); let (boots, _time) = crate::v3::compute_engine_boots_time(1, elapsed);
3260 assert_eq!(boots, max, "boots should be capped at MAX_ENGINE_TIME");
3261 }
3262
3263 #[test]
3264 fn test_engine_time_base_boots_preserved() {
3265 let max = crate::v3::MAX_ENGINE_TIME;
3267 let (boots, time) = crate::v3::compute_engine_boots_time(5, u64::from(max) + 100);
3268 assert_eq!(boots, 6, "base 5 + 1 wrap = 6");
3269 assert_eq!(time, 100);
3270 }
3271
3272 #[test]
3273 fn test_engine_time_high_base_boots_capped() {
3274 let max = crate::v3::MAX_ENGINE_TIME;
3276 let (boots, _time) = crate::v3::compute_engine_boots_time(max - 1, u64::from(max) * 2);
3277 assert_eq!(boots, max, "should cap at MAX_ENGINE_TIME, not overflow");
3278 }
3279
3280 #[tokio::test]
3281 async fn test_engine_boots_builder() {
3282 let agent = Agent::builder()
3284 .bind("127.0.0.1:0")
3285 .community(b"public")
3286 .engine_boots(42)
3287 .build()
3288 .await
3289 .unwrap();
3290
3291 assert_eq!(agent.engine_boots(), 42);
3292 }
3293
3294 #[tokio::test]
3295 async fn test_zero_max_concurrent_requests_rejected() {
3296 let result = Agent::builder()
3299 .bind("127.0.0.1:0")
3300 .community(b"public")
3301 .max_concurrent_requests(Some(0))
3302 .build()
3303 .await;
3304
3305 let err = result.err().expect("expected build to fail");
3306 assert!(matches!(*err, Error::Config(_)));
3307 }
3308
3309 #[tokio::test]
3310 async fn test_engine_boots_default() {
3311 let agent = Agent::builder()
3313 .bind("127.0.0.1:0")
3314 .community(b"public")
3315 .build()
3316 .await
3317 .unwrap();
3318
3319 assert_eq!(agent.engine_boots(), 1);
3320 }
3321
3322 #[tokio::test]
3323 async fn test_usm_counter_accessors_default_zero() {
3324 let agent = Agent::builder()
3325 .bind("127.0.0.1:0")
3326 .community(b"public")
3327 .build()
3328 .await
3329 .unwrap();
3330
3331 assert_eq!(agent.usm_unsupported_sec_levels(), 0);
3332 assert_eq!(agent.usm_decryption_errors(), 0);
3333 }
3334
3335 #[test]
3336 fn test_builtin_mib_without_single() {
3337 let builder = AgentBuilder::new().without_builtin_handler(BuiltinMib::UsmStats);
3338 assert!(builder.disabled_builtins.contains(&BuiltinMib::UsmStats));
3339 assert!(!builder.disabled_builtins.contains(&BuiltinMib::SnmpEngine));
3340 assert!(!builder.disabled_builtins.contains(&BuiltinMib::MpdStats));
3341 }
3342
3343 #[test]
3344 fn test_builtin_mib_without_all() {
3345 let builder = AgentBuilder::new().without_builtin_handlers();
3346 assert!(builder.disabled_builtins.contains(&BuiltinMib::SnmpEngine));
3347 assert!(builder.disabled_builtins.contains(&BuiltinMib::UsmStats));
3348 assert!(builder.disabled_builtins.contains(&BuiltinMib::MpdStats));
3349 }
3350
3351 #[tokio::test]
3352 async fn test_uptime_hundredths() {
3353 let agent = Agent::builder()
3354 .bind("127.0.0.1:0")
3355 .community(b"public")
3356 .build()
3357 .await
3358 .unwrap();
3359
3360 let uptime = agent.uptime_hundredths();
3361 assert!(
3362 uptime < 100,
3363 "uptime should be less than 1 second, got {uptime}"
3364 );
3365
3366 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
3367 let uptime2 = agent.uptime_hundredths();
3368 assert!(uptime2 > uptime, "uptime should increase after delay");
3369 }
3370
3371 #[tokio::test]
3372 async fn test_builtin_handlers_registered_by_default() {
3373 let agent = Agent::builder()
3374 .bind("127.0.0.1:0")
3375 .community(b"public")
3376 .build()
3377 .await
3378 .unwrap();
3379
3380 let ctx = test_ctx();
3381
3382 let handler = agent
3384 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 10, 2, 1, 4, 0))
3385 .expect("snmpEngine handler should be registered");
3386 let get_result = handler
3387 .handler
3388 .get(&ctx, &oid!(1, 3, 6, 1, 6, 3, 10, 2, 1, 4, 0))
3389 .await
3390 .unwrap();
3391 assert!(matches!(get_result, GetResult::Value(Value::Integer(_))));
3392
3393 let handler = agent
3395 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 5, 0))
3396 .expect("USM stats handler should be registered");
3397 let get_result = handler
3398 .handler
3399 .get(&ctx, &oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 5, 0))
3400 .await
3401 .unwrap();
3402 assert!(matches!(get_result, GetResult::Value(Value::Counter32(0))));
3403
3404 let handler = agent
3406 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 11, 2, 1, 1, 0))
3407 .expect("MPD stats handler should be registered");
3408 let get_result = handler
3409 .handler
3410 .get(&ctx, &oid!(1, 3, 6, 1, 6, 3, 11, 2, 1, 1, 0))
3411 .await
3412 .unwrap();
3413 assert!(matches!(get_result, GetResult::Value(Value::Counter32(0))));
3414 }
3415
3416 #[tokio::test]
3417 async fn test_builtin_handlers_disabled() {
3418 let agent = Agent::builder()
3419 .bind("127.0.0.1:0")
3420 .community(b"public")
3421 .without_builtin_handlers()
3422 .build()
3423 .await
3424 .unwrap();
3425
3426 assert!(
3427 agent
3428 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 10, 2, 1, 1, 0))
3429 .is_none()
3430 );
3431 assert!(
3432 agent
3433 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 1, 0))
3434 .is_none()
3435 );
3436 assert!(
3437 agent
3438 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 11, 2, 1, 1, 0))
3439 .is_none()
3440 );
3441 }
3442
3443 #[tokio::test]
3444 async fn test_builtin_handler_selective_disable() {
3445 let agent = Agent::builder()
3446 .bind("127.0.0.1:0")
3447 .community(b"public")
3448 .without_builtin_handler(BuiltinMib::UsmStats)
3449 .build()
3450 .await
3451 .unwrap();
3452
3453 assert!(
3454 agent
3455 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 10, 2, 1, 1, 0))
3456 .is_some()
3457 );
3458 assert!(
3459 agent
3460 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 1, 0))
3461 .is_none()
3462 );
3463 assert!(
3464 agent
3465 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 11, 2, 1, 1, 0))
3466 .is_some()
3467 );
3468 }
3469
3470 async fn small_limit_agent() -> Agent {
3473 Agent::builder()
3474 .bind("127.0.0.1:0")
3475 .community(b"public")
3476 .max_message_size(150)
3477 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(FiveOidHandler))
3478 .without_builtin_handlers()
3479 .build()
3480 .await
3481 .unwrap()
3482 }
3483
3484 fn five_varbinds() -> Vec<VarBind> {
3485 (1u32..=5)
3486 .map(|i| VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, i, 0), Value::Null))
3487 .collect()
3488 }
3489
3490 #[tokio::test]
3491 async fn test_get_too_big_returns_toobig_response() {
3492 let agent = small_limit_agent().await;
3493 let ctx = test_ctx();
3494
3495 let pdu = Pdu {
3498 pdu_type: PduType::GetRequest,
3499 request_id: 1,
3500 error_status: 0,
3501 error_index: 0,
3502 varbinds: five_varbinds(),
3503 };
3504
3505 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3506 assert_eq!(response.error_status, ErrorStatus::TooBig.as_i32());
3507 assert_eq!(response.error_index, 0);
3508 assert!(response.varbinds.is_empty());
3509 }
3510
3511 #[tokio::test]
3512 async fn test_get_too_big_v1_echoes_request_varbinds() {
3513 let agent = small_limit_agent().await;
3514
3515 let mut ctx = test_ctx();
3518 ctx.version = Version::V1;
3519 ctx.security_model = SecurityModel::V1;
3520
3521 let request_varbinds = five_varbinds();
3522 let pdu = Pdu {
3523 pdu_type: PduType::GetRequest,
3524 request_id: 1,
3525 error_status: 0,
3526 error_index: 0,
3527 varbinds: request_varbinds.clone(),
3528 };
3529
3530 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3531 assert_eq!(response.error_status, ErrorStatus::TooBig.as_i32());
3532 assert_eq!(response.error_index, 0);
3533 assert_eq!(response.varbinds, request_varbinds);
3534
3535 let v2c_response = agent.dispatch_request(&test_ctx(), &pdu).await.unwrap();
3537 assert_eq!(v2c_response.error_status, ErrorStatus::TooBig.as_i32());
3538 assert!(v2c_response.varbinds.is_empty());
3539 }
3540
3541 #[tokio::test]
3542 async fn test_get_within_limit_returns_response() {
3543 let agent = small_limit_agent().await;
3544 let ctx = test_ctx();
3545
3546 let pdu = Pdu {
3548 pdu_type: PduType::GetRequest,
3549 request_id: 1,
3550 error_status: 0,
3551 error_index: 0,
3552 varbinds: vec![VarBind::new(
3553 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
3554 Value::Null,
3555 )],
3556 };
3557
3558 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3559 assert_eq!(response.error_status, 0);
3560 assert_eq!(response.varbinds.len(), 1);
3561 assert!(matches!(response.varbinds[0].value, Value::Integer(1)));
3562 }
3563
3564 #[tokio::test]
3565 async fn test_getnext_too_big_returns_toobig_response() {
3566 let agent = small_limit_agent().await;
3567 let mut ctx = test_ctx();
3568 ctx.pdu_type = PduType::GetNextRequest;
3569
3570 let pdu = Pdu {
3571 pdu_type: PduType::GetNextRequest,
3572 request_id: 1,
3573 error_status: 0,
3574 error_index: 0,
3575 varbinds: five_varbinds(),
3576 };
3577
3578 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3579 assert_eq!(response.error_status, ErrorStatus::TooBig.as_i32());
3580 assert_eq!(response.error_index, 0);
3581 assert!(response.varbinds.is_empty());
3582 }
3583
3584 #[tokio::test]
3585 async fn test_inform_too_big_returns_toobig_response() {
3586 let agent = small_limit_agent().await;
3587 let mut ctx = test_ctx();
3588 ctx.pdu_type = PduType::InformRequest;
3589
3590 let big = Value::OctetString(Bytes::from(vec![0xABu8; 256]));
3596 let pdu = Pdu {
3597 pdu_type: PduType::InformRequest,
3598 request_id: 1,
3599 error_status: 0,
3600 error_index: 0,
3601 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0), big)],
3602 };
3603
3604 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3605 assert_eq!(response.error_status, ErrorStatus::TooBig.as_i32());
3606 assert_eq!(response.error_index, 0);
3607 assert!(response.varbinds.is_empty());
3608 }
3609
3610 #[tokio::test]
3611 async fn test_inform_within_limit_echoes_varbinds() {
3612 let agent = small_limit_agent().await;
3613 let mut ctx = test_ctx();
3614 ctx.pdu_type = PduType::InformRequest;
3615
3616 let pdu = Pdu {
3619 pdu_type: PduType::InformRequest,
3620 request_id: 7,
3621 error_status: 0,
3622 error_index: 0,
3623 varbinds: vec![VarBind::new(
3624 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
3625 Value::Integer(42),
3626 )],
3627 };
3628
3629 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3630 assert_eq!(response.pdu_type, PduType::Response);
3631 assert_eq!(response.error_status, 0);
3632 assert_eq!(response.request_id, 7);
3633 assert_eq!(response.varbinds.len(), 1);
3634 assert!(matches!(response.varbinds[0].value, Value::Integer(42)));
3635 }
3636
3637 #[tokio::test]
3638 async fn test_getnext_within_limit_returns_response() {
3639 let agent = small_limit_agent().await;
3640 let mut ctx = test_ctx();
3641 ctx.pdu_type = PduType::GetNextRequest;
3642
3643 let pdu = Pdu {
3644 pdu_type: PduType::GetNextRequest,
3645 request_id: 1,
3646 error_status: 0,
3647 error_index: 0,
3648 varbinds: vec![VarBind::new(
3649 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
3650 Value::Null,
3651 )],
3652 };
3653
3654 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3655 assert_eq!(response.error_status, 0);
3656 assert_eq!(response.varbinds.len(), 1);
3657 assert_eq!(
3658 response.varbinds[0].oid,
3659 oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0)
3660 );
3661 }
3662}