async_snmp/agent/
mod.rs

1//! SNMP Agent (RFC 3413).
2//!
3//! This module provides SNMP agent functionality for responding to
4//! GET, GETNEXT, GETBULK, and SET requests.
5//!
6//! # Features
7//!
8//! - **Async handlers**: All handler methods are async for database queries, network calls, etc.
9//! - **Atomic SET**: Two-phase commit protocol (test/commit/undo) per RFC 3416
10//! - **VACM support**: Optional View-based Access Control Model (RFC 3415)
11//!
12//! # Example
13//!
14//! ```rust,no_run
15//! use async_snmp::agent::Agent;
16//! use async_snmp::handler::{MibHandler, RequestContext, GetResult, GetNextResult, BoxFuture};
17//! use async_snmp::{Oid, Value, VarBind, oid};
18//! use std::sync::Arc;
19//!
20//! // Define a simple handler for the system MIB subtree
21//! struct SystemMibHandler;
22//!
23//! impl MibHandler for SystemMibHandler {
24//!     fn get<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
25//!         Box::pin(async move {
26//!             // sysDescr.0
27//!             if oid == &oid!(1, 3, 6, 1, 2, 1, 1, 1, 0) {
28//!                 return GetResult::Value(Value::OctetString("My SNMP Agent".into()));
29//!             }
30//!             // sysObjectID.0
31//!             if oid == &oid!(1, 3, 6, 1, 2, 1, 1, 2, 0) {
32//!                 return GetResult::Value(Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4, 1, 99999)));
33//!             }
34//!             GetResult::NoSuchObject
35//!         })
36//!     }
37//!
38//!     fn get_next<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetNextResult> {
39//!         Box::pin(async move {
40//!             // Return the lexicographically next OID after the given one
41//!             let sys_descr = oid!(1, 3, 6, 1, 2, 1, 1, 1, 0);
42//!             let sys_object_id = oid!(1, 3, 6, 1, 2, 1, 1, 2, 0);
43//!
44//!             if oid < &sys_descr {
45//!                 return GetNextResult::Value(VarBind::new(sys_descr, Value::OctetString("My SNMP Agent".into())));
46//!             }
47//!             if oid < &sys_object_id {
48//!                 return GetNextResult::Value(VarBind::new(sys_object_id, Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4, 1, 99999))));
49//!             }
50//!             GetNextResult::EndOfMibView
51//!         })
52//!     }
53//! }
54//!
55//! #[tokio::main]
56//! async fn main() -> Result<(), async_snmp::Error> {
57//!     let agent = Agent::builder()
58//!         .bind("0.0.0.0:161")
59//!         .community(b"public")
60//!         .handler(oid!(1, 3, 6, 1, 2, 1, 1), Arc::new(SystemMibHandler))
61//!         .build()
62//!         .await?;
63//!
64//!     agent.run().await
65//! }
66//! ```
67
68mod request;
69mod response;
70mod set_handler;
71pub mod vacm;
72
73pub use vacm::{SecurityModel, VacmBuilder, VacmConfig, View, ViewSubtree};
74
75use std::collections::HashMap;
76use std::net::SocketAddr;
77use std::sync::Arc;
78use std::sync::atomic::{AtomicU32, Ordering};
79use std::time::Instant;
80
81use bytes::Bytes;
82use subtle::ConstantTimeEq;
83use tokio::net::UdpSocket;
84use tracing::instrument;
85
86use crate::ber::Decoder;
87use crate::error::{DecodeErrorKind, Error, ErrorStatus, Result};
88use crate::handler::{GetNextResult, GetResult, MibHandler, RequestContext};
89use crate::notification::UsmUserConfig;
90use crate::oid::Oid;
91use crate::pdu::{Pdu, PduType};
92use crate::util::bind_udp_socket;
93use crate::v3::SaltCounter;
94use crate::value::Value;
95use crate::varbind::VarBind;
96use crate::version::Version;
97
98/// Default maximum message size for UDP (RFC 3417 recommendation).
99const DEFAULT_MAX_MESSAGE_SIZE: usize = 1472;
100
101/// Overhead for SNMP message encoding (approximate conservative estimate).
102/// This accounts for version, community/USM, PDU headers, etc.
103const RESPONSE_OVERHEAD: usize = 100;
104
105/// Registered handler with its OID prefix.
106pub(crate) struct RegisteredHandler {
107    pub(crate) prefix: Oid,
108    pub(crate) handler: Arc<dyn MibHandler>,
109}
110
111/// Builder for [`Agent`].
112///
113/// Use this builder to configure and construct an SNMP agent. The builder
114/// pattern allows you to chain configuration methods before calling
115/// [`build()`](AgentBuilder::build) to create the agent.
116///
117/// # Minimal Example
118///
119/// ```rust,no_run
120/// use async_snmp::agent::Agent;
121/// use async_snmp::handler::{MibHandler, RequestContext, GetResult, GetNextResult, BoxFuture};
122/// use async_snmp::{Oid, Value, VarBind, oid};
123/// use std::sync::Arc;
124///
125/// struct MyHandler;
126/// impl MibHandler for MyHandler {
127///     fn get<'a>(&'a self, _: &'a RequestContext, _: &'a Oid) -> BoxFuture<'a, GetResult> {
128///         Box::pin(async { GetResult::NoSuchObject })
129///     }
130///     fn get_next<'a>(&'a self, _: &'a RequestContext, _: &'a Oid) -> BoxFuture<'a, GetNextResult> {
131///         Box::pin(async { GetNextResult::EndOfMibView })
132///     }
133/// }
134///
135/// # async fn example() -> Result<(), async_snmp::Error> {
136/// let agent = Agent::builder()
137///     .bind("0.0.0.0:1161")  // Use non-privileged port
138///     .community(b"public")
139///     .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(MyHandler))
140///     .build()
141///     .await?;
142/// # Ok(())
143/// # }
144/// ```
145pub struct AgentBuilder {
146    bind_addr: String,
147    communities: Vec<Vec<u8>>,
148    usm_users: HashMap<Bytes, UsmUserConfig>,
149    handlers: Vec<RegisteredHandler>,
150    engine_id: Option<Vec<u8>>,
151    max_message_size: usize,
152    vacm: Option<VacmConfig>,
153}
154
155impl AgentBuilder {
156    /// Create a new builder with default settings.
157    ///
158    /// Defaults:
159    /// - Bind address: `0.0.0.0:161`
160    /// - Max message size: 1472 bytes (Ethernet MTU - IP/UDP headers)
161    /// - No communities or USM users (all requests rejected)
162    /// - No handlers registered
163    pub fn new() -> Self {
164        Self {
165            bind_addr: "0.0.0.0:161".to_string(),
166            communities: Vec::new(),
167            usm_users: HashMap::new(),
168            handlers: Vec::new(),
169            engine_id: None,
170            max_message_size: DEFAULT_MAX_MESSAGE_SIZE,
171            vacm: None,
172        }
173    }
174
175    /// Set the bind address.
176    ///
177    /// Default is `0.0.0.0:161` (standard SNMP port). Note that binding to
178    /// port 161 typically requires root/administrator privileges.
179    ///
180    /// # IPv4 Examples
181    ///
182    /// ```rust,no_run
183    /// use async_snmp::agent::Agent;
184    ///
185    /// # async fn example() -> Result<(), async_snmp::Error> {
186    /// // Bind to all IPv4 interfaces on standard port (requires privileges)
187    /// let agent = Agent::builder().bind("0.0.0.0:161").community(b"public").build().await?;
188    ///
189    /// // Bind to localhost only on non-privileged port
190    /// let agent = Agent::builder().bind("127.0.0.1:1161").community(b"public").build().await?;
191    ///
192    /// // Bind to specific interface
193    /// let agent = Agent::builder().bind("192.168.1.100:161").community(b"public").build().await?;
194    /// # Ok(())
195    /// # }
196    /// ```
197    ///
198    /// # IPv6 Examples
199    ///
200    /// ```rust,no_run
201    /// use async_snmp::agent::Agent;
202    ///
203    /// # async fn example() -> Result<(), async_snmp::Error> {
204    /// // Bind to all IPv6 interfaces (IPv6_V6ONLY is set, no IPv4 traffic)
205    /// let agent = Agent::builder().bind("[::]:161").community(b"public").build().await?;
206    ///
207    /// // Bind to IPv6 localhost
208    /// let agent = Agent::builder().bind("[::1]:1161").community(b"public").build().await?;
209    /// # Ok(())
210    /// # }
211    /// ```
212    pub fn bind(mut self, addr: impl Into<String>) -> Self {
213        self.bind_addr = addr.into();
214        self
215    }
216
217    /// Add an accepted community string for v1/v2c requests.
218    ///
219    /// Multiple communities can be added. If none are added,
220    /// all v1/v2c requests are rejected.
221    ///
222    /// # Example
223    ///
224    /// ```rust,no_run
225    /// use async_snmp::agent::Agent;
226    ///
227    /// # async fn example() -> Result<(), async_snmp::Error> {
228    /// let agent = Agent::builder()
229    ///     .bind("0.0.0.0:1161")
230    ///     .community(b"public")   // Read-only access
231    ///     .community(b"private")  // Read-write access (with VACM)
232    ///     .build()
233    ///     .await?;
234    /// # Ok(())
235    /// # }
236    /// ```
237    pub fn community(mut self, community: &[u8]) -> Self {
238        self.communities.push(community.to_vec());
239        self
240    }
241
242    /// Add multiple community strings.
243    ///
244    /// # Example
245    ///
246    /// ```rust,no_run
247    /// use async_snmp::agent::Agent;
248    ///
249    /// # async fn example() -> Result<(), async_snmp::Error> {
250    /// let communities = ["public", "private", "monitor"];
251    /// let agent = Agent::builder()
252    ///     .bind("0.0.0.0:1161")
253    ///     .communities(communities)
254    ///     .build()
255    ///     .await?;
256    /// # Ok(())
257    /// # }
258    /// ```
259    pub fn communities<I, C>(mut self, communities: I) -> Self
260    where
261        I: IntoIterator<Item = C>,
262        C: AsRef<[u8]>,
263    {
264        for c in communities {
265            self.communities.push(c.as_ref().to_vec());
266        }
267        self
268    }
269
270    /// Add a USM user for SNMPv3 authentication.
271    ///
272    /// Configure authentication and privacy settings using the closure.
273    /// Multiple users can be added with different security levels.
274    ///
275    /// # Security Levels
276    ///
277    /// - **noAuthNoPriv**: No authentication or encryption (not recommended)
278    /// - **authNoPriv**: Authentication only (HMAC verification)
279    /// - **authPriv**: Authentication and encryption (most secure)
280    ///
281    /// # Example
282    ///
283    /// ```rust,no_run
284    /// use async_snmp::agent::Agent;
285    /// use async_snmp::{AuthProtocol, PrivProtocol};
286    ///
287    /// # async fn example() -> Result<(), async_snmp::Error> {
288    /// let agent = Agent::builder()
289    ///     .bind("0.0.0.0:1161")
290    ///     // Read-only user with authentication only
291    ///     .usm_user("monitor", |u| {
292    ///         u.auth(AuthProtocol::Sha256, b"monitorpass123")
293    ///     })
294    ///     // Admin user with full encryption
295    ///     .usm_user("admin", |u| {
296    ///         u.auth(AuthProtocol::Sha256, b"adminauth123")
297    ///          .privacy(PrivProtocol::Aes128, b"adminpriv123")
298    ///     })
299    ///     .build()
300    ///     .await?;
301    /// # Ok(())
302    /// # }
303    /// ```
304    pub fn usm_user<F>(mut self, username: impl Into<Bytes>, configure: F) -> Self
305    where
306        F: FnOnce(UsmUserConfig) -> UsmUserConfig,
307    {
308        let username_bytes: Bytes = username.into();
309        let config = configure(UsmUserConfig::new(username_bytes.clone()));
310        self.usm_users.insert(username_bytes, config);
311        self
312    }
313
314    /// Set the engine ID for SNMPv3.
315    ///
316    /// If not set, a default engine ID will be generated based on the
317    /// RFC 3411 format using enterprise number and timestamp.
318    ///
319    /// # Example
320    ///
321    /// ```rust,no_run
322    /// use async_snmp::agent::Agent;
323    ///
324    /// # async fn example() -> Result<(), async_snmp::Error> {
325    /// let agent = Agent::builder()
326    ///     .bind("0.0.0.0:1161")
327    ///     .engine_id(b"\x80\x00\x00\x00\x01MyEngine".to_vec())
328    ///     .community(b"public")
329    ///     .build()
330    ///     .await?;
331    /// # Ok(())
332    /// # }
333    /// ```
334    pub fn engine_id(mut self, engine_id: impl Into<Vec<u8>>) -> Self {
335        self.engine_id = Some(engine_id.into());
336        self
337    }
338
339    /// Set the maximum message size for responses.
340    ///
341    /// Default is 1472 octets (fits Ethernet MTU minus IP/UDP headers).
342    /// GETBULK responses will be truncated to fit within this limit.
343    ///
344    /// For SNMPv3 requests, the agent uses the minimum of this value
345    /// and the msgMaxSize from the request.
346    pub fn max_message_size(mut self, size: usize) -> Self {
347        self.max_message_size = size;
348        self
349    }
350
351    /// Register a MIB handler for an OID subtree.
352    ///
353    /// Handlers are matched by longest prefix. When a request comes in,
354    /// the handler with the longest matching prefix is used.
355    ///
356    /// # Example
357    ///
358    /// ```rust,no_run
359    /// use async_snmp::agent::Agent;
360    /// use async_snmp::handler::{MibHandler, RequestContext, GetResult, GetNextResult, BoxFuture};
361    /// use async_snmp::{Oid, Value, VarBind, oid};
362    /// use std::sync::Arc;
363    ///
364    /// struct SystemHandler;
365    /// impl MibHandler for SystemHandler {
366    ///     fn get<'a>(&'a self, _: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
367    ///         Box::pin(async move {
368    ///             if oid == &oid!(1, 3, 6, 1, 2, 1, 1, 1, 0) {
369    ///                 GetResult::Value(Value::OctetString("My Agent".into()))
370    ///             } else {
371    ///                 GetResult::NoSuchObject
372    ///             }
373    ///         })
374    ///     }
375    ///     fn get_next<'a>(&'a self, _: &'a RequestContext, _: &'a Oid) -> BoxFuture<'a, GetNextResult> {
376    ///         Box::pin(async { GetNextResult::EndOfMibView })
377    ///     }
378    /// }
379    ///
380    /// # async fn example() -> Result<(), async_snmp::Error> {
381    /// let agent = Agent::builder()
382    ///     .bind("0.0.0.0:1161")
383    ///     .community(b"public")
384    ///     // Register handler for system MIB subtree
385    ///     .handler(oid!(1, 3, 6, 1, 2, 1, 1), Arc::new(SystemHandler))
386    ///     .build()
387    ///     .await?;
388    /// # Ok(())
389    /// # }
390    /// ```
391    pub fn handler(mut self, prefix: Oid, handler: Arc<dyn MibHandler>) -> Self {
392        self.handlers.push(RegisteredHandler { prefix, handler });
393        self
394    }
395
396    /// Configure VACM (View-based Access Control Model) using a builder function.
397    ///
398    /// When VACM is enabled, all requests are checked against the configured
399    /// access control rules. Requests that don't have proper access are rejected
400    /// with `noAccess` error (v2c/v3) or `noSuchName` (v1).
401    ///
402    /// # Example
403    ///
404    /// ```rust,no_run
405    /// use async_snmp::agent::{Agent, SecurityModel, VacmBuilder};
406    /// use async_snmp::message::SecurityLevel;
407    /// use async_snmp::oid;
408    ///
409    /// # async fn example() -> Result<(), async_snmp::Error> {
410    /// let agent = Agent::builder()
411    ///     .bind("0.0.0.0:161")
412    ///     .community(b"public")
413    ///     .community(b"private")
414    ///     .vacm(|v| v
415    ///         .group("public", SecurityModel::V2c, "readonly_group")
416    ///         .group("private", SecurityModel::V2c, "readwrite_group")
417    ///         .access("readonly_group", |a| a
418    ///             .read_view("full_view"))
419    ///         .access("readwrite_group", |a| a
420    ///             .read_view("full_view")
421    ///             .write_view("write_view"))
422    ///         .view("full_view", |v| v
423    ///             .include(oid!(1, 3, 6, 1)))
424    ///         .view("write_view", |v| v
425    ///             .include(oid!(1, 3, 6, 1, 2, 1, 1))))
426    ///     .build()
427    ///     .await?;
428    /// # Ok(())
429    /// # }
430    /// ```
431    pub fn vacm<F>(mut self, configure: F) -> Self
432    where
433        F: FnOnce(VacmBuilder) -> VacmBuilder,
434    {
435        let builder = VacmBuilder::new();
436        self.vacm = Some(configure(builder).build());
437        self
438    }
439
440    /// Build the agent.
441    ///
442    /// For IPv6 bind addresses, the socket has `IPV6_V6ONLY` set to true.
443    pub async fn build(mut self) -> Result<Agent> {
444        let bind_addr: std::net::SocketAddr = self.bind_addr.parse().map_err(|_| Error::Io {
445            target: None,
446            source: std::io::Error::new(
447                std::io::ErrorKind::InvalidInput,
448                format!("invalid bind address: {}", self.bind_addr),
449            ),
450        })?;
451
452        let socket = bind_udp_socket(bind_addr).await.map_err(|e| Error::Io {
453            target: Some(bind_addr),
454            source: e,
455        })?;
456
457        let local_addr = socket.local_addr().map_err(|e| Error::Io {
458            target: Some(bind_addr),
459            source: e,
460        })?;
461
462        // Generate default engine ID if not provided
463        let engine_id = self.engine_id.unwrap_or_else(|| {
464            // RFC 3411 format: enterprise number + format + local identifier
465            // Use a simple format: 0x80 (local) + timestamp + random
466            let mut id = vec![0x80, 0x00, 0x00, 0x00, 0x01]; // Enterprise format indicator
467            let timestamp = std::time::SystemTime::now()
468                .duration_since(std::time::UNIX_EPOCH)
469                .unwrap_or_default()
470                .as_secs();
471            id.extend_from_slice(&timestamp.to_be_bytes());
472            id
473        });
474
475        // Sort handlers by prefix length (longest first) for matching
476        self.handlers
477            .sort_by(|a, b| b.prefix.len().cmp(&a.prefix.len()));
478
479        Ok(Agent {
480            inner: Arc::new(AgentInner {
481                socket,
482                local_addr,
483                communities: self.communities,
484                usm_users: self.usm_users,
485                handlers: self.handlers,
486                engine_id,
487                engine_boots: AtomicU32::new(1),
488                engine_time: AtomicU32::new(0),
489                engine_start: Instant::now(),
490                salt_counter: SaltCounter::new(),
491                max_message_size: self.max_message_size,
492                vacm: self.vacm,
493                snmp_invalid_msgs: AtomicU32::new(0),
494                snmp_unknown_security_models: AtomicU32::new(0),
495                snmp_silent_drops: AtomicU32::new(0),
496            }),
497        })
498    }
499}
500
501impl Default for AgentBuilder {
502    fn default() -> Self {
503        Self::new()
504    }
505}
506
507/// Inner state shared across agent clones.
508pub(crate) struct AgentInner {
509    pub(crate) socket: UdpSocket,
510    pub(crate) local_addr: SocketAddr,
511    pub(crate) communities: Vec<Vec<u8>>,
512    pub(crate) usm_users: HashMap<Bytes, UsmUserConfig>,
513    pub(crate) handlers: Vec<RegisteredHandler>,
514    pub(crate) engine_id: Vec<u8>,
515    pub(crate) engine_boots: AtomicU32,
516    pub(crate) engine_time: AtomicU32,
517    pub(crate) engine_start: Instant,
518    pub(crate) salt_counter: SaltCounter,
519    pub(crate) max_message_size: usize,
520    pub(crate) vacm: Option<VacmConfig>,
521    // RFC 3412 statistics counters
522    /// snmpInvalidMsgs (1.3.6.1.6.3.11.2.1.2) - messages with invalid msgFlags
523    /// (e.g., privacy without authentication)
524    pub(crate) snmp_invalid_msgs: AtomicU32,
525    /// snmpUnknownSecurityModels (1.3.6.1.6.3.11.2.1.1) - messages with
526    /// unrecognized security model
527    pub(crate) snmp_unknown_security_models: AtomicU32,
528    /// snmpSilentDrops (1.3.6.1.6.3.11.2.1.3) - confirmed-class PDUs silently
529    /// dropped because even an empty response would exceed max message size
530    pub(crate) snmp_silent_drops: AtomicU32,
531}
532
533/// SNMP Agent.
534///
535/// Listens for and responds to SNMP requests (GET, GETNEXT, GETBULK, SET).
536///
537/// # Example
538///
539/// ```rust,no_run
540/// use async_snmp::agent::Agent;
541/// use async_snmp::oid;
542///
543/// # async fn example() -> Result<(), async_snmp::Error> {
544/// let agent = Agent::builder()
545///     .bind("0.0.0.0:161")
546///     .community(b"public")
547///     .build()
548///     .await?;
549///
550/// agent.run().await
551/// # }
552/// ```
553pub struct Agent {
554    pub(crate) inner: Arc<AgentInner>,
555}
556
557impl Agent {
558    /// Create a builder for configuring the agent.
559    pub fn builder() -> AgentBuilder {
560        AgentBuilder::new()
561    }
562
563    /// Get the local address the agent is bound to.
564    pub fn local_addr(&self) -> SocketAddr {
565        self.inner.local_addr
566    }
567
568    /// Get the engine ID.
569    pub fn engine_id(&self) -> &[u8] {
570        &self.inner.engine_id
571    }
572
573    /// Get the snmpInvalidMsgs counter value.
574    ///
575    /// This counter tracks messages with invalid msgFlags, such as
576    /// privacy-without-authentication (RFC 3412 Section 7.2 Step 5d).
577    ///
578    /// OID: 1.3.6.1.6.3.11.2.1.2
579    pub fn snmp_invalid_msgs(&self) -> u32 {
580        self.inner.snmp_invalid_msgs.load(Ordering::Relaxed)
581    }
582
583    /// Get the snmpUnknownSecurityModels counter value.
584    ///
585    /// This counter tracks messages with unrecognized security models
586    /// (RFC 3412 Section 7.2 Step 2).
587    ///
588    /// OID: 1.3.6.1.6.3.11.2.1.1
589    pub fn snmp_unknown_security_models(&self) -> u32 {
590        self.inner
591            .snmp_unknown_security_models
592            .load(Ordering::Relaxed)
593    }
594
595    /// Get the snmpSilentDrops counter value.
596    ///
597    /// This counter tracks confirmed-class PDUs (GetRequest, GetNextRequest,
598    /// GetBulkRequest, SetRequest, InformRequest) that were silently dropped
599    /// because even an empty Response-PDU would exceed the maximum message
600    /// size constraint (RFC 3412 Section 7.1).
601    ///
602    /// OID: 1.3.6.1.6.3.11.2.1.3
603    pub fn snmp_silent_drops(&self) -> u32 {
604        self.inner.snmp_silent_drops.load(Ordering::Relaxed)
605    }
606
607    /// Run the agent, processing requests indefinitely.
608    ///
609    /// This method runs until an error occurs or the task is cancelled.
610    #[instrument(skip(self), err, fields(snmp.local_addr = %self.local_addr()))]
611    pub async fn run(&self) -> Result<()> {
612        let mut buf = vec![0u8; 65535];
613
614        loop {
615            let (len, source) =
616                self.inner
617                    .socket
618                    .recv_from(&mut buf)
619                    .await
620                    .map_err(|e| Error::Io {
621                        target: Some(self.inner.local_addr),
622                        source: e,
623                    })?;
624
625            let data = Bytes::copy_from_slice(&buf[..len]);
626
627            // Update engine time before processing
628            self.update_engine_time();
629
630            match self.handle_request(data, source).await {
631                Ok(Some(response_bytes)) => {
632                    if let Err(e) = self.inner.socket.send_to(&response_bytes, source).await {
633                        tracing::warn!(snmp.source = %source, error = %e, "failed to send response");
634                    }
635                }
636                Ok(None) => {
637                    // No response needed (e.g., invalid message)
638                }
639                Err(e) => {
640                    tracing::warn!(snmp.source = %source, error = %e, "error handling request");
641                }
642            }
643        }
644    }
645
646    /// Process a single request and return the response bytes.
647    ///
648    /// Returns `None` if no response should be sent.
649    async fn handle_request(&self, data: Bytes, source: SocketAddr) -> Result<Option<Bytes>> {
650        // Peek at version
651        let mut decoder = Decoder::new(data.clone());
652        let mut seq = decoder.read_sequence()?;
653        let version_num = seq.read_integer()?;
654        let version = Version::from_i32(version_num).ok_or_else(|| {
655            Error::decode(seq.offset(), DecodeErrorKind::UnknownVersion(version_num))
656        })?;
657        drop(seq);
658        drop(decoder);
659
660        match version {
661            Version::V1 => self.handle_v1(data, source).await,
662            Version::V2c => self.handle_v2c(data, source).await,
663            Version::V3 => self.handle_v3(data, source).await,
664        }
665    }
666
667    /// Update engine time based on elapsed time since start.
668    fn update_engine_time(&self) {
669        let elapsed = self.inner.engine_start.elapsed().as_secs() as u32;
670        self.inner.engine_time.store(elapsed, Ordering::Relaxed);
671    }
672
673    /// Validate community string using constant-time comparison.
674    ///
675    /// Uses constant-time comparison to prevent timing attacks that could
676    /// be used to guess valid community strings character by character.
677    pub(crate) fn validate_community(&self, community: &[u8]) -> bool {
678        if self.inner.communities.is_empty() {
679            // No communities configured = reject all
680            return false;
681        }
682        // Use constant-time comparison for each community string.
683        // We compare against all configured communities regardless of
684        // early matches to maintain constant-time behavior.
685        let mut valid = false;
686        for configured in &self.inner.communities {
687            // ct_eq returns a Choice, which we convert to bool after comparison
688            if configured.len() == community.len()
689                && bool::from(configured.as_slice().ct_eq(community))
690            {
691                valid = true;
692            }
693        }
694        valid
695    }
696
697    /// Dispatch a request to the appropriate handler.
698    async fn dispatch_request(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
699        match pdu.pdu_type {
700            PduType::GetRequest => self.handle_get(ctx, pdu).await,
701            PduType::GetNextRequest => self.handle_get_next(ctx, pdu).await,
702            PduType::GetBulkRequest => self.handle_get_bulk(ctx, pdu).await,
703            PduType::SetRequest => self.handle_set(ctx, pdu).await,
704            PduType::InformRequest => self.handle_inform(pdu),
705            _ => {
706                // Should not happen - filtered earlier
707                Ok(pdu.to_error_response(ErrorStatus::GenErr, 0))
708            }
709        }
710    }
711
712    /// Handle InformRequest PDU.
713    ///
714    /// Per RFC 3416 Section 4.2.7, an InformRequest is a confirmed-class PDU
715    /// that the receiver acknowledges by returning a Response with the same
716    /// request-id and varbind list.
717    fn handle_inform(&self, pdu: &Pdu) -> Result<Pdu> {
718        // Simply acknowledge by returning the same varbinds in a Response
719        Ok(Pdu {
720            pdu_type: PduType::Response,
721            request_id: pdu.request_id,
722            error_status: 0,
723            error_index: 0,
724            varbinds: pdu.varbinds.clone(),
725        })
726    }
727
728    /// Handle GET request.
729    async fn handle_get(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
730        let mut response_varbinds = Vec::with_capacity(pdu.varbinds.len());
731
732        for (index, vb) in pdu.varbinds.iter().enumerate() {
733            // VACM read access check
734            if let Some(ref vacm) = self.inner.vacm
735                && !vacm.check_access(ctx.read_view.as_ref(), &vb.oid)
736            {
737                // v1: noSuchName, v2c/v3: noAccess or NoSuchObject
738                if ctx.version == Version::V1 {
739                    return Ok(Pdu {
740                        pdu_type: PduType::Response,
741                        request_id: pdu.request_id,
742                        error_status: ErrorStatus::NoSuchName.as_i32(),
743                        error_index: (index + 1) as i32,
744                        varbinds: pdu.varbinds.clone(),
745                    });
746                } else {
747                    // For GET, return NoSuchObject for inaccessible OIDs per RFC 3415
748                    response_varbinds.push(VarBind::new(vb.oid.clone(), Value::NoSuchObject));
749                    continue;
750                }
751            }
752
753            let result = if let Some(handler) = self.find_handler(&vb.oid) {
754                handler.handler.get(ctx, &vb.oid).await
755            } else {
756                GetResult::NoSuchObject
757            };
758
759            let response_value = match result {
760                GetResult::Value(v) => v,
761                GetResult::NoSuchObject => {
762                    // v1 returns noSuchName error, v2c/v3 returns NoSuchObject exception
763                    if ctx.version == Version::V1 {
764                        return Ok(Pdu {
765                            pdu_type: PduType::Response,
766                            request_id: pdu.request_id,
767                            error_status: ErrorStatus::NoSuchName.as_i32(),
768                            error_index: (index + 1) as i32,
769                            varbinds: pdu.varbinds.clone(),
770                        });
771                    } else {
772                        Value::NoSuchObject
773                    }
774                }
775                GetResult::NoSuchInstance => {
776                    // v1 returns noSuchName error, v2c/v3 returns NoSuchInstance exception
777                    if ctx.version == Version::V1 {
778                        return Ok(Pdu {
779                            pdu_type: PduType::Response,
780                            request_id: pdu.request_id,
781                            error_status: ErrorStatus::NoSuchName.as_i32(),
782                            error_index: (index + 1) as i32,
783                            varbinds: pdu.varbinds.clone(),
784                        });
785                    } else {
786                        Value::NoSuchInstance
787                    }
788                }
789            };
790
791            response_varbinds.push(VarBind::new(vb.oid.clone(), response_value));
792        }
793
794        Ok(Pdu {
795            pdu_type: PduType::Response,
796            request_id: pdu.request_id,
797            error_status: 0,
798            error_index: 0,
799            varbinds: response_varbinds,
800        })
801    }
802
803    /// Handle GETNEXT request.
804    async fn handle_get_next(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
805        let mut response_varbinds = Vec::with_capacity(pdu.varbinds.len());
806
807        for (index, vb) in pdu.varbinds.iter().enumerate() {
808            // Try to find the next OID from any handler
809            let next = self.get_next_oid(ctx, &vb.oid).await;
810
811            // Check VACM access for the returned OID (if VACM enabled)
812            let next = if let Some(ref next_vb) = next {
813                if let Some(ref vacm) = self.inner.vacm {
814                    if vacm.check_access(ctx.read_view.as_ref(), &next_vb.oid) {
815                        next
816                    } else {
817                        // OID not accessible, continue searching
818                        // For simplicity, we just return EndOfMibView here
819                        // A more complete implementation would continue the search
820                        None
821                    }
822                } else {
823                    next
824                }
825            } else {
826                next
827            };
828
829            match next {
830                Some(next_vb) => {
831                    response_varbinds.push(next_vb);
832                }
833                None => {
834                    // v1 returns noSuchName, v2c/v3 returns endOfMibView
835                    if ctx.version == Version::V1 {
836                        return Ok(Pdu {
837                            pdu_type: PduType::Response,
838                            request_id: pdu.request_id,
839                            error_status: ErrorStatus::NoSuchName.as_i32(),
840                            error_index: (index + 1) as i32,
841                            varbinds: pdu.varbinds.clone(),
842                        });
843                    } else {
844                        response_varbinds.push(VarBind::new(vb.oid.clone(), Value::EndOfMibView));
845                    }
846                }
847            }
848        }
849
850        Ok(Pdu {
851            pdu_type: PduType::Response,
852            request_id: pdu.request_id,
853            error_status: 0,
854            error_index: 0,
855            varbinds: response_varbinds,
856        })
857    }
858
859    /// Handle GETBULK request.
860    ///
861    /// Per RFC 3416 Section 4.2.3, if the response would exceed the message
862    /// size limit, we return fewer variable bindings rather than all of them.
863    async fn handle_get_bulk(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
864        // For GETBULK, error_status is non_repeaters and error_index is max_repetitions
865        let non_repeaters = pdu.error_status.max(0) as usize;
866        let max_repetitions = pdu.error_index.max(0) as usize;
867
868        let mut response_varbinds = Vec::new();
869        let mut current_size: usize = RESPONSE_OVERHEAD;
870        let max_size = self.inner.max_message_size;
871
872        // Helper to check if we can add a varbind
873        let can_add = |vb: &VarBind, current_size: usize| -> bool {
874            current_size + vb.encoded_size() <= max_size
875        };
876
877        // Handle non-repeaters (first N varbinds get one GETNEXT each)
878        for vb in pdu.varbinds.iter().take(non_repeaters) {
879            let next_vb = match self.get_next_oid(ctx, &vb.oid).await {
880                Some(next_vb) => next_vb,
881                None => VarBind::new(vb.oid.clone(), Value::EndOfMibView),
882            };
883
884            if !can_add(&next_vb, current_size) {
885                // Can't fit even non-repeaters, return tooBig if we have nothing
886                if response_varbinds.is_empty() {
887                    return Ok(Pdu {
888                        pdu_type: PduType::Response,
889                        request_id: pdu.request_id,
890                        error_status: ErrorStatus::TooBig.as_i32(),
891                        error_index: 0,
892                        varbinds: pdu.varbinds.clone(),
893                    });
894                }
895                // Otherwise return what we have
896                break;
897            }
898
899            current_size += next_vb.encoded_size();
900            response_varbinds.push(next_vb);
901        }
902
903        // Handle repeaters
904        if non_repeaters < pdu.varbinds.len() {
905            let repeaters = &pdu.varbinds[non_repeaters..];
906            let mut current_oids: Vec<Oid> = repeaters.iter().map(|vb| vb.oid.clone()).collect();
907            let mut all_done = vec![false; repeaters.len()];
908
909            'outer: for _ in 0..max_repetitions {
910                let mut row_complete = true;
911                for (i, oid) in current_oids.iter_mut().enumerate() {
912                    let next_vb = if all_done[i] {
913                        VarBind::new(oid.clone(), Value::EndOfMibView)
914                    } else {
915                        match self.get_next_oid(ctx, oid).await {
916                            Some(next_vb) => {
917                                *oid = next_vb.oid.clone();
918                                row_complete = false;
919                                next_vb
920                            }
921                            None => {
922                                all_done[i] = true;
923                                VarBind::new(oid.clone(), Value::EndOfMibView)
924                            }
925                        }
926                    };
927
928                    // Check size before adding
929                    if !can_add(&next_vb, current_size) {
930                        // Can't fit more, return what we have
931                        break 'outer;
932                    }
933
934                    current_size += next_vb.encoded_size();
935                    response_varbinds.push(next_vb);
936                }
937
938                if row_complete {
939                    break;
940                }
941            }
942        }
943
944        Ok(Pdu {
945            pdu_type: PduType::Response,
946            request_id: pdu.request_id,
947            error_status: 0,
948            error_index: 0,
949            varbinds: response_varbinds,
950        })
951    }
952
953    /// Find the handler for a given OID.
954    pub(crate) fn find_handler(&self, oid: &Oid) -> Option<&RegisteredHandler> {
955        // Handlers are sorted by prefix length (longest first)
956        self.inner
957            .handlers
958            .iter()
959            .find(|&handler| handler.handler.handles(&handler.prefix, oid))
960            .map(|v| v as _)
961    }
962
963    /// Get the next OID from any handler.
964    async fn get_next_oid(&self, ctx: &RequestContext, oid: &Oid) -> Option<VarBind> {
965        // Find the first handler that can provide a next OID
966        let mut best_result: Option<VarBind> = None;
967
968        for handler in &self.inner.handlers {
969            if let GetNextResult::Value(next) = handler.handler.get_next(ctx, oid).await {
970                // Must be lexicographically greater than the request OID
971                if next.oid > *oid {
972                    match &best_result {
973                        None => best_result = Some(next),
974                        Some(current) if next.oid < current.oid => best_result = Some(next),
975                        _ => {}
976                    }
977                }
978            }
979        }
980
981        best_result
982    }
983}
984
985impl Clone for Agent {
986    fn clone(&self) -> Self {
987        Self {
988            inner: Arc::clone(&self.inner),
989        }
990    }
991}
992
993#[cfg(test)]
994mod tests {
995    use super::*;
996    use crate::handler::{
997        BoxFuture, GetNextResult, GetResult, MibHandler, RequestContext, SecurityModel, SetResult,
998    };
999    use crate::message::SecurityLevel;
1000    use crate::oid;
1001
1002    struct TestHandler;
1003
1004    impl MibHandler for TestHandler {
1005        fn get<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
1006            Box::pin(async move {
1007                if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
1008                    return GetResult::Value(Value::Integer(42));
1009                }
1010                if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0) {
1011                    return GetResult::Value(Value::OctetString(Bytes::from_static(b"test")));
1012                }
1013                GetResult::NoSuchObject
1014            })
1015        }
1016
1017        fn get_next<'a>(
1018            &'a self,
1019            _ctx: &'a RequestContext,
1020            oid: &'a Oid,
1021        ) -> BoxFuture<'a, GetNextResult> {
1022            Box::pin(async move {
1023                let oid1 = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
1024                let oid2 = oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0);
1025
1026                if oid < &oid1 {
1027                    return GetNextResult::Value(VarBind::new(oid1, Value::Integer(42)));
1028                }
1029                if oid < &oid2 {
1030                    return GetNextResult::Value(VarBind::new(
1031                        oid2,
1032                        Value::OctetString(Bytes::from_static(b"test")),
1033                    ));
1034                }
1035                GetNextResult::EndOfMibView
1036            })
1037        }
1038    }
1039
1040    fn test_ctx() -> RequestContext {
1041        RequestContext {
1042            source: "127.0.0.1:12345".parse().unwrap(),
1043            version: Version::V2c,
1044            security_model: SecurityModel::V2c,
1045            security_name: Bytes::from_static(b"public"),
1046            security_level: SecurityLevel::NoAuthNoPriv,
1047            context_name: Bytes::new(),
1048            request_id: 1,
1049            pdu_type: PduType::GetRequest,
1050            group_name: None,
1051            read_view: None,
1052            write_view: None,
1053        }
1054    }
1055
1056    #[test]
1057    fn test_agent_builder_defaults() {
1058        let builder = AgentBuilder::new();
1059        assert_eq!(builder.bind_addr, "0.0.0.0:161");
1060        assert!(builder.communities.is_empty());
1061        assert!(builder.usm_users.is_empty());
1062        assert!(builder.handlers.is_empty());
1063    }
1064
1065    #[test]
1066    fn test_agent_builder_community() {
1067        let builder = AgentBuilder::new()
1068            .community(b"public")
1069            .community(b"private");
1070        assert_eq!(builder.communities.len(), 2);
1071    }
1072
1073    #[test]
1074    fn test_agent_builder_communities() {
1075        let builder = AgentBuilder::new().communities(["public", "private"]);
1076        assert_eq!(builder.communities.len(), 2);
1077    }
1078
1079    #[test]
1080    fn test_agent_builder_handler() {
1081        let builder =
1082            AgentBuilder::new().handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(TestHandler));
1083        assert_eq!(builder.handlers.len(), 1);
1084    }
1085
1086    #[tokio::test]
1087    async fn test_mib_handler_default_set() {
1088        let handler = TestHandler;
1089        let mut ctx = test_ctx();
1090        ctx.pdu_type = PduType::SetRequest;
1091
1092        let result = handler
1093            .test_set(&ctx, &oid!(1, 3, 6, 1), &Value::Integer(1))
1094            .await;
1095        assert_eq!(result, SetResult::NotWritable);
1096    }
1097
1098    #[test]
1099    fn test_mib_handler_handles() {
1100        let handler = TestHandler;
1101        let prefix = oid!(1, 3, 6, 1, 4, 1, 99999);
1102
1103        // OID within prefix
1104        assert!(handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0)));
1105
1106        // OID before prefix (GETNEXT should still try)
1107        assert!(handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 99998)));
1108
1109        // OID after prefix (not handled)
1110        assert!(!handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 100000)));
1111    }
1112
1113    #[tokio::test]
1114    async fn test_test_handler_get() {
1115        let handler = TestHandler;
1116        let ctx = test_ctx();
1117
1118        // Existing OID
1119        let result = handler
1120            .get(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0))
1121            .await;
1122        assert!(matches!(result, GetResult::Value(Value::Integer(42))));
1123
1124        // Non-existing OID
1125        let result = handler
1126            .get(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 99, 0))
1127            .await;
1128        assert!(matches!(result, GetResult::NoSuchObject));
1129    }
1130
1131    #[tokio::test]
1132    async fn test_test_handler_get_next() {
1133        let handler = TestHandler;
1134        let mut ctx = test_ctx();
1135        ctx.pdu_type = PduType::GetNextRequest;
1136
1137        // Before first OID
1138        let next = handler.get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999)).await;
1139        assert!(next.is_value());
1140        if let GetNextResult::Value(vb) = next {
1141            assert_eq!(vb.oid, oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0));
1142        }
1143
1144        // Between OIDs
1145        let next = handler
1146            .get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0))
1147            .await;
1148        assert!(next.is_value());
1149        if let GetNextResult::Value(vb) = next {
1150            assert_eq!(vb.oid, oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0));
1151        }
1152
1153        // After last OID
1154        let next = handler
1155            .get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0))
1156            .await;
1157        assert!(next.is_end_of_mib_view());
1158    }
1159}