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::oid;
100use crate::oid::Oid;
101use crate::pdu::{Pdu, PduType};
102use crate::util::bind_udp_socket;
103use crate::v3::process::UsmStats;
104use crate::v3::{AuthoritativeEngine, UsmConfig};
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 authoritative_engine: Option<AuthoritativeEngine>,
225 max_message_size: usize,
226 max_concurrent_requests: Option<usize>,
227 recv_buffer_size: Option<usize>,
228 vacm: Option<VacmConfig>,
229 cancel: Option<CancellationToken>,
230 trap_sinks: Vec<(String, crate::client::Auth)>,
231 inform_timeout: Duration,
232 inform_retry: crate::client::Retry,
233 disabled_builtins: HashSet<BuiltinMib>,
234}
235
236impl AgentBuilder {
237 #[must_use]
247 pub fn new() -> Self {
248 Self {
249 bind_addr: "0.0.0.0:161".to_string(),
250 communities: Vec::new(),
251 usm_users: HashMap::new(),
252 handlers: Vec::new(),
253 authoritative_engine: None,
254 max_message_size: DEFAULT_MAX_MESSAGE_SIZE,
255 max_concurrent_requests: Some(1000),
256 recv_buffer_size: Some(4 * 1024 * 1024), vacm: None,
258 cancel: None,
259 trap_sinks: Vec::new(),
260 inform_timeout: Duration::from_secs(5),
261 inform_retry: crate::client::Retry::default(),
262 disabled_builtins: HashSet::new(),
263 }
264 }
265
266 #[must_use]
304 pub fn bind(mut self, addr: impl Into<String>) -> Self {
305 self.bind_addr = addr.into();
306 self
307 }
308
309 #[must_use]
330 pub fn community(mut self, community: &[u8]) -> Self {
331 self.communities.push(community.to_vec());
332 self
333 }
334
335 #[must_use]
353 pub fn communities<I, C>(mut self, communities: I) -> Self
354 where
355 I: IntoIterator<Item = C>,
356 C: AsRef<[u8]>,
357 {
358 for c in communities {
359 self.communities.push(c.as_ref().to_vec());
360 }
361 self
362 }
363
364 #[must_use]
409 pub fn usm_user<F>(mut self, username: impl Into<Bytes>, configure: F) -> Self
410 where
411 F: FnOnce(UsmConfig) -> UsmConfig,
412 {
413 let username_bytes: Bytes = username.into();
414 let config = configure(UsmConfig::new(username_bytes.clone()));
415 self.usm_users.insert(username_bytes, config);
416 self
417 }
418
419 #[must_use]
449 pub fn authoritative_engine(mut self, engine: AuthoritativeEngine) -> Self {
450 self.authoritative_engine = Some(engine);
451 self
452 }
453
454 #[cfg(test)]
455 pub(crate) fn engine_id(mut self, engine_id: impl Into<Vec<u8>>) -> Self {
456 let boots = self
457 .authoritative_engine
458 .as_ref()
459 .map_or(1, AuthoritativeEngine::engine_boots);
460 self.authoritative_engine = Some(AuthoritativeEngine::for_test(engine_id.into(), boots));
461 self
462 }
463
464 #[cfg(test)]
465 pub(crate) fn engine_boots(mut self, boots: u32) -> Self {
466 let engine_id = self
467 .authoritative_engine
468 .as_ref()
469 .map(|engine| engine.engine_id().to_vec())
470 .unwrap_or_else(|| crate::v3::generate_engine_id().to_vec());
471 self.authoritative_engine = Some(AuthoritativeEngine::for_test(engine_id, boots));
472 self
473 }
474
475 #[must_use]
483 pub fn max_message_size(mut self, size: usize) -> Self {
484 self.max_message_size = size;
485 self
486 }
487
488 #[must_use]
499 pub fn max_concurrent_requests(mut self, limit: Option<usize>) -> Self {
500 self.max_concurrent_requests = limit;
501 self
502 }
503
504 #[must_use]
511 pub fn recv_buffer_size(mut self, size: Option<usize>) -> Self {
512 self.recv_buffer_size = size;
513 self
514 }
515
516 #[must_use]
557 pub fn handler(mut self, prefix: Oid, handler: Arc<dyn MibHandler>) -> Self {
558 self.handlers.push(RegisteredHandler { prefix, handler });
559 self
560 }
561
562 #[must_use]
601 pub fn vacm<F>(mut self, configure: F) -> Self
602 where
603 F: FnOnce(VacmBuilder) -> VacmBuilder,
604 {
605 let builder = VacmBuilder::new();
606 self.vacm = Some(configure(builder).build());
607 self
608 }
609
610 #[must_use]
614 pub fn cancel(mut self, token: CancellationToken) -> Self {
615 self.cancel = Some(token);
616 self
617 }
618
619 #[must_use]
658 pub fn trap_sink(
659 mut self,
660 dest: impl Into<String>,
661 auth: impl Into<crate::client::Auth>,
662 ) -> Self {
663 self.trap_sinks.push((dest.into(), auth.into()));
664 self
665 }
666
667 #[must_use]
671 pub fn inform_timeout(mut self, timeout: Duration) -> Self {
672 self.inform_timeout = timeout;
673 self
674 }
675
676 #[must_use]
681 pub fn inform_retry(mut self, retry: crate::client::Retry) -> Self {
682 self.inform_retry = retry;
683 self
684 }
685
686 #[must_use]
692 pub fn without_builtin_handler(mut self, mib: BuiltinMib) -> Self {
693 self.disabled_builtins.insert(mib);
694 self
695 }
696
697 #[must_use]
703 pub fn without_builtin_handlers(mut self) -> Self {
704 self.disabled_builtins.insert(BuiltinMib::SnmpEngine);
705 self.disabled_builtins.insert(BuiltinMib::UsmStats);
706 self.disabled_builtins.insert(BuiltinMib::MpdStats);
707 self
708 }
709
710 pub async fn build(mut self) -> Result<Agent> {
715 for config in self.usm_users.values_mut() {
718 config.precompute_master_keys();
719 }
720
721 let bind_addr: std::net::SocketAddr = self.bind_addr.parse().map_err(|_| {
722 Error::Config(format!("invalid bind address: {}", self.bind_addr).into())
723 })?;
724
725 let socket = bind_udp_socket(bind_addr, self.recv_buffer_size, None, false)
726 .await
727 .map_err(|e| Error::Network {
728 target: bind_addr,
729 source: e,
730 })?;
731
732 let local_addr = socket.local_addr().map_err(|e| Error::Network {
733 target: bind_addr,
734 source: e,
735 })?;
736
737 let socket_state =
738 UdpSocketState::new(UdpSockRef::from(&socket)).map_err(|e| Error::Network {
739 target: bind_addr,
740 source: e,
741 })?;
742
743 let requires_authoritative_engine = !self.usm_users.is_empty()
744 || self
745 .trap_sinks
746 .iter()
747 .any(|(_, auth)| matches!(auth, crate::client::Auth::Usm(_)));
748 let (authoritative_engine, engine_id, engine_boots) = match self.authoritative_engine {
749 Some(engine) => {
750 let (engine_boots, _) = engine.current_boots_time()?;
751 let engine_id = Bytes::copy_from_slice(engine.engine_id());
752 (Some(engine), engine_id, engine_boots)
753 }
754 None if requires_authoritative_engine => {
755 return Err(Error::Config(
756 "authoritative engine state is required for SNMPv3 agent roles".into(),
757 )
758 .boxed());
759 }
760 None => (None, crate::v3::generate_engine_id(), 1),
761 };
762
763 let cancel = self.cancel.unwrap_or_default();
764
765 if self.max_concurrent_requests == Some(0) {
768 return Err(
769 Error::Config("max_concurrent_requests must be greater than 0".into()).into(),
770 );
771 }
772 let concurrency_limit = self
773 .max_concurrent_requests
774 .map(|n| Arc::new(Semaphore::new(n)));
775
776 let mut trap_sinks = Vec::with_capacity(self.trap_sinks.len());
778 for (dest_str, auth) in self.trap_sinks {
779 let dest: SocketAddr = dest_str.parse().map_err(|_| {
780 Error::Config(format!("invalid trap sink address: {dest_str}").into())
781 })?;
782 trap_sinks.push(notification::TrapSink::new(
783 dest,
784 auth,
785 self.inform_timeout,
786 self.inform_retry.clone(),
787 ));
788 }
789
790 let state = Arc::new(AgentState {
791 authoritative_engine,
792 engine_id,
793 engine_boots: AtomicU32::new(engine_boots),
794 engine_time: AtomicU32::new(0),
795 engine_start: Instant::now(),
796 engine_boots_base: engine_boots,
797 max_message_size: self.max_message_size,
798 snmp_invalid_msgs: AtomicU32::new(0),
799 snmp_unknown_security_models: AtomicU32::new(0),
800 snmp_silent_drops: AtomicU32::new(0),
801 snmp_unknown_contexts: AtomicU32::new(0),
802 usm_stats: UsmStats::default(),
803 });
804
805 if !self.disabled_builtins.contains(&BuiltinMib::SnmpEngine) {
807 self.handlers.push(RegisteredHandler {
808 prefix: oid!(1, 3, 6, 1, 6, 3, 10, 2, 1),
809 handler: Arc::new(builtins::SnmpEngineHandler {
810 state: Arc::clone(&state),
811 }),
812 });
813 }
814 if !self.disabled_builtins.contains(&BuiltinMib::UsmStats) {
815 self.handlers.push(RegisteredHandler {
816 prefix: oid!(1, 3, 6, 1, 6, 3, 15, 1, 1),
817 handler: Arc::new(builtins::UsmStatsHandler {
818 state: Arc::clone(&state),
819 }),
820 });
821 }
822 if !self.disabled_builtins.contains(&BuiltinMib::MpdStats) {
823 self.handlers.push(RegisteredHandler {
824 prefix: oid!(1, 3, 6, 1, 6, 3, 11, 2, 1),
825 handler: Arc::new(builtins::MpdStatsHandler {
826 state: Arc::clone(&state),
827 }),
828 });
829 }
830
831 self.handlers
833 .sort_by_key(|h| std::cmp::Reverse(h.prefix.len()));
834
835 Ok(Agent {
836 inner: Arc::new(AgentInner {
837 socket: Arc::new(socket),
838 socket_state,
839 local_addr,
840 communities: self.communities,
841 usm_users: self.usm_users,
842 handlers: self.handlers,
843 state,
844 salt_counter: SaltCounter::new(),
845 concurrency_limit,
846 vacm: self.vacm,
847 cancel,
848 trap_sinks,
849 notification_id: std::sync::atomic::AtomicI32::new(1),
850 }),
851 })
852 }
853}
854
855impl Default for AgentBuilder {
856 fn default() -> Self {
857 Self::new()
858 }
859}
860
861pub(crate) struct AgentState {
863 pub(crate) authoritative_engine: Option<AuthoritativeEngine>,
864 pub(crate) engine_id: Bytes,
865 pub(crate) engine_boots: AtomicU32,
866 pub(crate) engine_time: AtomicU32,
867 pub(crate) engine_start: Instant,
868 pub(crate) engine_boots_base: u32,
870 pub(crate) max_message_size: usize,
871 pub(crate) snmp_invalid_msgs: AtomicU32,
875 pub(crate) snmp_unknown_security_models: AtomicU32,
878 pub(crate) snmp_silent_drops: AtomicU32,
881 pub(crate) snmp_unknown_contexts: AtomicU32,
884 pub(crate) usm_stats: UsmStats,
886}
887
888impl AgentState {
889 pub(crate) fn authoritative_boots_time(&self) -> Result<(u32, u32)> {
891 let pair = match &self.authoritative_engine {
892 Some(engine) => engine.current_boots_time()?,
893 None => {
894 let total_secs = self.engine_start.elapsed().as_secs();
895 compute_engine_boots_time(self.engine_boots_base, total_secs)
896 }
897 };
898 self.engine_boots.store(pair.0, Ordering::Relaxed);
899 self.engine_time.store(pair.1, Ordering::Relaxed);
900 Ok(pair)
901 }
902}
903
904pub(crate) struct AgentInner {
906 pub(crate) socket: Arc<UdpSocket>,
907 pub(crate) socket_state: UdpSocketState,
908 pub(crate) local_addr: SocketAddr,
909 pub(crate) communities: Vec<Vec<u8>>,
910 pub(crate) usm_users: HashMap<Bytes, UsmConfig>,
911 pub(crate) handlers: Vec<RegisteredHandler>,
912 pub(crate) state: Arc<AgentState>,
913 pub(crate) salt_counter: SaltCounter,
914 pub(crate) concurrency_limit: Option<Arc<Semaphore>>,
915 pub(crate) vacm: Option<VacmConfig>,
916 pub(crate) cancel: CancellationToken,
918 pub(crate) trap_sinks: Vec<notification::TrapSink>,
920 pub(crate) notification_id: std::sync::atomic::AtomicI32,
922}
923
924pub struct Agent {
945 pub(crate) inner: Arc<AgentInner>,
946}
947
948impl Agent {
949 #[must_use]
951 pub fn builder() -> AgentBuilder {
952 AgentBuilder::new()
953 }
954
955 #[must_use]
957 pub fn local_addr(&self) -> SocketAddr {
958 self.inner.local_addr
959 }
960
961 #[must_use]
967 pub fn engine_id(&self) -> &[u8] {
968 &self.inner.state.engine_id
969 }
970
971 #[must_use]
977 pub fn engine_boots(&self) -> u32 {
978 self.inner.state.engine_boots.load(Ordering::Relaxed)
979 }
980
981 #[must_use]
986 pub fn engine_time(&self) -> u32 {
987 self.inner.state.engine_time.load(Ordering::Relaxed)
988 }
989
990 #[must_use]
994 pub fn cancel(&self) -> CancellationToken {
995 self.inner.cancel.clone()
996 }
997
998 #[must_use]
1005 pub fn snmp_invalid_msgs(&self) -> u32 {
1006 self.inner.state.snmp_invalid_msgs.load(Ordering::Relaxed)
1007 }
1008
1009 #[must_use]
1016 pub fn snmp_unknown_security_models(&self) -> u32 {
1017 self.inner
1018 .state
1019 .snmp_unknown_security_models
1020 .load(Ordering::Relaxed)
1021 }
1022
1023 #[must_use]
1032 pub fn snmp_silent_drops(&self) -> u32 {
1033 self.inner.state.snmp_silent_drops.load(Ordering::Relaxed)
1034 }
1035
1036 #[must_use]
1045 pub fn snmp_unknown_contexts(&self) -> u32 {
1046 self.inner
1047 .state
1048 .snmp_unknown_contexts
1049 .load(Ordering::Relaxed)
1050 }
1051
1052 #[must_use]
1060 pub fn usm_unknown_engine_ids(&self) -> u32 {
1061 self.inner
1062 .state
1063 .usm_stats
1064 .unknown_engine_ids
1065 .load(Ordering::Relaxed)
1066 }
1067
1068 #[must_use]
1076 pub fn usm_unknown_usernames(&self) -> u32 {
1077 self.inner
1078 .state
1079 .usm_stats
1080 .unknown_usernames
1081 .load(Ordering::Relaxed)
1082 }
1083
1084 #[must_use]
1091 pub fn usm_wrong_digests(&self) -> u32 {
1092 self.inner
1093 .state
1094 .usm_stats
1095 .wrong_digests
1096 .load(Ordering::Relaxed)
1097 }
1098
1099 #[must_use]
1109 pub fn usm_not_in_time_windows(&self) -> u32 {
1110 self.inner
1111 .state
1112 .usm_stats
1113 .not_in_time_windows
1114 .load(Ordering::Relaxed)
1115 }
1116
1117 #[must_use]
1125 pub fn usm_unsupported_sec_levels(&self) -> u32 {
1126 self.inner
1127 .state
1128 .usm_stats
1129 .unsupported_sec_levels
1130 .load(Ordering::Relaxed)
1131 }
1132
1133 #[must_use]
1141 pub fn usm_decryption_errors(&self) -> u32 {
1142 self.inner
1143 .state
1144 .usm_stats
1145 .decryption_errors
1146 .load(Ordering::Relaxed)
1147 }
1148
1149 #[must_use]
1154 pub fn uptime_hundredths(&self) -> u32 {
1155 let elapsed = self.inner.state.engine_start.elapsed();
1156 let centisecs = elapsed.as_millis() / 10;
1157 centisecs.min(u128::from(u32::MAX)) as u32
1158 }
1159
1160 #[instrument(skip(self), err, fields(snmp.local_addr = %self.local_addr()))]
1166 pub async fn run(&self) -> Result<()> {
1167 let mut buf = vec![0u8; 65535];
1168
1169 loop {
1170 let recv_meta = tokio::select! {
1171 result = self.recv_packet(&mut buf) => {
1172 result?
1173 }
1174 () = self.inner.cancel.cancelled() => {
1175 tracing::info!(target: "async_snmp::agent", "agent shutdown requested");
1176 return Ok(());
1177 }
1178 };
1179
1180 let data = Bytes::copy_from_slice(&buf[..recv_meta.len]);
1181 let agent = self.clone();
1182
1183 let permit = if let Some(ref sem) = self.inner.concurrency_limit {
1184 tokio::select! {
1185 result = sem.clone().acquire_owned() => {
1186 Some(result.expect("semaphore closed"))
1187 }
1188 () = self.inner.cancel.cancelled() => {
1189 tracing::info!(target: "async_snmp::agent", "agent shutdown requested");
1190 return Ok(());
1191 }
1192 }
1193 } else {
1194 None
1195 };
1196
1197 tokio::spawn(async move {
1198 if let Err(error) = agent.update_engine_time() {
1199 tracing::warn!(target: "async_snmp::agent", %error, "could not persist authoritative engine time transition");
1200 }
1201
1202 match agent.handle_request(data, recv_meta.addr).await {
1203 Ok(Some(response_bytes)) => {
1204 if response_bytes.len() > agent.inner.state.max_message_size {
1211 agent
1212 .inner
1213 .state
1214 .snmp_silent_drops
1215 .fetch_add(1, Ordering::Relaxed);
1216 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");
1217 } else if let Err(e) =
1218 agent.send_response(&response_bytes, &recv_meta).await
1219 {
1220 tracing::warn!(target: "async_snmp::agent", { snmp.source = %recv_meta.addr, error = %e }, "failed to send response");
1221 }
1222 }
1223 Ok(None) => {}
1224 Err(e) => {
1225 tracing::warn!(target: "async_snmp::agent", { snmp.source = %recv_meta.addr, error = %e }, "error handling request");
1226 }
1227 }
1228
1229 drop(permit);
1230 });
1231 }
1232 }
1233
1234 async fn recv_packet(&self, buf: &mut [u8]) -> Result<RecvMeta> {
1235 let mut iov = [IoSliceMut::new(buf)];
1236 let mut meta = [RecvMeta::default()];
1237
1238 loop {
1239 self.inner
1240 .socket
1241 .readable()
1242 .await
1243 .map_err(|e| Error::Network {
1244 target: self.inner.local_addr,
1245 source: e,
1246 })?;
1247
1248 let result = self.inner.socket.try_io(tokio::io::Interest::READABLE, || {
1249 let sref = UdpSockRef::from(&*self.inner.socket);
1250 self.inner.socket_state.recv(sref, &mut iov, &mut meta)
1251 });
1252
1253 match result {
1254 Ok(n) if n > 0 => return Ok(meta[0]),
1255 Ok(_) => { }
1256 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { }
1258 Err(e) => {
1259 return Err(Error::Network {
1260 target: self.inner.local_addr,
1261 source: e,
1262 }
1263 .boxed());
1264 }
1265 }
1266 }
1267 }
1268
1269 async fn send_response(&self, data: &[u8], recv_meta: &RecvMeta) -> std::io::Result<()> {
1270 let transmit = Transmit {
1271 destination: recv_meta.addr,
1272 ecn: None,
1273 contents: data,
1274 segment_size: None,
1275 src_ip: recv_meta.dst_ip,
1276 };
1277
1278 loop {
1279 self.inner.socket.writable().await?;
1280
1281 let result = self.inner.socket.try_io(tokio::io::Interest::WRITABLE, || {
1282 let sref = UdpSockRef::from(&*self.inner.socket);
1283 self.inner.socket_state.try_send(sref, &transmit)
1284 });
1285
1286 match result {
1287 Ok(()) => return Ok(()),
1288 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { }
1290 Err(e) => return Err(e),
1291 }
1292 }
1293 }
1294
1295 async fn handle_request(&self, data: Bytes, source: SocketAddr) -> Result<Option<Bytes>> {
1299 match crate::message::peek_version(data.clone(), source)? {
1300 Version::V1 => self.handle_v1(data, source).await,
1301 Version::V2c => self.handle_v2c(data, source).await,
1302 Version::V3 => self.handle_v3(data, source).await,
1303 }
1304 }
1305
1306 fn update_engine_time(&self) -> Result<()> {
1314 let previous_boots = self.inner.state.engine_boots.load(Ordering::Relaxed);
1315 let (boots, _) = self.inner.state.authoritative_boots_time()?;
1316
1317 if boots != previous_boots && boots > self.inner.state.engine_boots_base {
1318 tracing::warn!(
1319 target: "async_snmp::agent",
1320 engine_boots = boots,
1321 "engine time wrapped past MAX_ENGINE_TIME, incrementing engine boots"
1322 );
1323 }
1324
1325 Ok(())
1326 }
1327
1328 pub(crate) fn validate_community(&self, community: &[u8]) -> bool {
1333 if self.inner.communities.is_empty() {
1334 return false;
1336 }
1337 let mut valid = false;
1341 for configured in &self.inner.communities {
1342 if configured.len() == community.len()
1344 && bool::from(configured.as_slice().ct_eq(community))
1345 {
1346 valid = true;
1347 }
1348 }
1349 valid
1350 }
1351
1352 async fn dispatch_request(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
1354 match pdu.pdu_type {
1355 PduType::GetRequest => self.handle_get(ctx, pdu).await,
1356 PduType::GetNextRequest => self.handle_get_next(ctx, pdu).await,
1357 PduType::GetBulkRequest => {
1358 if ctx.version == Version::V1 {
1360 return Ok(pdu.to_error_response(ErrorStatus::GenErr, 0));
1361 }
1362 self.handle_get_bulk(ctx, pdu).await
1363 }
1364 PduType::SetRequest => self.handle_set(ctx, pdu).await,
1365 PduType::InformRequest => Ok(self.handle_inform(ctx, pdu)),
1366 _ => {
1367 Ok(pdu.to_error_response(ErrorStatus::GenErr, 0))
1369 }
1370 }
1371 }
1372
1373 fn handle_inform(&self, ctx: &RequestContext, pdu: &Pdu) -> Pdu {
1384 if !Self::response_fits(
1393 &pdu.varbinds,
1394 self.response_overhead(ctx),
1395 self.effective_max_size(ctx),
1396 ) {
1397 return Self::too_big_response(ctx.version, pdu);
1398 }
1399
1400 pdu.to_response()
1401 }
1402
1403 fn effective_max_size(&self, ctx: &RequestContext) -> usize {
1407 let agent_max = self.inner.state.max_message_size;
1408 match ctx.msg_max_size {
1409 Some(client_max) => agent_max.min(client_max as usize),
1410 None => agent_max,
1411 }
1412 }
1413
1414 fn response_overhead(&self, ctx: &RequestContext) -> usize {
1428 if ctx.version != Version::V3 {
1429 return RESPONSE_OVERHEAD + ctx.security_name.len();
1434 }
1435 let mut overhead = RESPONSE_OVERHEAD
1436 + 2 * self.inner.state.engine_id.len()
1437 + ctx.security_name.len()
1438 + ctx.context_name.len();
1439 if ctx.security_level.requires_auth() {
1440 overhead += V3_AUTH_OVERHEAD;
1441 }
1442 if ctx.security_level.requires_priv() {
1443 overhead += V3_PRIV_OVERHEAD;
1444 }
1445 overhead
1446 }
1447
1448 fn response_fits(varbinds: &[VarBind], overhead: usize, max_size: usize) -> bool {
1452 let size = overhead + varbinds.iter().map(VarBind::encoded_size).sum::<usize>();
1453 size <= max_size
1454 }
1455
1456 pub(super) fn too_big_response(version: Version, pdu: &Pdu) -> Pdu {
1464 let varbinds = if version == Version::V1 {
1465 pdu.varbinds.clone()
1466 } else {
1467 Vec::new()
1468 };
1469 Pdu {
1470 pdu_type: PduType::Response,
1471 request_id: pdu.request_id,
1472 error_status: ErrorStatus::TooBig.as_i32(),
1473 error_index: 0,
1474 varbinds,
1475 }
1476 }
1477
1478 async fn handle_get(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
1480 let mut response_varbinds = Vec::with_capacity(pdu.varbinds.len());
1481
1482 for (index, vb) in pdu.varbinds.iter().enumerate() {
1483 if let Some(ref vacm) = self.inner.vacm
1485 && !vacm.check_access(ctx.read_view.as_ref(), &vb.oid)
1486 {
1487 if ctx.version == Version::V1 {
1489 return Ok(pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32));
1490 }
1491 response_varbinds.push(VarBind::new(vb.oid.clone(), Value::NoSuchObject));
1493 continue;
1494 }
1495
1496 let result = if let Some(handler) = self.find_handler(&vb.oid) {
1497 match handler.handler.get(ctx, &vb.oid).await {
1498 Ok(result) => result,
1499 Err(err) => {
1500 tracing::warn!(
1503 target: "async_snmp::agent",
1504 oid = %vb.oid,
1505 error = %err,
1506 "handler GET failed; responding genErr"
1507 );
1508 return Ok(pdu.to_error_response(ErrorStatus::GenErr, (index + 1) as i32));
1509 }
1510 }
1511 } else {
1512 GetResult::NoSuchObject
1513 };
1514
1515 let response_value = match result {
1516 GetResult::Value(v) => {
1517 if v1_rejects_counter64(ctx.version, &v) {
1518 return Ok(
1519 pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32)
1520 );
1521 }
1522 v
1523 }
1524 GetResult::NoSuchObject => {
1525 if ctx.version == Version::V1 {
1527 return Ok(
1528 pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32)
1529 );
1530 }
1531 Value::NoSuchObject
1532 }
1533 GetResult::NoSuchInstance => {
1534 if ctx.version == Version::V1 {
1536 return Ok(
1537 pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32)
1538 );
1539 }
1540 Value::NoSuchInstance
1541 }
1542 };
1543
1544 response_varbinds.push(VarBind::new(vb.oid.clone(), response_value));
1545 }
1546
1547 if !Self::response_fits(
1550 &response_varbinds,
1551 self.response_overhead(ctx),
1552 self.effective_max_size(ctx),
1553 ) {
1554 return Ok(Self::too_big_response(ctx.version, pdu));
1555 }
1556
1557 Ok(Pdu {
1558 pdu_type: PduType::Response,
1559 request_id: pdu.request_id,
1560 error_status: 0,
1561 error_index: 0,
1562 varbinds: response_varbinds,
1563 })
1564 }
1565
1566 async fn handle_get_next(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
1568 let mut response_varbinds = Vec::with_capacity(pdu.varbinds.len());
1569
1570 for (index, vb) in pdu.varbinds.iter().enumerate() {
1571 let next = match self.get_next_accessible_oid(ctx, &vb.oid).await {
1575 Ok(next) => next,
1576 Err(err) => {
1577 tracing::warn!(
1578 target: "async_snmp::agent",
1579 oid = %vb.oid,
1580 error = %err,
1581 "handler GETNEXT failed; responding genErr"
1582 );
1583 return Ok(pdu.to_error_response(ErrorStatus::GenErr, (index + 1) as i32));
1584 }
1585 };
1586
1587 if let Some(next_vb) = next {
1588 response_varbinds.push(next_vb);
1589 } else {
1590 if ctx.version == Version::V1 {
1592 return Ok(pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32));
1593 }
1594 response_varbinds.push(VarBind::new(vb.oid.clone(), Value::EndOfMibView));
1595 }
1596 }
1597
1598 if !Self::response_fits(
1601 &response_varbinds,
1602 self.response_overhead(ctx),
1603 self.effective_max_size(ctx),
1604 ) {
1605 return Ok(Self::too_big_response(ctx.version, pdu));
1606 }
1607
1608 Ok(Pdu {
1609 pdu_type: PduType::Response,
1610 request_id: pdu.request_id,
1611 error_status: 0,
1612 error_index: 0,
1613 varbinds: response_varbinds,
1614 })
1615 }
1616
1617 async fn handle_get_bulk(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
1622 let non_repeaters = pdu.error_status.try_into().unwrap_or(0);
1624 let max_repetitions = pdu.error_index.max(0);
1625
1626 let mut response_varbinds = Vec::new();
1627 let mut current_size: usize = self.response_overhead(ctx);
1628 let max_size = self.effective_max_size(ctx);
1629
1630 let can_add = |vb: &VarBind, current_size: usize| -> bool {
1632 current_size + vb.encoded_size() <= max_size
1633 };
1634
1635 for (index, vb) in pdu.varbinds.iter().take(non_repeaters).enumerate() {
1637 let next = match self.get_next_accessible_oid(ctx, &vb.oid).await {
1638 Ok(next) => next,
1639 Err(err) => {
1640 tracing::warn!(
1643 target: "async_snmp::agent",
1644 oid = %vb.oid,
1645 error = %err,
1646 "handler GETBULK failed; responding genErr"
1647 );
1648 return Ok(pdu.to_error_response(ErrorStatus::GenErr, (index + 1) as i32));
1649 }
1650 };
1651
1652 let next_vb = match next {
1653 Some(next_vb) => next_vb,
1654 None => VarBind::new(vb.oid.clone(), Value::EndOfMibView),
1655 };
1656
1657 if !can_add(&next_vb, current_size) {
1658 if response_varbinds.is_empty() {
1660 return Ok(Self::too_big_response(ctx.version, pdu));
1661 }
1662 return Ok(Pdu {
1670 pdu_type: PduType::Response,
1671 request_id: pdu.request_id,
1672 error_status: 0,
1673 error_index: 0,
1674 varbinds: response_varbinds,
1675 });
1676 }
1677
1678 current_size += next_vb.encoded_size();
1679 response_varbinds.push(next_vb);
1680 }
1681
1682 if non_repeaters < pdu.varbinds.len() {
1684 let repeaters = &pdu.varbinds[non_repeaters..];
1685 let mut current_oids: Vec<Oid> = repeaters.iter().map(|vb| vb.oid.clone()).collect();
1686 let mut all_done = vec![false; repeaters.len()];
1687
1688 'outer: for _ in 0..max_repetitions {
1689 let mut row_complete = true;
1690 for (i, oid) in current_oids.iter_mut().enumerate() {
1691 let next_vb = if all_done[i] {
1692 VarBind::new(oid.clone(), Value::EndOfMibView)
1693 } else {
1694 let next = match self.get_next_accessible_oid(ctx, oid).await {
1695 Ok(next) => next,
1696 Err(err) => {
1697 tracing::warn!(
1702 target: "async_snmp::agent",
1703 oid = %oid,
1704 error = %err,
1705 "handler GETBULK failed; responding genErr"
1706 );
1707 return Ok(pdu.to_error_response(
1708 ErrorStatus::GenErr,
1709 (non_repeaters + i + 1) as i32,
1710 ));
1711 }
1712 };
1713
1714 if let Some(next_vb) = next {
1715 *oid = next_vb.oid.clone();
1716 row_complete = false;
1717 next_vb
1718 } else {
1719 all_done[i] = true;
1720 VarBind::new(oid.clone(), Value::EndOfMibView)
1721 }
1722 };
1723
1724 if !can_add(&next_vb, current_size) {
1726 if response_varbinds.is_empty() {
1735 return Ok(Self::too_big_response(ctx.version, pdu));
1736 }
1737 break 'outer;
1739 }
1740
1741 current_size += next_vb.encoded_size();
1742 response_varbinds.push(next_vb);
1743 }
1744
1745 if row_complete {
1746 break;
1747 }
1748 }
1749 }
1750
1751 Ok(Pdu {
1752 pdu_type: PduType::Response,
1753 request_id: pdu.request_id,
1754 error_status: 0,
1755 error_index: 0,
1756 varbinds: response_varbinds,
1757 })
1758 }
1759
1760 pub(crate) fn find_handler(&self, oid: &Oid) -> Option<&RegisteredHandler> {
1762 self.inner
1764 .handlers
1765 .iter()
1766 .find(|&handler| handler.handler.handles(&handler.prefix, oid))
1767 .map(|v| v as _)
1768 }
1769
1770 async fn get_next_accessible_oid(
1775 &self,
1776 ctx: &RequestContext,
1777 from_oid: &Oid,
1778 ) -> HandlerResult<Option<VarBind>> {
1779 let mut search_from = from_oid.clone();
1780 for _ in 0..MAX_VACM_SKIP_ITERATIONS {
1781 let candidate = self.get_next_oid(ctx, &search_from).await?;
1782 match candidate {
1783 None => return Ok(None),
1784 Some(ref next_vb) => {
1785 if next_vb.oid <= search_from {
1786 tracing::error!(
1787 target: "async_snmp::agent",
1788 from = %search_from,
1789 got = %next_vb.oid,
1790 "handler returned non-increasing OID in GETNEXT"
1791 );
1792 return Ok(None);
1793 }
1794 if v1_rejects_counter64(ctx.version, &next_vb.value) {
1795 search_from = next_vb.oid.clone();
1796 continue;
1797 }
1798 if let Some(ref vacm) = self.inner.vacm {
1799 if vacm.check_access(ctx.read_view.as_ref(), &next_vb.oid) {
1800 return Ok(candidate);
1801 }
1802 search_from = next_vb.oid.clone();
1803 } else {
1804 return Ok(candidate);
1805 }
1806 }
1807 }
1808 }
1809 tracing::warn!(
1812 target: "async_snmp::agent",
1813 from = %from_oid,
1814 cap = MAX_VACM_SKIP_ITERATIONS,
1815 "VACM skip cap reached in GETNEXT; ending scan for this varbind"
1816 );
1817 Ok(None)
1818 }
1819
1820 async fn get_next_oid(
1822 &self,
1823 ctx: &RequestContext,
1824 oid: &Oid,
1825 ) -> HandlerResult<Option<VarBind>> {
1826 let mut best_result: Option<VarBind> = None;
1835
1836 for handler in &self.inner.handlers {
1837 let prefix = &handler.prefix;
1838 if prefix <= oid && !oid.starts_with(prefix) {
1839 continue;
1840 }
1841 if let GetNextResult::Value(next) = handler.handler.get_next(ctx, oid).await? {
1842 if next.oid > *oid {
1844 match &best_result {
1845 None => best_result = Some(next),
1846 Some(current) if next.oid < current.oid => best_result = Some(next),
1847 _ => {}
1848 }
1849 }
1850 }
1851 }
1852
1853 Ok(best_result)
1854 }
1855}
1856
1857impl Clone for Agent {
1858 fn clone(&self) -> Self {
1859 Self {
1860 inner: Arc::clone(&self.inner),
1861 }
1862 }
1863}
1864
1865#[cfg(test)]
1866mod tests {
1867 use super::*;
1868 use crate::handler::{
1869 BoxFuture, GetNextResult, GetResult, HandlerError, HandlerResult, MibHandler,
1870 RequestContext, SecurityModel, SetResult,
1871 };
1872 use crate::message::SecurityLevel;
1873 use crate::oid;
1874
1875 struct TestHandler;
1876
1877 impl MibHandler for TestHandler {
1878 fn get<'a>(
1879 &'a self,
1880 _ctx: &'a RequestContext,
1881 oid: &'a Oid,
1882 ) -> BoxFuture<'a, HandlerResult<GetResult>> {
1883 Box::pin(async move {
1884 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
1885 return Ok(GetResult::Value(Value::Integer(42)));
1886 }
1887 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0) {
1888 return Ok(GetResult::Value(Value::OctetString(Bytes::from_static(
1889 b"test",
1890 ))));
1891 }
1892 Ok(GetResult::NoSuchObject)
1893 })
1894 }
1895
1896 fn get_next<'a>(
1897 &'a self,
1898 _ctx: &'a RequestContext,
1899 oid: &'a Oid,
1900 ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
1901 Box::pin(async move {
1902 let oid1 = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
1903 let oid2 = oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0);
1904
1905 if oid < &oid1 {
1906 return Ok(GetNextResult::Value(VarBind::new(oid1, Value::Integer(42))));
1907 }
1908 if oid < &oid2 {
1909 return Ok(GetNextResult::Value(VarBind::new(
1910 oid2,
1911 Value::OctetString(Bytes::from_static(b"test")),
1912 )));
1913 }
1914 Ok(GetNextResult::EndOfMibView)
1915 })
1916 }
1917 }
1918
1919 fn test_ctx() -> RequestContext {
1920 RequestContext {
1921 source: "127.0.0.1:12345".parse().unwrap(),
1922 version: Version::V2c,
1923 security_model: SecurityModel::V2c,
1924 security_name: Bytes::from_static(b"public"),
1925 security_level: SecurityLevel::NoAuthNoPriv,
1926 context_name: Bytes::new(),
1927 request_id: 1,
1928 pdu_type: PduType::GetRequest,
1929 group_name: None,
1930 read_view: None,
1931 write_view: None,
1932 msg_max_size: None,
1933 }
1934 }
1935
1936 #[test]
1937 fn test_agent_builder_defaults() {
1938 let builder = AgentBuilder::new();
1939 assert_eq!(builder.bind_addr, "0.0.0.0:161");
1940 assert!(builder.communities.is_empty());
1941 assert!(builder.usm_users.is_empty());
1942 assert!(builder.handlers.is_empty());
1943 }
1944
1945 #[test]
1946 fn test_agent_builder_community() {
1947 let builder = AgentBuilder::new()
1948 .community(b"public")
1949 .community(b"private");
1950 assert_eq!(builder.communities.len(), 2);
1951 }
1952
1953 #[test]
1954 fn test_agent_builder_communities() {
1955 let builder = AgentBuilder::new().communities(["public", "private"]);
1956 assert_eq!(builder.communities.len(), 2);
1957 }
1958
1959 #[test]
1960 fn test_agent_builder_handler() {
1961 let builder =
1962 AgentBuilder::new().handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(TestHandler));
1963 assert_eq!(builder.handlers.len(), 1);
1964 }
1965
1966 #[tokio::test]
1967 async fn test_mib_handler_default_set() {
1968 let handler = TestHandler;
1969 let mut ctx = test_ctx();
1970 ctx.pdu_type = PduType::SetRequest;
1971
1972 let result = handler
1973 .test_set(&ctx, &oid!(1, 3, 6, 1), &Value::Integer(1))
1974 .await;
1975 assert_eq!(result, SetResult::NotWritable);
1976 }
1977
1978 #[test]
1979 fn test_mib_handler_handles() {
1980 let handler = TestHandler;
1981 let prefix = oid!(1, 3, 6, 1, 4, 1, 99_999);
1982
1983 assert!(handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 99_999, 1, 0)));
1985
1986 assert!(handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 99_999)));
1988
1989 assert!(!handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 99_998)));
1992
1993 assert!(!handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 100_000)));
1995 }
1996
1997 #[tokio::test]
1998 async fn test_test_handler_get() {
1999 let handler = TestHandler;
2000 let ctx = test_ctx();
2001
2002 let result = handler
2004 .get(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0))
2005 .await
2006 .unwrap();
2007 assert!(matches!(result, GetResult::Value(Value::Integer(42))));
2008
2009 let result = handler
2011 .get(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 99, 0))
2012 .await
2013 .unwrap();
2014 assert!(matches!(result, GetResult::NoSuchObject));
2015 }
2016
2017 #[tokio::test]
2018 async fn test_test_handler_get_next() {
2019 let handler = TestHandler;
2020 let mut ctx = test_ctx();
2021 ctx.pdu_type = PduType::GetNextRequest;
2022
2023 let next = handler
2025 .get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999))
2026 .await
2027 .unwrap();
2028 assert!(next.is_value());
2029 if let GetNextResult::Value(vb) = next {
2030 assert_eq!(vb.oid, oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0));
2031 }
2032
2033 let next = handler
2035 .get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0))
2036 .await
2037 .unwrap();
2038 assert!(next.is_value());
2039 if let GetNextResult::Value(vb) = next {
2040 assert_eq!(vb.oid, oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0));
2041 }
2042
2043 let next = handler
2045 .get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0))
2046 .await
2047 .unwrap();
2048 assert!(next.is_end_of_mib_view());
2049 }
2050
2051 struct FailingBackendHandler;
2054
2055 impl MibHandler for FailingBackendHandler {
2056 fn get<'a>(
2057 &'a self,
2058 _ctx: &'a RequestContext,
2059 oid: &'a Oid,
2060 ) -> BoxFuture<'a, HandlerResult<GetResult>> {
2061 Box::pin(async move {
2062 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
2063 return Ok(GetResult::Value(Value::Integer(1)));
2064 }
2065 Err(HandlerError::new("backing store unavailable"))
2066 })
2067 }
2068
2069 fn get_next<'a>(
2070 &'a self,
2071 _ctx: &'a RequestContext,
2072 oid: &'a Oid,
2073 ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
2074 Box::pin(async move {
2075 let first = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
2076 if oid < &first {
2077 return Ok(GetNextResult::Value(VarBind::new(first, Value::Integer(1))));
2078 }
2079 Err(HandlerError::new("backing store unavailable"))
2080 })
2081 }
2082 }
2083
2084 async fn failing_backend_agent() -> Agent {
2085 AgentBuilder::new()
2086 .bind("127.0.0.1:0")
2087 .community(b"public")
2088 .handler(
2089 oid!(1, 3, 6, 1, 4, 1, 99999),
2090 Arc::new(FailingBackendHandler),
2091 )
2092 .build()
2093 .await
2094 .unwrap()
2095 }
2096
2097 #[tokio::test]
2098 async fn test_get_handler_error_maps_to_generr() {
2099 let agent = failing_backend_agent().await;
2100 let ctx = test_ctx();
2101
2102 let pdu = Pdu {
2106 pdu_type: PduType::GetRequest,
2107 request_id: 1,
2108 error_status: 0,
2109 error_index: 0,
2110 varbinds: vec![
2111 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0), Value::Null),
2112 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0), Value::Null),
2113 ],
2114 };
2115
2116 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2117 assert_eq!(response.error_status, ErrorStatus::GenErr.as_i32());
2118 assert_eq!(response.error_index, 2);
2119 assert_eq!(response.varbinds.len(), 2);
2120 assert_eq!(response.varbinds[0].oid, pdu.varbinds[0].oid);
2121 }
2122
2123 #[tokio::test]
2124 async fn test_get_v1_handler_error_maps_to_generr() {
2125 let agent = failing_backend_agent().await;
2126 let mut ctx = test_ctx();
2127 ctx.version = Version::V1;
2128
2129 let pdu = Pdu {
2130 pdu_type: PduType::GetRequest,
2131 request_id: 2,
2132 error_status: 0,
2133 error_index: 0,
2134 varbinds: vec![VarBind::new(
2135 oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0),
2136 Value::Null,
2137 )],
2138 };
2139
2140 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2141 assert_eq!(response.error_status, ErrorStatus::GenErr.as_i32());
2142 assert_eq!(response.error_index, 1);
2143 }
2144
2145 #[tokio::test]
2146 async fn test_getnext_handler_error_maps_to_generr() {
2147 let agent = failing_backend_agent().await;
2148 let mut ctx = test_ctx();
2149 ctx.pdu_type = PduType::GetNextRequest;
2150
2151 let pdu = Pdu {
2152 pdu_type: PduType::GetNextRequest,
2153 request_id: 3,
2154 error_status: 0,
2155 error_index: 0,
2156 varbinds: vec![VarBind::new(
2157 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
2158 Value::Null,
2159 )],
2160 };
2161
2162 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2163 assert_eq!(response.error_status, ErrorStatus::GenErr.as_i32());
2164 assert_eq!(response.error_index, 1);
2165 }
2166
2167 #[tokio::test]
2168 async fn test_getbulk_handler_error_maps_to_generr() {
2169 let agent = failing_backend_agent().await;
2170 let mut ctx = test_ctx();
2171 ctx.pdu_type = PduType::GetBulkRequest;
2172
2173 let pdu = Pdu {
2177 pdu_type: PduType::GetBulkRequest,
2178 request_id: 4,
2179 error_status: 1, error_index: 5, varbinds: vec![
2182 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null),
2183 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0), Value::Null),
2184 ],
2185 };
2186
2187 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2188 assert_eq!(response.error_status, ErrorStatus::GenErr.as_i32());
2189 assert_eq!(response.error_index, 2);
2190 }
2191
2192 struct FiveOidHandler;
2194
2195 impl MibHandler for FiveOidHandler {
2196 fn get<'a>(
2197 &'a self,
2198 _ctx: &'a RequestContext,
2199 oid: &'a Oid,
2200 ) -> BoxFuture<'a, HandlerResult<GetResult>> {
2201 Box::pin(async move {
2202 for i in 1u16..=5 {
2203 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, i.into(), 0) {
2204 return Ok(GetResult::Value(Value::Integer(i.into())));
2205 }
2206 }
2207 Ok(GetResult::NoSuchObject)
2208 })
2209 }
2210
2211 fn get_next<'a>(
2212 &'a self,
2213 _ctx: &'a RequestContext,
2214 oid: &'a Oid,
2215 ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
2216 Box::pin(async move {
2217 for i in 1u32..=5 {
2218 let candidate = oid!(1, 3, 6, 1, 4, 1, 99999, i, 0);
2219 if oid < &candidate {
2220 return Ok(GetNextResult::Value(VarBind::new(
2221 candidate,
2222 Value::Integer(i as i32),
2223 )));
2224 }
2225 }
2226 Ok(GetNextResult::EndOfMibView)
2227 })
2228 }
2229 }
2230
2231 async fn test_agent_with_restricted_vacm() -> Agent {
2235 Agent::builder()
2236 .bind("127.0.0.1:0")
2237 .community(b"public")
2238 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(FiveOidHandler))
2239 .vacm(|v| {
2240 v.group("public", SecurityModel::V2c, "readers")
2241 .access("readers", |a| a.read_view("restricted"))
2242 .view("restricted", |v| {
2243 v.include(oid!(1, 3, 6, 1, 4, 1, 99999, 2))
2244 .include(oid!(1, 3, 6, 1, 4, 1, 99999, 4))
2245 })
2246 })
2247 .build()
2248 .await
2249 .unwrap()
2250 }
2251
2252 #[tokio::test]
2253 async fn test_getbulk_vacm_filters_inaccessible_oids() {
2254 let agent = test_agent_with_restricted_vacm().await;
2255
2256 let mut ctx = test_ctx();
2257 ctx.pdu_type = PduType::GetBulkRequest;
2258 ctx.read_view = Some(Bytes::from_static(b"restricted"));
2259
2260 let pdu = Pdu {
2264 pdu_type: PduType::GetBulkRequest,
2265 request_id: 1,
2266 error_status: 0, error_index: 10, varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2269 };
2270
2271 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2272
2273 let returned_oids: Vec<&Oid> = response
2275 .varbinds
2276 .iter()
2277 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
2278 .map(|vb| &vb.oid)
2279 .collect();
2280
2281 assert!(
2283 returned_oids.contains(&&oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0)),
2284 "expected .99999.2.0 in response, got: {returned_oids:?}"
2285 );
2286 assert!(
2287 returned_oids.contains(&&oid!(1, 3, 6, 1, 4, 1, 99999, 4, 0)),
2288 "expected .99999.4.0 in response (walk must continue past denied OIDs), got: {returned_oids:?}"
2289 );
2290
2291 for &oid in &[
2293 &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
2294 &oid!(1, 3, 6, 1, 4, 1, 99999, 3, 0),
2295 &oid!(1, 3, 6, 1, 4, 1, 99999, 5, 0),
2296 ] {
2297 assert!(
2298 !returned_oids.contains(&oid),
2299 "GETBULK returned OID outside read view: {oid:?}"
2300 );
2301 }
2302 }
2303
2304 #[tokio::test]
2305 async fn test_getbulk_non_repeaters_vacm_filtered() {
2306 let agent = test_agent_with_restricted_vacm().await;
2307
2308 let mut ctx = test_ctx();
2309 ctx.pdu_type = PduType::GetBulkRequest;
2310 ctx.read_view = Some(Bytes::from_static(b"restricted"));
2311
2312 let pdu = Pdu {
2318 pdu_type: PduType::GetBulkRequest,
2319 request_id: 2,
2320 error_status: 2, error_index: 0, varbinds: vec![
2323 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null),
2324 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 4, 0), Value::Null),
2325 ],
2326 };
2327
2328 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2329
2330 assert_eq!(
2332 response.varbinds[0].oid,
2333 oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0)
2334 );
2335 assert!(matches!(response.varbinds[0].value, Value::Integer(2)));
2336
2337 assert_eq!(response.varbinds[1].value, Value::EndOfMibView);
2339 }
2340
2341 struct CountingRangeHandler {
2346 calls: Arc<std::sync::atomic::AtomicUsize>,
2347 }
2348
2349 impl MibHandler for CountingRangeHandler {
2350 fn get<'a>(
2351 &'a self,
2352 _ctx: &'a RequestContext,
2353 _oid: &'a Oid,
2354 ) -> BoxFuture<'a, HandlerResult<GetResult>> {
2355 Box::pin(async move { Ok(GetResult::NoSuchObject) })
2356 }
2357
2358 fn get_next<'a>(
2359 &'a self,
2360 _ctx: &'a RequestContext,
2361 _oid: &'a Oid,
2362 ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
2363 Box::pin(async move {
2364 let n = self.calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;
2368 let next = Oid::from_slice(&[1, 3, 6, 1, 4, 1, 99999, 1]).child(n as u32);
2369 Ok(GetNextResult::Value(VarBind::new(next, Value::Integer(1))))
2370 })
2371 }
2372 }
2373
2374 #[tokio::test]
2379 async fn test_getnext_vacm_denied_range_is_capped() {
2380 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2381 let agent = Agent::builder()
2382 .bind("127.0.0.1:0")
2383 .community(b"public")
2384 .handler(
2385 oid!(1, 3, 6, 1, 4, 1, 99999),
2386 Arc::new(CountingRangeHandler {
2387 calls: calls.clone(),
2388 }),
2389 )
2390 .vacm(|v| {
2393 v.group("public", SecurityModel::V2c, "readers")
2394 .access("readers", |a| a.read_view("restricted"))
2395 .view("restricted", |v| v.include(oid!(1, 3, 6, 1, 4, 1, 88888)))
2396 })
2397 .build()
2398 .await
2399 .unwrap();
2400
2401 let mut ctx = test_ctx();
2402 ctx.pdu_type = PduType::GetNextRequest;
2403 ctx.read_view = Some(Bytes::from_static(b"restricted"));
2404
2405 let pdu = Pdu {
2406 pdu_type: PduType::GetNextRequest,
2407 request_id: 1,
2408 error_status: 0,
2409 error_index: 0,
2410 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2411 };
2412
2413 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2414
2415 assert_eq!(response.varbinds.len(), 1);
2417 assert_eq!(response.varbinds[0].value, Value::EndOfMibView);
2418
2419 let total = calls.load(std::sync::atomic::Ordering::SeqCst);
2421 assert!(
2422 total <= MAX_VACM_SKIP_ITERATIONS,
2423 "handler called {total} times, expected <= {MAX_VACM_SKIP_ITERATIONS}"
2424 );
2425 }
2426
2427 struct ThreeOidHandler;
2429
2430 impl MibHandler for ThreeOidHandler {
2431 fn get<'a>(
2432 &'a self,
2433 _ctx: &'a RequestContext,
2434 oid: &'a Oid,
2435 ) -> BoxFuture<'a, HandlerResult<GetResult>> {
2436 Box::pin(async move {
2437 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
2438 return Ok(GetResult::Value(Value::Integer(1)));
2439 }
2440 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0) {
2441 return Ok(GetResult::Value(Value::Integer(2)));
2442 }
2443 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 3, 0) {
2444 return Ok(GetResult::Value(Value::Integer(3)));
2445 }
2446 Ok(GetResult::NoSuchObject)
2447 })
2448 }
2449
2450 fn get_next<'a>(
2451 &'a self,
2452 _ctx: &'a RequestContext,
2453 oid: &'a Oid,
2454 ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
2455 Box::pin(async move {
2456 let oid1 = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
2457 let oid2 = oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0);
2458 let oid3 = oid!(1, 3, 6, 1, 4, 1, 99999, 3, 0);
2459
2460 if oid < &oid1 {
2461 return Ok(GetNextResult::Value(VarBind::new(oid1, Value::Integer(1))));
2462 }
2463 if oid < &oid2 {
2464 return Ok(GetNextResult::Value(VarBind::new(oid2, Value::Integer(2))));
2465 }
2466 if oid < &oid3 {
2467 return Ok(GetNextResult::Value(VarBind::new(oid3, Value::Integer(3))));
2468 }
2469 Ok(GetNextResult::EndOfMibView)
2470 })
2471 }
2472 }
2473
2474 async fn test_agent_with_gap_vacm() -> Agent {
2477 Agent::builder()
2478 .bind("127.0.0.1:0")
2479 .community(b"public")
2480 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(ThreeOidHandler))
2481 .vacm(|v| {
2482 v.group("public", SecurityModel::V2c, "readers")
2483 .access("readers", |a| a.read_view("gap"))
2484 .view("gap", |v| {
2485 v.include(oid!(1, 3, 6, 1, 4, 1, 99999, 1))
2486 .include(oid!(1, 3, 6, 1, 4, 1, 99999, 3))
2487 })
2488 })
2489 .build()
2490 .await
2491 .unwrap()
2492 }
2493
2494 #[tokio::test]
2495 async fn test_getnext_vacm_skips_inaccessible_continues_walk() {
2496 let agent = test_agent_with_gap_vacm().await;
2500
2501 let mut ctx = test_ctx();
2502 ctx.pdu_type = PduType::GetNextRequest;
2503 ctx.read_view = Some(Bytes::from_static(b"gap"));
2504
2505 let pdu = Pdu {
2506 pdu_type: PduType::GetNextRequest,
2507 request_id: 1,
2508 error_status: 0,
2509 error_index: 0,
2510 varbinds: vec![VarBind::new(
2511 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
2512 Value::Null,
2513 )],
2514 };
2515
2516 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2517 assert_eq!(response.varbinds.len(), 1);
2518 assert_eq!(
2519 response.varbinds[0].oid,
2520 oid!(1, 3, 6, 1, 4, 1, 99999, 3, 0),
2521 "GETNEXT should skip denied .99999.2.0 and return accessible .99999.3.0"
2522 );
2523 assert!(matches!(response.varbinds[0].value, Value::Integer(3)));
2524 }
2525
2526 #[tokio::test]
2527 async fn test_getnext_vacm_all_remaining_denied_returns_end_of_mib() {
2528 let agent = test_agent_with_restricted_vacm().await;
2532
2533 let mut ctx = test_ctx();
2534 ctx.pdu_type = PduType::GetNextRequest;
2535 ctx.read_view = Some(Bytes::from_static(b"restricted"));
2536
2537 let pdu = Pdu {
2538 pdu_type: PduType::GetNextRequest,
2539 request_id: 1,
2540 error_status: 0,
2541 error_index: 0,
2542 varbinds: vec![VarBind::new(
2543 oid!(1, 3, 6, 1, 4, 1, 99999, 4, 0),
2544 Value::Null,
2545 )],
2546 };
2547
2548 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2549 assert_eq!(response.varbinds.len(), 1);
2550 assert_eq!(
2551 response.varbinds[0].value,
2552 Value::EndOfMibView,
2553 "GETNEXT should return EndOfMibView when all remaining OIDs are denied"
2554 );
2555 }
2556
2557 #[tokio::test]
2558 async fn test_getbulk_without_vacm_returns_all_oids() {
2559 let agent = Agent::builder()
2561 .bind("127.0.0.1:0")
2562 .community(b"public")
2563 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(TestHandler))
2564 .build()
2565 .await
2566 .unwrap();
2567
2568 let mut ctx = test_ctx();
2569 ctx.pdu_type = PduType::GetBulkRequest;
2570
2571 let pdu = Pdu {
2572 pdu_type: PduType::GetBulkRequest,
2573 request_id: 1,
2574 error_status: 0,
2575 error_index: 10,
2576 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2577 };
2578
2579 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2580
2581 assert!(
2583 response
2584 .varbinds
2585 .iter()
2586 .any(|vb| vb.oid == oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0))
2587 );
2588 assert!(
2589 response
2590 .varbinds
2591 .iter()
2592 .any(|vb| vb.oid == oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0))
2593 );
2594 }
2595
2596 #[tokio::test]
2597 async fn test_v1_getbulk_rejected() {
2598 let agent = Agent::builder()
2600 .bind("127.0.0.1:0")
2601 .community(b"public")
2602 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(TestHandler))
2603 .build()
2604 .await
2605 .unwrap();
2606
2607 let mut ctx = test_ctx();
2608 ctx.version = Version::V1;
2609 ctx.security_model = SecurityModel::V1;
2610 ctx.pdu_type = PduType::GetBulkRequest;
2611
2612 let pdu = Pdu {
2613 pdu_type: PduType::GetBulkRequest,
2614 request_id: 1,
2615 error_status: 0,
2616 error_index: 10,
2617 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2618 };
2619
2620 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2621 assert_eq!(
2622 ErrorStatus::from_i32(response.error_status),
2623 ErrorStatus::GenErr,
2624 "v1 GETBULK should be rejected"
2625 );
2626 }
2627
2628 struct Counter64Handler;
2630
2631 impl MibHandler for Counter64Handler {
2632 fn get<'a>(
2633 &'a self,
2634 _ctx: &'a RequestContext,
2635 oid: &'a Oid,
2636 ) -> BoxFuture<'a, HandlerResult<GetResult>> {
2637 Box::pin(async move {
2638 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
2639 return Ok(GetResult::Value(Value::Counter64(1_000_000_000_000)));
2640 }
2641 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0) {
2642 return Ok(GetResult::Value(Value::Integer(42)));
2643 }
2644 Ok(GetResult::NoSuchObject)
2645 })
2646 }
2647
2648 fn get_next<'a>(
2649 &'a self,
2650 _ctx: &'a RequestContext,
2651 oid: &'a Oid,
2652 ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
2653 Box::pin(async move {
2654 let oid1 = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
2655 let oid2 = oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0);
2656
2657 if oid < &oid1 {
2658 return Ok(GetNextResult::Value(VarBind::new(
2659 oid1,
2660 Value::Counter64(1_000_000_000_000),
2661 )));
2662 }
2663 if oid < &oid2 {
2664 return Ok(GetNextResult::Value(VarBind::new(oid2, Value::Integer(42))));
2665 }
2666 Ok(GetNextResult::EndOfMibView)
2667 })
2668 }
2669 }
2670
2671 async fn test_agent_with_counter64() -> Agent {
2672 Agent::builder()
2673 .bind("127.0.0.1:0")
2674 .community(b"public")
2675 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(Counter64Handler))
2676 .build()
2677 .await
2678 .unwrap()
2679 }
2680
2681 #[tokio::test]
2682 async fn test_v1_get_filters_counter64() {
2683 let agent = test_agent_with_counter64().await;
2686
2687 let mut ctx = test_ctx();
2688 ctx.version = Version::V1;
2689 ctx.security_model = SecurityModel::V1;
2690 ctx.pdu_type = PduType::GetRequest;
2691
2692 let pdu = Pdu {
2693 pdu_type: PduType::GetRequest,
2694 request_id: 1,
2695 error_status: 0,
2696 error_index: 0,
2697 varbinds: vec![VarBind::new(
2698 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
2699 Value::Null,
2700 )],
2701 };
2702
2703 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2704 assert_eq!(
2705 ErrorStatus::from_i32(response.error_status),
2706 ErrorStatus::NoSuchName,
2707 "v1 GET of Counter64 should return noSuchName"
2708 );
2709 }
2710
2711 #[tokio::test]
2712 async fn test_v2c_get_allows_counter64() {
2713 let agent = test_agent_with_counter64().await;
2715
2716 let ctx = test_ctx(); let pdu = Pdu {
2719 pdu_type: PduType::GetRequest,
2720 request_id: 1,
2721 error_status: 0,
2722 error_index: 0,
2723 varbinds: vec![VarBind::new(
2724 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
2725 Value::Null,
2726 )],
2727 };
2728
2729 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2730 assert_eq!(response.error_status, 0);
2731 assert!(matches!(response.varbinds[0].value, Value::Counter64(_)));
2732 }
2733
2734 #[tokio::test]
2735 async fn test_getbulk_respects_v3_msg_max_size() {
2736 let agent = Agent::builder()
2741 .bind("127.0.0.1:0")
2742 .community(b"public")
2743 .max_message_size(65507) .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(FiveOidHandler))
2745 .build()
2746 .await
2747 .unwrap();
2748
2749 let mut ctx_unlimited = test_ctx();
2751 ctx_unlimited.pdu_type = PduType::GetBulkRequest;
2752 ctx_unlimited.msg_max_size = None;
2753
2754 let pdu = Pdu {
2755 pdu_type: PduType::GetBulkRequest,
2756 request_id: 1,
2757 error_status: 0, error_index: 10, varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2760 };
2761
2762 let full_response = agent.dispatch_request(&ctx_unlimited, &pdu).await.unwrap();
2763 let full_count = full_response
2764 .varbinds
2765 .iter()
2766 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
2767 .count();
2768 assert!(
2769 full_count >= 3,
2770 "expected at least 3 data varbinds without limit, got {full_count}"
2771 );
2772
2773 let mut ctx_limited = test_ctx();
2778 ctx_limited.pdu_type = PduType::GetBulkRequest;
2779 ctx_limited.msg_max_size = Some(150); let limited_response = agent.dispatch_request(&ctx_limited, &pdu).await.unwrap();
2782 let limited_count = limited_response
2783 .varbinds
2784 .iter()
2785 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
2786 .count();
2787
2788 assert!(
2789 limited_count < full_count,
2790 "V3 msg_max_size should limit response: got {limited_count} varbinds (unlimited: {full_count})"
2791 );
2792 assert!(
2793 limited_count > 0,
2794 "should still return at least one varbind"
2795 );
2796 }
2797
2798 #[tokio::test]
2799 async fn test_response_overhead_scales_with_v3_security_level() {
2800 let engine_id = vec![0x11u8; 17];
2802 let agent = Agent::builder()
2803 .bind("127.0.0.1:0")
2804 .community(b"public")
2805 .engine_id(engine_id.clone())
2806 .build()
2807 .await
2808 .unwrap();
2809
2810 let v2c = test_ctx();
2813 assert_eq!(
2814 agent.response_overhead(&v2c),
2815 RESPONSE_OVERHEAD + v2c.security_name.len()
2816 );
2817
2818 let username = Bytes::from_static(b"user");
2819 let variable = 2 * engine_id.len() + username.len(); let mut noauth = test_ctx();
2822 noauth.version = Version::V3;
2823 noauth.security_level = SecurityLevel::NoAuthNoPriv;
2824 noauth.security_name = username.clone();
2825 assert_eq!(
2826 agent.response_overhead(&noauth),
2827 RESPONSE_OVERHEAD + variable
2828 );
2829
2830 let mut authnopriv = noauth.clone();
2831 authnopriv.security_level = SecurityLevel::AuthNoPriv;
2832 assert_eq!(
2833 agent.response_overhead(&authnopriv),
2834 RESPONSE_OVERHEAD + variable + V3_AUTH_OVERHEAD
2835 );
2836
2837 let mut authpriv = noauth.clone();
2838 authpriv.security_level = SecurityLevel::AuthPriv;
2839 assert_eq!(
2840 agent.response_overhead(&authpriv),
2841 RESPONSE_OVERHEAD + variable + V3_AUTH_OVERHEAD + V3_PRIV_OVERHEAD
2842 );
2843
2844 assert!(agent.response_overhead(&v2c) < agent.response_overhead(&noauth));
2846 assert!(agent.response_overhead(&noauth) < agent.response_overhead(&authnopriv));
2847 assert!(agent.response_overhead(&authnopriv) < agent.response_overhead(&authpriv));
2848 }
2849
2850 #[tokio::test]
2851 async fn test_response_overhead_counts_community_length() {
2852 let agent = Agent::builder()
2853 .bind("127.0.0.1:0")
2854 .community(b"public")
2855 .build()
2856 .await
2857 .unwrap();
2858
2859 let short = test_ctx();
2864 let mut long = test_ctx();
2865 long.security_name = Bytes::from(vec![b'x'; 200]);
2866
2867 assert_eq!(
2868 agent.response_overhead(&long) - agent.response_overhead(&short),
2869 long.security_name.len() - short.security_name.len()
2870 );
2871
2872 let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::Integer(0));
2875 let max = RESPONSE_OVERHEAD + short.security_name.len() + vb.encoded_size();
2876 assert!(Agent::response_fits(
2877 std::slice::from_ref(&vb),
2878 agent.response_overhead(&short),
2879 max
2880 ));
2881 assert!(!Agent::response_fits(
2882 std::slice::from_ref(&vb),
2883 agent.response_overhead(&long),
2884 max
2885 ));
2886 }
2887
2888 #[tokio::test]
2889 async fn test_getbulk_authpriv_budgets_for_wrapper() {
2890 let agent = Agent::builder()
2895 .bind("127.0.0.1:0")
2896 .community(b"public")
2897 .max_message_size(65507)
2898 .engine_id(vec![0x11u8; 17])
2899 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(FiveOidHandler))
2900 .build()
2901 .await
2902 .unwrap();
2903
2904 let pdu = Pdu {
2905 pdu_type: PduType::GetBulkRequest,
2906 request_id: 1,
2907 error_status: 0, error_index: 10, varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2910 };
2911
2912 let limit = 200;
2915
2916 let mut v2c = test_ctx();
2917 v2c.pdu_type = PduType::GetBulkRequest;
2918 v2c.msg_max_size = Some(limit);
2919 let v2c_count = agent
2920 .dispatch_request(&v2c, &pdu)
2921 .await
2922 .unwrap()
2923 .varbinds
2924 .iter()
2925 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
2926 .count();
2927
2928 let mut authpriv = test_ctx();
2929 authpriv.version = Version::V3;
2930 authpriv.security_level = SecurityLevel::AuthPriv;
2931 authpriv.security_name = Bytes::from_static(b"user");
2932 authpriv.pdu_type = PduType::GetBulkRequest;
2933 authpriv.msg_max_size = Some(limit);
2934 let authpriv_count = agent
2935 .dispatch_request(&authpriv, &pdu)
2936 .await
2937 .unwrap()
2938 .varbinds
2939 .iter()
2940 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
2941 .count();
2942
2943 assert!(
2944 authpriv_count < v2c_count,
2945 "authPriv should budget fewer varbinds than v2c for the same \
2946 msgMaxSize: authpriv={authpriv_count}, v2c={v2c_count}"
2947 );
2948 }
2949
2950 struct MixedSizeHandler;
2953
2954 impl MibHandler for MixedSizeHandler {
2955 fn get<'a>(
2956 &'a self,
2957 _ctx: &'a RequestContext,
2958 oid: &'a Oid,
2959 ) -> BoxFuture<'a, HandlerResult<GetResult>> {
2960 Box::pin(async move {
2961 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0)
2962 || oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0)
2963 {
2964 return Ok(GetResult::Value(Value::OctetString(Bytes::from(vec![
2965 0xAB;
2966 200
2967 ]))));
2968 }
2969 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 9, 0) {
2970 return Ok(GetResult::Value(Value::Integer(7)));
2971 }
2972 Ok(GetResult::NoSuchObject)
2973 })
2974 }
2975
2976 fn get_next<'a>(
2977 &'a self,
2978 _ctx: &'a RequestContext,
2979 oid: &'a Oid,
2980 ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
2981 Box::pin(async move {
2982 let big1 = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
2983 let big2 = oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0);
2984 let small = oid!(1, 3, 6, 1, 4, 1, 99999, 9, 0);
2985 if oid < &big1 {
2986 return Ok(GetNextResult::Value(VarBind::new(
2987 big1,
2988 Value::OctetString(Bytes::from(vec![0xAB; 200])),
2989 )));
2990 }
2991 if oid < &big2 {
2992 return Ok(GetNextResult::Value(VarBind::new(
2993 big2,
2994 Value::OctetString(Bytes::from(vec![0xAB; 200])),
2995 )));
2996 }
2997 if oid < &small {
2998 return Ok(GetNextResult::Value(VarBind::new(small, Value::Integer(7))));
2999 }
3000 Ok(GetNextResult::EndOfMibView)
3001 })
3002 }
3003 }
3004
3005 #[tokio::test]
3006 async fn test_getbulk_dropped_non_repeater_omits_repeaters() {
3007 let agent = Agent::builder()
3013 .bind("127.0.0.1:0")
3014 .community(b"public")
3015 .max_message_size(65507)
3016 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(MixedSizeHandler))
3017 .without_builtin_handlers()
3018 .build()
3019 .await
3020 .unwrap();
3021
3022 let big_vb = VarBind::new(
3025 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
3026 Value::OctetString(Bytes::from(vec![0xAB; 200])),
3027 );
3028 let small_vb = VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 9, 0), Value::Integer(7));
3029 let max = RESPONSE_OVERHEAD + big_vb.encoded_size() + small_vb.encoded_size();
3030
3031 let mut ctx = test_ctx();
3032 ctx.pdu_type = PduType::GetBulkRequest;
3033 ctx.msg_max_size = Some(max as u32);
3034
3035 let pdu = Pdu {
3036 pdu_type: PduType::GetBulkRequest,
3037 request_id: 1,
3038 error_status: 2, error_index: 2, varbinds: vec![
3041 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 1), Value::Null),
3042 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 2), Value::Null),
3043 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 9), Value::Null),
3044 ],
3045 };
3046
3047 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3048
3049 assert_eq!(
3051 response.varbinds.len(),
3052 1,
3053 "expected exactly the non-repeater prefix, got {:?}",
3054 response
3055 .varbinds
3056 .iter()
3057 .map(|vb| &vb.oid)
3058 .collect::<Vec<_>>()
3059 );
3060 assert_eq!(
3061 response.varbinds[0].oid,
3062 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0)
3063 );
3064 assert!(
3066 !response
3067 .varbinds
3068 .iter()
3069 .any(|vb| vb.oid == oid!(1, 3, 6, 1, 4, 1, 99999, 9, 0)),
3070 "repeater varbind leaked into response after a dropped non-repeater"
3071 );
3072 }
3073
3074 #[tokio::test]
3075 async fn test_getbulk_too_big_has_empty_varbinds() {
3076 let agent = Agent::builder()
3079 .bind("127.0.0.1:0")
3080 .community(b"public")
3081 .max_message_size(65507)
3082 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(MixedSizeHandler))
3083 .without_builtin_handlers()
3084 .build()
3085 .await
3086 .unwrap();
3087
3088 let mut ctx = test_ctx();
3089 ctx.pdu_type = PduType::GetBulkRequest;
3090 ctx.msg_max_size = Some((RESPONSE_OVERHEAD - 1) as u32);
3092
3093 let pdu = Pdu {
3094 pdu_type: PduType::GetBulkRequest,
3095 request_id: 1,
3096 error_status: 2, error_index: 2, varbinds: vec![
3099 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 1), Value::Null),
3100 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 2), Value::Null),
3101 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 9), Value::Null),
3102 ],
3103 };
3104
3105 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3106
3107 assert_eq!(response.error_status, ErrorStatus::TooBig.as_i32());
3108 assert!(
3109 response.varbinds.is_empty(),
3110 "tooBig Response must have empty varbinds, got {}",
3111 response.varbinds.len()
3112 );
3113 }
3114
3115 #[tokio::test]
3116 async fn test_getbulk_too_big_zero_non_repeaters_first_repeater_oversized() {
3117 let agent = Agent::builder()
3124 .bind("127.0.0.1:0")
3125 .community(b"public")
3126 .max_message_size(65507)
3127 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(MixedSizeHandler))
3128 .without_builtin_handlers()
3129 .build()
3130 .await
3131 .unwrap();
3132
3133 let big_vb = VarBind::new(
3138 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
3139 Value::OctetString(Bytes::from(vec![0xAB; 200])),
3140 );
3141 let max = RESPONSE_OVERHEAD + big_vb.encoded_size() - 1;
3142
3143 let mut ctx = test_ctx();
3144 ctx.pdu_type = PduType::GetBulkRequest;
3145 ctx.msg_max_size = Some(max as u32);
3146
3147 let pdu = Pdu {
3148 pdu_type: PduType::GetBulkRequest,
3149 request_id: 1,
3150 error_status: 0, error_index: 5, varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 1), Value::Null)],
3153 };
3154
3155 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3156
3157 assert_eq!(
3158 response.error_status,
3159 ErrorStatus::TooBig.as_i32(),
3160 "first oversized repeater varbind (non_repeaters == 0) must yield tooBig"
3161 );
3162 assert!(
3163 response.varbinds.is_empty(),
3164 "tooBig Response must have empty varbinds, got {}",
3165 response.varbinds.len()
3166 );
3167 }
3168
3169 #[tokio::test]
3170 async fn test_getbulk_msg_max_size_none_uses_agent_max() {
3171 let agent = Agent::builder()
3174 .bind("127.0.0.1:0")
3175 .community(b"public")
3176 .max_message_size(65507)
3177 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(FiveOidHandler))
3178 .without_builtin_handlers()
3179 .build()
3180 .await
3181 .unwrap();
3182
3183 let mut ctx = test_ctx();
3184 ctx.pdu_type = PduType::GetBulkRequest;
3185 ctx.msg_max_size = None; let pdu = Pdu {
3188 pdu_type: PduType::GetBulkRequest,
3189 request_id: 1,
3190 error_status: 0,
3191 error_index: 10,
3192 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
3193 };
3194
3195 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3196 let data_count = response
3197 .varbinds
3198 .iter()
3199 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
3200 .count();
3201 assert_eq!(
3202 data_count, 5,
3203 "all 5 OIDs should be returned without msg_max_size limit"
3204 );
3205 }
3206
3207 #[tokio::test]
3208 async fn test_v1_getnext_skips_counter64() {
3209 let agent = test_agent_with_counter64().await;
3213
3214 let mut ctx = test_ctx();
3215 ctx.version = Version::V1;
3216 ctx.security_model = SecurityModel::V1;
3217 ctx.pdu_type = PduType::GetNextRequest;
3218
3219 let pdu = Pdu {
3220 pdu_type: PduType::GetNextRequest,
3221 request_id: 1,
3222 error_status: 0,
3223 error_index: 0,
3224 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
3225 };
3226
3227 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3228 assert_eq!(response.error_status, 0, "should succeed");
3229 assert_eq!(
3230 response.varbinds[0].oid,
3231 oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0),
3232 "should skip Counter64 and return next non-Counter64 OID"
3233 );
3234 assert!(matches!(response.varbinds[0].value, Value::Integer(42)));
3235 }
3236
3237 #[test]
3238 fn test_engine_time_no_overflow() {
3239 let (boots, time) = crate::v3::compute_engine_boots_time(1, 1000);
3241 assert_eq!(boots, 1);
3242 assert_eq!(time, 1000);
3243 }
3244
3245 #[test]
3246 fn test_engine_time_zero_elapsed() {
3247 let (boots, time) = crate::v3::compute_engine_boots_time(1, 0);
3248 assert_eq!(boots, 1);
3249 assert_eq!(time, 0);
3250 }
3251
3252 #[test]
3253 fn test_engine_time_just_below_max() {
3254 let max = crate::v3::MAX_ENGINE_TIME;
3255 let (boots, time) = crate::v3::compute_engine_boots_time(1, u64::from(max) - 1);
3256 assert_eq!(boots, 1);
3257 assert_eq!(time, max - 1);
3258 }
3259
3260 #[test]
3261 fn test_engine_time_at_max_is_representable() {
3262 let max = crate::v3::MAX_ENGINE_TIME;
3263 let (boots, time) = crate::v3::compute_engine_boots_time(1, u64::from(max));
3264 assert_eq!(boots, 1);
3265 assert_eq!(time, max);
3266 }
3267
3268 #[test]
3269 fn test_engine_time_wraps_after_max() {
3270 let max = crate::v3::MAX_ENGINE_TIME;
3271 let (boots, time) = crate::v3::compute_engine_boots_time(1, u64::from(max) + 1);
3272 assert_eq!(boots, 2);
3273 assert_eq!(time, 0);
3274 }
3275
3276 #[test]
3277 fn test_engine_time_past_max() {
3278 let cycle = u64::from(crate::v3::MAX_ENGINE_TIME) + 1;
3280 let (boots, time) = crate::v3::compute_engine_boots_time(1, cycle + 500);
3281 assert_eq!(boots, 2);
3282 assert_eq!(time, 500);
3283 }
3284
3285 #[test]
3286 fn test_engine_time_multiple_wraps() {
3287 let cycle = u64::from(crate::v3::MAX_ENGINE_TIME) + 1;
3289 let elapsed = cycle * 3 + 42;
3290 let (boots, time) = crate::v3::compute_engine_boots_time(1, elapsed);
3291 assert_eq!(boots, 4, "base 1 + 3 wraps = 4");
3292 assert_eq!(time, 42);
3293 }
3294
3295 #[test]
3296 fn test_engine_time_boots_capped_at_max() {
3297 let max = crate::v3::MAX_ENGINE_TIME;
3299 let cycle = u64::from(max) + 1;
3300 let elapsed = cycle * u64::from(max); let (boots, _time) = crate::v3::compute_engine_boots_time(1, elapsed);
3302 assert_eq!(boots, max, "boots should be capped at MAX_ENGINE_TIME");
3303 }
3304
3305 #[test]
3306 fn test_engine_time_base_boots_preserved() {
3307 let cycle = u64::from(crate::v3::MAX_ENGINE_TIME) + 1;
3309 let (boots, time) = crate::v3::compute_engine_boots_time(5, cycle + 100);
3310 assert_eq!(boots, 6, "base 5 + 1 wrap = 6");
3311 assert_eq!(time, 100);
3312 }
3313
3314 #[test]
3315 fn test_engine_time_high_base_boots_capped() {
3316 let max = crate::v3::MAX_ENGINE_TIME;
3318 let cycle = u64::from(max) + 1;
3319 let (boots, _time) = crate::v3::compute_engine_boots_time(max - 1, cycle * 2);
3320 assert_eq!(boots, max, "should cap at MAX_ENGINE_TIME, not overflow");
3321 }
3322
3323 #[tokio::test]
3324 async fn test_authoritative_engine_builder() {
3325 let engine = AuthoritativeEngine::install(b"test-agent-engine".to_vec(), |_| {
3326 Ok::<(), std::convert::Infallible>(())
3327 })
3328 .unwrap();
3329 let agent = Agent::builder()
3330 .bind("127.0.0.1:0")
3331 .community(b"public")
3332 .authoritative_engine(engine)
3333 .build()
3334 .await
3335 .unwrap();
3336
3337 assert_eq!(agent.engine_boots(), 1);
3338 assert_eq!(agent.engine_id(), b"test-agent-engine");
3339 }
3340
3341 #[tokio::test]
3342 async fn test_v3_agent_requires_authoritative_engine() {
3343 let result = Agent::builder()
3344 .bind("127.0.0.1:0")
3345 .usm_user("user", |user| user)
3346 .build()
3347 .await;
3348
3349 let err = result.err().expect("expected build to fail");
3350 assert!(matches!(*err, Error::Config(_)));
3351 }
3352
3353 #[tokio::test]
3354 async fn test_zero_max_concurrent_requests_rejected() {
3355 let result = Agent::builder()
3358 .bind("127.0.0.1:0")
3359 .community(b"public")
3360 .max_concurrent_requests(Some(0))
3361 .build()
3362 .await;
3363
3364 let err = result.err().expect("expected build to fail");
3365 assert!(matches!(*err, Error::Config(_)));
3366 }
3367
3368 #[tokio::test]
3369 async fn test_engine_boots_default() {
3370 let agent = Agent::builder()
3372 .bind("127.0.0.1:0")
3373 .community(b"public")
3374 .build()
3375 .await
3376 .unwrap();
3377
3378 assert_eq!(agent.engine_boots(), 1);
3379 }
3380
3381 #[tokio::test]
3382 async fn test_usm_counter_accessors_default_zero() {
3383 let agent = Agent::builder()
3384 .bind("127.0.0.1:0")
3385 .community(b"public")
3386 .build()
3387 .await
3388 .unwrap();
3389
3390 assert_eq!(agent.usm_unsupported_sec_levels(), 0);
3391 assert_eq!(agent.usm_decryption_errors(), 0);
3392 }
3393
3394 #[test]
3395 fn test_builtin_mib_without_single() {
3396 let builder = AgentBuilder::new().without_builtin_handler(BuiltinMib::UsmStats);
3397 assert!(builder.disabled_builtins.contains(&BuiltinMib::UsmStats));
3398 assert!(!builder.disabled_builtins.contains(&BuiltinMib::SnmpEngine));
3399 assert!(!builder.disabled_builtins.contains(&BuiltinMib::MpdStats));
3400 }
3401
3402 #[test]
3403 fn test_builtin_mib_without_all() {
3404 let builder = AgentBuilder::new().without_builtin_handlers();
3405 assert!(builder.disabled_builtins.contains(&BuiltinMib::SnmpEngine));
3406 assert!(builder.disabled_builtins.contains(&BuiltinMib::UsmStats));
3407 assert!(builder.disabled_builtins.contains(&BuiltinMib::MpdStats));
3408 }
3409
3410 #[tokio::test]
3411 async fn test_uptime_hundredths() {
3412 let agent = Agent::builder()
3413 .bind("127.0.0.1:0")
3414 .community(b"public")
3415 .build()
3416 .await
3417 .unwrap();
3418
3419 let uptime = agent.uptime_hundredths();
3420 assert!(
3421 uptime < 100,
3422 "uptime should be less than 1 second, got {uptime}"
3423 );
3424
3425 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
3426 let uptime2 = agent.uptime_hundredths();
3427 assert!(uptime2 > uptime, "uptime should increase after delay");
3428 }
3429
3430 #[tokio::test]
3431 async fn test_builtin_handlers_registered_by_default() {
3432 let agent = Agent::builder()
3433 .bind("127.0.0.1:0")
3434 .community(b"public")
3435 .build()
3436 .await
3437 .unwrap();
3438
3439 let ctx = test_ctx();
3440
3441 let handler = agent
3443 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 10, 2, 1, 4, 0))
3444 .expect("snmpEngine handler should be registered");
3445 let get_result = handler
3446 .handler
3447 .get(&ctx, &oid!(1, 3, 6, 1, 6, 3, 10, 2, 1, 4, 0))
3448 .await
3449 .unwrap();
3450 assert!(matches!(get_result, GetResult::Value(Value::Integer(_))));
3451
3452 let handler = agent
3454 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 5, 0))
3455 .expect("USM stats handler should be registered");
3456 let get_result = handler
3457 .handler
3458 .get(&ctx, &oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 5, 0))
3459 .await
3460 .unwrap();
3461 assert!(matches!(get_result, GetResult::Value(Value::Counter32(0))));
3462
3463 let handler = agent
3465 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 11, 2, 1, 1, 0))
3466 .expect("MPD stats handler should be registered");
3467 let get_result = handler
3468 .handler
3469 .get(&ctx, &oid!(1, 3, 6, 1, 6, 3, 11, 2, 1, 1, 0))
3470 .await
3471 .unwrap();
3472 assert!(matches!(get_result, GetResult::Value(Value::Counter32(0))));
3473 }
3474
3475 #[tokio::test]
3476 async fn test_builtin_handlers_disabled() {
3477 let agent = Agent::builder()
3478 .bind("127.0.0.1:0")
3479 .community(b"public")
3480 .without_builtin_handlers()
3481 .build()
3482 .await
3483 .unwrap();
3484
3485 assert!(
3486 agent
3487 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 10, 2, 1, 1, 0))
3488 .is_none()
3489 );
3490 assert!(
3491 agent
3492 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 1, 0))
3493 .is_none()
3494 );
3495 assert!(
3496 agent
3497 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 11, 2, 1, 1, 0))
3498 .is_none()
3499 );
3500 }
3501
3502 #[tokio::test]
3503 async fn test_builtin_handler_selective_disable() {
3504 let agent = Agent::builder()
3505 .bind("127.0.0.1:0")
3506 .community(b"public")
3507 .without_builtin_handler(BuiltinMib::UsmStats)
3508 .build()
3509 .await
3510 .unwrap();
3511
3512 assert!(
3513 agent
3514 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 10, 2, 1, 1, 0))
3515 .is_some()
3516 );
3517 assert!(
3518 agent
3519 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 1, 0))
3520 .is_none()
3521 );
3522 assert!(
3523 agent
3524 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 11, 2, 1, 1, 0))
3525 .is_some()
3526 );
3527 }
3528
3529 async fn small_limit_agent() -> Agent {
3532 Agent::builder()
3533 .bind("127.0.0.1:0")
3534 .community(b"public")
3535 .max_message_size(150)
3536 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(FiveOidHandler))
3537 .without_builtin_handlers()
3538 .build()
3539 .await
3540 .unwrap()
3541 }
3542
3543 fn five_varbinds() -> Vec<VarBind> {
3544 (1u32..=5)
3545 .map(|i| VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, i, 0), Value::Null))
3546 .collect()
3547 }
3548
3549 #[tokio::test]
3550 async fn test_get_too_big_returns_toobig_response() {
3551 let agent = small_limit_agent().await;
3552 let ctx = test_ctx();
3553
3554 let pdu = Pdu {
3557 pdu_type: PduType::GetRequest,
3558 request_id: 1,
3559 error_status: 0,
3560 error_index: 0,
3561 varbinds: five_varbinds(),
3562 };
3563
3564 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3565 assert_eq!(response.error_status, ErrorStatus::TooBig.as_i32());
3566 assert_eq!(response.error_index, 0);
3567 assert!(response.varbinds.is_empty());
3568 }
3569
3570 #[tokio::test]
3571 async fn test_get_too_big_v1_echoes_request_varbinds() {
3572 let agent = small_limit_agent().await;
3573
3574 let mut ctx = test_ctx();
3577 ctx.version = Version::V1;
3578 ctx.security_model = SecurityModel::V1;
3579
3580 let request_varbinds = five_varbinds();
3581 let pdu = Pdu {
3582 pdu_type: PduType::GetRequest,
3583 request_id: 1,
3584 error_status: 0,
3585 error_index: 0,
3586 varbinds: request_varbinds.clone(),
3587 };
3588
3589 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3590 assert_eq!(response.error_status, ErrorStatus::TooBig.as_i32());
3591 assert_eq!(response.error_index, 0);
3592 assert_eq!(response.varbinds, request_varbinds);
3593
3594 let v2c_response = agent.dispatch_request(&test_ctx(), &pdu).await.unwrap();
3596 assert_eq!(v2c_response.error_status, ErrorStatus::TooBig.as_i32());
3597 assert!(v2c_response.varbinds.is_empty());
3598 }
3599
3600 #[tokio::test]
3601 async fn test_get_within_limit_returns_response() {
3602 let agent = small_limit_agent().await;
3603 let ctx = test_ctx();
3604
3605 let pdu = Pdu {
3607 pdu_type: PduType::GetRequest,
3608 request_id: 1,
3609 error_status: 0,
3610 error_index: 0,
3611 varbinds: vec![VarBind::new(
3612 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
3613 Value::Null,
3614 )],
3615 };
3616
3617 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3618 assert_eq!(response.error_status, 0);
3619 assert_eq!(response.varbinds.len(), 1);
3620 assert!(matches!(response.varbinds[0].value, Value::Integer(1)));
3621 }
3622
3623 #[tokio::test]
3624 async fn test_getnext_too_big_returns_toobig_response() {
3625 let agent = small_limit_agent().await;
3626 let mut ctx = test_ctx();
3627 ctx.pdu_type = PduType::GetNextRequest;
3628
3629 let pdu = Pdu {
3630 pdu_type: PduType::GetNextRequest,
3631 request_id: 1,
3632 error_status: 0,
3633 error_index: 0,
3634 varbinds: five_varbinds(),
3635 };
3636
3637 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3638 assert_eq!(response.error_status, ErrorStatus::TooBig.as_i32());
3639 assert_eq!(response.error_index, 0);
3640 assert!(response.varbinds.is_empty());
3641 }
3642
3643 #[tokio::test]
3644 async fn test_inform_too_big_returns_toobig_response() {
3645 let agent = small_limit_agent().await;
3646 let mut ctx = test_ctx();
3647 ctx.pdu_type = PduType::InformRequest;
3648
3649 let big = Value::OctetString(Bytes::from(vec![0xABu8; 256]));
3655 let pdu = Pdu {
3656 pdu_type: PduType::InformRequest,
3657 request_id: 1,
3658 error_status: 0,
3659 error_index: 0,
3660 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0), big)],
3661 };
3662
3663 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3664 assert_eq!(response.error_status, ErrorStatus::TooBig.as_i32());
3665 assert_eq!(response.error_index, 0);
3666 assert!(response.varbinds.is_empty());
3667 }
3668
3669 #[tokio::test]
3670 async fn test_inform_within_limit_echoes_varbinds() {
3671 let agent = small_limit_agent().await;
3672 let mut ctx = test_ctx();
3673 ctx.pdu_type = PduType::InformRequest;
3674
3675 let pdu = Pdu {
3678 pdu_type: PduType::InformRequest,
3679 request_id: 7,
3680 error_status: 0,
3681 error_index: 0,
3682 varbinds: vec![VarBind::new(
3683 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
3684 Value::Integer(42),
3685 )],
3686 };
3687
3688 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3689 assert_eq!(response.pdu_type, PduType::Response);
3690 assert_eq!(response.error_status, 0);
3691 assert_eq!(response.request_id, 7);
3692 assert_eq!(response.varbinds.len(), 1);
3693 assert!(matches!(response.varbinds[0].value, Value::Integer(42)));
3694 }
3695
3696 #[tokio::test]
3697 async fn test_getnext_within_limit_returns_response() {
3698 let agent = small_limit_agent().await;
3699 let mut ctx = test_ctx();
3700 ctx.pdu_type = PduType::GetNextRequest;
3701
3702 let pdu = Pdu {
3703 pdu_type: PduType::GetNextRequest,
3704 request_id: 1,
3705 error_status: 0,
3706 error_index: 0,
3707 varbinds: vec![VarBind::new(
3708 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
3709 Value::Null,
3710 )],
3711 };
3712
3713 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
3714 assert_eq!(response.error_status, 0);
3715 assert_eq!(response.varbinds.len(), 1);
3716 assert_eq!(
3717 response.varbinds[0].oid,
3718 oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0)
3719 );
3720 }
3721}