async_snmp/handler/mod.rs
1//! Handler types and traits for SNMP MIB operations.
2//!
3//! This module provides the interface for implementing SNMP agent handlers:
4//!
5//! - [`MibHandler`] - Trait for handling GET, GETNEXT, and SET operations
6//! - [`RequestContext`] - Information about the incoming request
7//! - [`GetResult`], [`GetNextResult`], [`SetResult`] - Operation results
8//! - [`HandlerError`], [`HandlerResult`] - Processing failures, reported as `genErr`
9//! - [`OidTable`] - Helper for implementing GETNEXT with sorted OID storage
10//!
11//! # Overview
12//!
13//! Handlers are registered with an [`Agent`](crate::agent::Agent) using a prefix OID.
14//! When the agent receives a request, it dispatches to the handler with the longest
15//! matching prefix. Each handler implements the [`MibHandler`] trait to respond to
16//! GET, GETNEXT, and optionally SET operations.
17//!
18//! GET and GETNEXT return [`HandlerResult`], so `?` works on any
19//! [`std::error::Error`] inside a handler. `Ok` carries the protocol answer —
20//! including the "doesn't exist" exception values — while `Err` means the
21//! handler failed to produce one (e.g. its backing store was unreachable) and
22//! makes the agent answer the request with `genErr` (RFC 3416 Section 4.2.1).
23//!
24//! # Basic Handler Example
25//!
26//! A minimal handler that provides two scalar values:
27//!
28//! ```rust
29//! use async_snmp::handler::{MibHandler, RequestContext, GetResult, GetNextResult, HandlerResult, BoxFuture};
30//! use async_snmp::{Oid, Value, VarBind, oid};
31//!
32//! struct MyHandler;
33//!
34//! impl MibHandler for MyHandler {
35//! fn get<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, HandlerResult<GetResult>> {
36//! Box::pin(async move {
37//! if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
38//! return Ok(GetResult::Value(Value::Integer(42)));
39//! }
40//! Ok(GetResult::NoSuchObject)
41//! })
42//! }
43//!
44//! fn get_next<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
45//! Box::pin(async move {
46//! let my_oid = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
47//! if oid < &my_oid {
48//! return Ok(GetNextResult::Value(VarBind::new(my_oid, Value::Integer(42))));
49//! }
50//! Ok(GetNextResult::EndOfMibView)
51//! })
52//! }
53//! }
54//! ```
55//!
56//! # SET Operations and Multi-Phase Protocol
57//!
58//! SET operations follow a multi-phase protocol as defined in RFC 3416, modeled
59//! after net-snmp's RESERVE/ACTION/COMMIT/FREE/UNDO phases:
60//!
61//! 1. **Test Phase**: [`MibHandler::test_set`] is called for ALL varbinds before any
62//! commits. If any test fails, [`MibHandler::free_set`] is called for all previously
63//! successful varbinds (in reverse order) to release resources, then the error is
64//! returned.
65//!
66//! 2. **Commit Phase**: [`MibHandler::commit_set`] is called for each varbind in order.
67//! If a commit fails, [`MibHandler::undo_set`] is called for all previously committed
68//! varbinds in reverse order.
69//!
70//! By default, handlers are read-only (returning [`SetResult::NotWritable`]).
71//! See [`MibHandler`] documentation for implementation details.
72//!
73//! # Using `OidTable` for GETNEXT
74//!
75//! For handlers with static or slowly-changing data, [`OidTable`] simplifies
76//! GETNEXT implementation by maintaining OIDs in sorted order:
77//!
78//! ```rust
79//! use async_snmp::handler::{MibHandler, RequestContext, GetResult, GetNextResult, HandlerResult, OidTable, BoxFuture};
80//! use async_snmp::{Oid, Value, VarBind, oid};
81//!
82//! struct StaticHandler {
83//! table: OidTable<Value>,
84//! }
85//!
86//! impl StaticHandler {
87//! fn new() -> Self {
88//! let mut table = OidTable::new();
89//! table.insert(oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0), Value::Integer(100));
90//! table.insert(oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0), Value::OctetString("test".into()));
91//! Self { table }
92//! }
93//! }
94//!
95//! impl MibHandler for StaticHandler {
96//! fn get<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, HandlerResult<GetResult>> {
97//! Box::pin(async move {
98//! Ok(self.table.get(oid)
99//! .cloned()
100//! .map(GetResult::Value)
101//! .unwrap_or(GetResult::NoSuchObject))
102//! })
103//! }
104//!
105//! fn get_next<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
106//! Box::pin(async move {
107//! Ok(self.table.get_next(oid)
108//! .map(|(o, v)| GetNextResult::Value(VarBind::new(o.clone(), v.clone())))
109//! .unwrap_or(GetNextResult::EndOfMibView))
110//! })
111//! }
112//! }
113
114mod context;
115mod oid_table;
116mod results;
117mod traits;
118
119pub use context::RequestContext;
120pub use oid_table::OidTable;
121pub use results::{GetNextResult, GetResult, HandlerError, HandlerResult, Response, SetResult};
122pub use traits::{BoxFuture, MibHandler};
123
124/// Security model identifiers (RFC 3411).
125///
126/// Used to specify which SNMP version/security mechanism a mapping applies to.
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
128pub enum SecurityModel {
129 /// Wildcard for VACM matching (matches any model).
130 ///
131 /// Use this when the same mapping should apply regardless of SNMP version.
132 Any = 0,
133 /// `SNMPv1`.
134 V1 = 1,
135 /// `SNMPv2c`.
136 V2c = 2,
137 /// `SNMPv3` User-based Security Model.
138 Usm = 3,
139}