async_snmp/handler/traits.rs
1//! `MibHandler` trait and related types.
2
3use std::future::Future;
4use std::pin::Pin;
5
6use crate::oid::Oid;
7use crate::value::Value;
8
9use super::{GetNextResult, GetResult, HandlerResult, RequestContext, SetResult};
10
11/// Type alias for boxed async return type (dyn-compatible).
12///
13/// This type is required because async trait methods cannot be object-safe.
14/// All handler methods return `BoxFuture` to allow handlers to be stored
15/// as trait objects in the agent.
16///
17/// # Example
18///
19/// ```rust
20/// use async_snmp::handler::{BoxFuture, GetResult, HandlerResult};
21///
22/// fn example_async_fn<'a>(value: &'a i32) -> BoxFuture<'a, HandlerResult<GetResult>> {
23/// Box::pin(async move {
24/// // Async work here
25/// Ok(GetResult::Value(async_snmp::Value::Integer(*value)))
26/// })
27/// }
28/// ```
29pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
30
31/// Handler for SNMP MIB operations.
32///
33/// Implement this trait to provide values for a subtree of OIDs.
34/// Register handlers with [`AgentBuilder::handler()`](crate::agent::AgentBuilder::handler)
35/// using a prefix OID.
36///
37/// # Required Methods
38///
39/// - [`get`](MibHandler::get): Handle GET requests for specific OIDs
40/// - [`get_next`](MibHandler::get_next): Handle GETNEXT/GETBULK requests
41///
42/// # Optional Methods
43///
44/// - [`test_set`](MibHandler::test_set): Validate SET operations (default: read-only)
45/// - [`commit_set`](MibHandler::commit_set): Apply SET operations (default: read-only)
46/// - [`undo_set`](MibHandler::undo_set): Rollback failed SET operations
47/// - [`free_set`](MibHandler::free_set): Cleanup resources on test failure
48/// - [`handles`](MibHandler::handles): Custom OID matching logic
49///
50/// # GET Implementation
51///
52/// The [`get`](MibHandler::get) method should return:
53/// - `Ok(`[`GetResult::Value`]`)` if the OID exists and has a value
54/// - `Ok(`[`GetResult::NoSuchObject`]`)` if the object type is not implemented
55/// - `Ok(`[`GetResult::NoSuchInstance`]`)` if the object exists but this instance doesn't
56/// - `Err(`[`HandlerError`](super::HandlerError)`)` if the handler failed to
57/// determine an answer (backing store unreachable, hardware fault, ...)
58///
59/// # GETNEXT and Lexicographic Ordering
60///
61/// The [`get_next`](MibHandler::get_next) method must return the lexicographically
62/// next OID after the requested one. OIDs are compared arc-by-arc as unsigned integers.
63/// For example: `1.3.6.1.2` < `1.3.6.1.2.1` < `1.3.6.1.3`.
64///
65/// Key considerations:
66/// - The returned OID must be strictly greater than the input OID
67/// - GETBULK uses GETNEXT repeatedly, so efficient implementation matters
68/// - Use [`OidTable`](super::OidTable) to simplify sorted OID management
69///
70/// # Error Handling
71///
72/// Both methods return [`HandlerResult`], so `?` works on any error type
73/// implementing [`std::error::Error`]. Return `Err` only for *processing
74/// failures* — "I could not find out" — never for "the object does not
75/// exist", which is expressed by the `Ok` variants above. On `Err`, the
76/// agent responds to the whole request with `genErr` and the error-index of
77/// the failing variable binding (RFC 3416 Section 4.2.1), and logs the
78/// error; the message is never sent to the manager.
79///
80/// # SET Two-Phase Commit (RFC 3416)
81///
82/// SET operations use a multi-phase protocol modeled after net-snmp's
83/// RESERVE1/RESERVE2/ACTION/COMMIT/FREE/UNDO phases:
84///
85/// 1. **Test phase**: [`test_set`](MibHandler::test_set) is called for ALL varbinds
86/// before any commits. If any test fails, [`free_set`](MibHandler::free_set)
87/// is called for all previously successful varbinds (in reverse order) to
88/// release resources allocated during the test phase.
89///
90/// 2. **Commit phase**: [`commit_set`](MibHandler::commit_set) is called for each
91/// varbind in order. If a commit fails, [`undo_set`](MibHandler::undo_set) is
92/// called for all previously committed varbinds (in reverse order).
93///
94/// By default, handlers are read-only and return [`SetResult::NotWritable`].
95///
96/// # Bounds
97///
98/// The `'static` bound is required because handlers are stored as
99/// `Arc<dyn MibHandler>` within the agent. This allows the agent to
100/// hold handlers for its entire lifetime without lifetime annotations.
101/// In practice, most handlers naturally satisfy this bound.
102///
103/// # Thread Safety
104///
105/// Handlers must be `Send + Sync` because the agent may process
106/// requests concurrently from multiple tasks.
107///
108/// # Example: Read-Only Handler
109///
110/// ```rust
111/// use async_snmp::handler::{
112/// MibHandler, RequestContext, GetResult, GetNextResult, HandlerResult, BoxFuture,
113/// };
114/// use async_snmp::{Oid, Value, VarBind, oid};
115///
116/// struct SystemInfoHandler {
117/// sys_descr: String,
118/// sys_uptime: u32,
119/// }
120///
121/// impl MibHandler for SystemInfoHandler {
122/// fn get<'a>(
123/// &'a self,
124/// _ctx: &'a RequestContext,
125/// oid: &'a Oid,
126/// ) -> BoxFuture<'a, HandlerResult<GetResult>> {
127/// Box::pin(async move {
128/// // sysDescr.0
129/// if oid == &oid!(1, 3, 6, 1, 2, 1, 1, 1, 0) {
130/// return Ok(GetResult::Value(Value::OctetString(self.sys_descr.clone().into())));
131/// }
132/// // sysUpTime.0
133/// if oid == &oid!(1, 3, 6, 1, 2, 1, 1, 3, 0) {
134/// return Ok(GetResult::Value(Value::TimeTicks(self.sys_uptime)));
135/// }
136/// Ok(GetResult::NoSuchObject)
137/// })
138/// }
139///
140/// fn get_next<'a>(
141/// &'a self,
142/// _ctx: &'a RequestContext,
143/// oid: &'a Oid,
144/// ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
145/// Box::pin(async move {
146/// let sys_descr = oid!(1, 3, 6, 1, 2, 1, 1, 1, 0);
147/// let sys_uptime = oid!(1, 3, 6, 1, 2, 1, 1, 3, 0);
148///
149/// // Return the next OID in lexicographic order
150/// if oid < &sys_descr {
151/// return Ok(GetNextResult::Value(VarBind::new(
152/// sys_descr,
153/// Value::OctetString("My System".into())
154/// )));
155/// }
156/// if oid < &sys_uptime {
157/// return Ok(GetNextResult::Value(VarBind::new(
158/// sys_uptime,
159/// Value::TimeTicks(12345)
160/// )));
161/// }
162/// Ok(GetNextResult::EndOfMibView)
163/// })
164/// }
165/// }
166/// ```
167///
168/// # Example: Writable Handler
169///
170/// ```rust
171/// use async_snmp::handler::{
172/// MibHandler, RequestContext, GetResult, GetNextResult, HandlerResult, SetResult, BoxFuture
173/// };
174/// use async_snmp::{Oid, Value, VarBind, oid};
175/// use std::sync::atomic::{AtomicI32, Ordering};
176///
177/// struct WritableHandler {
178/// counter: AtomicI32,
179/// }
180///
181/// impl MibHandler for WritableHandler {
182/// fn get<'a>(
183/// &'a self,
184/// _ctx: &'a RequestContext,
185/// oid: &'a Oid,
186/// ) -> BoxFuture<'a, HandlerResult<GetResult>> {
187/// Box::pin(async move {
188/// if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
189/// return Ok(GetResult::Value(Value::Integer(
190/// self.counter.load(Ordering::Relaxed)
191/// )));
192/// }
193/// Ok(GetResult::NoSuchObject)
194/// })
195/// }
196///
197/// fn get_next<'a>(
198/// &'a self,
199/// _ctx: &'a RequestContext,
200/// oid: &'a Oid,
201/// ) -> BoxFuture<'a, HandlerResult<GetNextResult>> {
202/// Box::pin(async move {
203/// let my_oid = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
204/// if oid < &my_oid {
205/// return Ok(GetNextResult::Value(VarBind::new(
206/// my_oid,
207/// Value::Integer(self.counter.load(Ordering::Relaxed))
208/// )));
209/// }
210/// Ok(GetNextResult::EndOfMibView)
211/// })
212/// }
213///
214/// fn test_set<'a>(
215/// &'a self,
216/// _ctx: &'a RequestContext,
217/// oid: &'a Oid,
218/// value: &'a Value,
219/// ) -> BoxFuture<'a, SetResult> {
220/// Box::pin(async move {
221/// if oid != &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
222/// return SetResult::NotWritable;
223/// }
224/// // Validate the value type
225/// match value {
226/// Value::Integer(_) => SetResult::Ok,
227/// _ => SetResult::WrongType,
228/// }
229/// })
230/// }
231///
232/// fn commit_set<'a>(
233/// &'a self,
234/// _ctx: &'a RequestContext,
235/// _oid: &'a Oid,
236/// value: &'a Value,
237/// ) -> BoxFuture<'a, SetResult> {
238/// Box::pin(async move {
239/// if let Value::Integer(v) = value {
240/// self.counter.store(*v, Ordering::Relaxed);
241/// SetResult::Ok
242/// } else {
243/// SetResult::CommitFailed
244/// }
245/// })
246/// }
247/// }
248/// ```
249pub trait MibHandler: Send + Sync + 'static {
250 /// Handle a GET request for a specific OID.
251 ///
252 /// Return `Ok(`[`GetResult::Value`]`)` if the OID exists,
253 /// `Ok(`[`GetResult::NoSuchObject`]`)` if the object type is not implemented,
254 /// or `Ok(`[`GetResult::NoSuchInstance`]`)` if the object type exists but
255 /// this specific instance doesn't.
256 ///
257 /// Return `Err(`[`HandlerError`](super::HandlerError)`)` only when the
258 /// handler failed to determine an answer (e.g. its backing store is
259 /// unreachable); the agent then responds to the whole request with
260 /// `genErr` per RFC 3416 Section 4.2.1.
261 ///
262 /// See [`GetResult`] documentation for details on when to use each variant.
263 fn get<'a>(
264 &'a self,
265 ctx: &'a RequestContext,
266 oid: &'a Oid,
267 ) -> BoxFuture<'a, HandlerResult<GetResult>>;
268
269 /// Handle a GETNEXT request.
270 ///
271 /// Return `Ok(`[`GetNextResult::Value`]`)` with the lexicographically next
272 /// OID and value after `oid`, or `Ok(`[`GetNextResult::EndOfMibView`]`)`
273 /// if there are no more OIDs in this handler's subtree.
274 ///
275 /// Return `Err(`[`HandlerError`](super::HandlerError)`)` only when the
276 /// handler failed to determine an answer; the agent then responds to the
277 /// whole request (including GETBULK) with `genErr` per RFC 3416
278 /// Section 4.2.1.
279 fn get_next<'a>(
280 &'a self,
281 ctx: &'a RequestContext,
282 oid: &'a Oid,
283 ) -> BoxFuture<'a, HandlerResult<GetNextResult>>;
284
285 /// Test if a SET operation would succeed (phase 1 of two-phase commit).
286 ///
287 /// Called for ALL varbinds before any commits. Must NOT modify state.
288 /// Return `SetResult::Ok` if the SET would succeed, or an appropriate
289 /// error otherwise.
290 ///
291 /// Default implementation returns `NotWritable` (read-only handler).
292 fn test_set<'a>(
293 &'a self,
294 _ctx: &'a RequestContext,
295 _oid: &'a Oid,
296 _value: &'a Value,
297 ) -> BoxFuture<'a, SetResult> {
298 Box::pin(async { SetResult::NotWritable })
299 }
300
301 /// Commit a SET operation (phase 2 of two-phase commit).
302 ///
303 /// Only called after ALL `test_set` calls succeed. Should apply the change.
304 /// If this fails, `undo_set` will be called for all previously committed
305 /// varbinds in this request.
306 ///
307 /// Default implementation returns `NotWritable` (read-only handler).
308 fn commit_set<'a>(
309 &'a self,
310 _ctx: &'a RequestContext,
311 _oid: &'a Oid,
312 _value: &'a Value,
313 ) -> BoxFuture<'a, SetResult> {
314 Box::pin(async { SetResult::NotWritable })
315 }
316
317 /// Undo a committed SET operation (rollback on partial failure).
318 ///
319 /// Called if a later `commit_set` fails. Should restore the previous value.
320 /// This is best-effort: if undo fails, log a warning but continue.
321 ///
322 /// Default implementation does nothing (no rollback support).
323 fn undo_set<'a>(
324 &'a self,
325 _ctx: &'a RequestContext,
326 _oid: &'a Oid,
327 _value: &'a Value,
328 ) -> BoxFuture<'a, SetResult> {
329 Box::pin(async { SetResult::Ok })
330 }
331
332 /// Free resources allocated during `test_set` (cleanup on test failure).
333 ///
334 /// Called for varbinds whose `test_set` succeeded when a later varbind's
335 /// `test_set` fails. This allows handlers to release any resources
336 /// (locks, temporary allocations) acquired during the test phase.
337 ///
338 /// Called in reverse order, matching the `undo_set` convention.
339 ///
340 /// Default implementation does nothing.
341 fn free_set<'a>(
342 &'a self,
343 _ctx: &'a RequestContext,
344 _oid: &'a Oid,
345 _value: &'a Value,
346 ) -> BoxFuture<'a, ()> {
347 Box::pin(async {})
348 }
349
350 /// Check if this handler handles the given OID.
351 ///
352 /// Default implementation returns true if the OID starts with
353 /// the registered prefix (i.e., the OID is within this handler's subtree).
354 /// Override for more complex matching.
355 ///
356 /// This method is used to route GET and SET requests. GETNEXT and GETBULK
357 /// consult all handlers regardless of this method.
358 fn handles(&self, registered_prefix: &Oid, oid: &Oid) -> bool {
359 oid.starts_with(registered_prefix)
360 }
361}