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, RequestContext, SetResult};
10
11/// Type alias for boxed async return type (dyn-compatible).
12pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
13
14/// Handler for SNMP MIB operations.
15///
16/// Implement this trait to provide values for a subtree of OIDs.
17/// Register handlers with [`AgentBuilder::handler()`](crate::agent::AgentBuilder::handler)
18/// using a prefix OID.
19///
20/// # SET Operations
21///
22/// SET operations use a two-phase commit protocol for RFC 3416 atomicity:
23///
24/// 1. **Test phase**: `test_set` is called for ALL varbinds before any commits.
25///    If any test fails, no changes are made.
26///
27/// 2. **Commit phase**: `commit_set` is called for each varbind in order.
28///    If a commit fails, `undo_set` is called for all previously committed
29///    varbinds (in reverse order).
30///
31/// # Bounds
32///
33/// The `'static` bound is required because handlers are stored as
34/// `Arc<dyn MibHandler>` within the agent. This allows the agent to
35/// hold handlers for its entire lifetime without lifetime annotations.
36/// In practice, most handlers naturally satisfy this bound.
37///
38/// # Thread Safety
39///
40/// Handlers must be `Send + Sync` because the agent may process
41/// requests concurrently from multiple tasks.
42///
43/// # Example
44///
45/// ```rust
46/// use async_snmp::handler::{MibHandler, RequestContext, GetResult, GetNextResult, BoxFuture};
47/// use async_snmp::{Oid, Value, VarBind, oid};
48///
49/// struct MyHandler;
50///
51/// impl MibHandler for MyHandler {
52///     fn get<'a>(&'a self, ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
53///         Box::pin(async move {
54///             if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
55///                 return GetResult::Value(Value::Integer(42));
56///             }
57///             GetResult::NoSuchObject
58///         })
59///     }
60///
61///     fn get_next<'a>(&'a self, ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetNextResult> {
62///         Box::pin(async move {
63///             let my_oid = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
64///             if oid < &my_oid {
65///                 return GetNextResult::Value(VarBind::new(my_oid, Value::Integer(42)));
66///             }
67///             GetNextResult::EndOfMibView
68///         })
69///     }
70/// }
71/// ```
72pub trait MibHandler: Send + Sync + 'static {
73    /// Handle a GET request for a specific OID.
74    ///
75    /// Return [`GetResult::Value`] if the OID exists, [`GetResult::NoSuchObject`]
76    /// if the object type is not implemented, or [`GetResult::NoSuchInstance`]
77    /// if the object type exists but this specific instance doesn't.
78    ///
79    /// See [`GetResult`] documentation for details on when to use each variant.
80    fn get<'a>(&'a self, ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult>;
81
82    /// Handle a GETNEXT request.
83    ///
84    /// Return [`GetNextResult::Value`] with the lexicographically next OID and value
85    /// after `oid`, or [`GetNextResult::EndOfMibView`] if there are no more OIDs
86    /// in this handler's subtree.
87    fn get_next<'a>(
88        &'a self,
89        ctx: &'a RequestContext,
90        oid: &'a Oid,
91    ) -> BoxFuture<'a, GetNextResult>;
92
93    /// Test if a SET operation would succeed (phase 1 of two-phase commit).
94    ///
95    /// Called for ALL varbinds before any commits. Must NOT modify state.
96    /// Return `SetResult::Ok` if the SET would succeed, or an appropriate
97    /// error otherwise.
98    ///
99    /// Default implementation returns `NotWritable` (read-only handler).
100    fn test_set<'a>(
101        &'a self,
102        _ctx: &'a RequestContext,
103        _oid: &'a Oid,
104        _value: &'a Value,
105    ) -> BoxFuture<'a, SetResult> {
106        Box::pin(async { SetResult::NotWritable })
107    }
108
109    /// Commit a SET operation (phase 2 of two-phase commit).
110    ///
111    /// Only called after ALL `test_set` calls succeed. Should apply the change.
112    /// If this fails, `undo_set` will be called for all previously committed
113    /// varbinds in this request.
114    ///
115    /// Default implementation returns `NotWritable` (read-only handler).
116    fn commit_set<'a>(
117        &'a self,
118        _ctx: &'a RequestContext,
119        _oid: &'a Oid,
120        _value: &'a Value,
121    ) -> BoxFuture<'a, SetResult> {
122        Box::pin(async { SetResult::NotWritable })
123    }
124
125    /// Undo a committed SET operation (rollback on partial failure).
126    ///
127    /// Called if a later `commit_set` fails. Should restore the previous value.
128    /// This is best-effort: if undo fails, log a warning but continue.
129    ///
130    /// Default implementation does nothing (no rollback support).
131    fn undo_set<'a>(
132        &'a self,
133        _ctx: &'a RequestContext,
134        _oid: &'a Oid,
135        _value: &'a Value,
136    ) -> BoxFuture<'a, ()> {
137        Box::pin(async {})
138    }
139
140    /// Check if this handler handles the given OID prefix.
141    ///
142    /// Default implementation returns true if the OID starts with
143    /// the registered prefix or is lexicographically before it
144    /// (for GETNEXT support). Override for more complex matching.
145    fn handles(&self, registered_prefix: &Oid, oid: &Oid) -> bool {
146        oid.starts_with(registered_prefix) || oid < registered_prefix
147    }
148}