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).
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};
21///
22/// fn example_async_fn<'a>(value: &'a i32) -> BoxFuture<'a, GetResult> {
23/// Box::pin(async move {
24/// // Async work here
25/// 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/// - [`GetResult::Value`] if the OID exists and has a value
54/// - [`GetResult::NoSuchObject`] if the object type is not implemented
55/// - [`GetResult::NoSuchInstance`] if the object exists but this instance doesn't
56///
57/// # GETNEXT and Lexicographic Ordering
58///
59/// The [`get_next`](MibHandler::get_next) method must return the lexicographically
60/// next OID after the requested one. OIDs are compared arc-by-arc as unsigned integers.
61/// For example: `1.3.6.1.2` < `1.3.6.1.2.1` < `1.3.6.1.3`.
62///
63/// Key considerations:
64/// - The returned OID must be strictly greater than the input OID
65/// - GETBULK uses GETNEXT repeatedly, so efficient implementation matters
66/// - Use [`OidTable`](super::OidTable) to simplify sorted OID management
67///
68/// # SET Two-Phase Commit (RFC 3416)
69///
70/// SET operations use a multi-phase protocol modeled after net-snmp's
71/// RESERVE1/RESERVE2/ACTION/COMMIT/FREE/UNDO phases:
72///
73/// 1. **Test phase**: [`test_set`](MibHandler::test_set) is called for ALL varbinds
74/// before any commits. If any test fails, [`free_set`](MibHandler::free_set)
75/// is called for all previously successful varbinds (in reverse order) to
76/// release resources allocated during the test phase.
77///
78/// 2. **Commit phase**: [`commit_set`](MibHandler::commit_set) is called for each
79/// varbind in order. If a commit fails, [`undo_set`](MibHandler::undo_set) is
80/// called for all previously committed varbinds (in reverse order).
81///
82/// By default, handlers are read-only and return [`SetResult::NotWritable`].
83///
84/// # Bounds
85///
86/// The `'static` bound is required because handlers are stored as
87/// `Arc<dyn MibHandler>` within the agent. This allows the agent to
88/// hold handlers for its entire lifetime without lifetime annotations.
89/// In practice, most handlers naturally satisfy this bound.
90///
91/// # Thread Safety
92///
93/// Handlers must be `Send + Sync` because the agent may process
94/// requests concurrently from multiple tasks.
95///
96/// # Example: Read-Only Handler
97///
98/// ```rust
99/// use async_snmp::handler::{MibHandler, RequestContext, GetResult, GetNextResult, BoxFuture};
100/// use async_snmp::{Oid, Value, VarBind, oid};
101///
102/// struct SystemInfoHandler {
103/// sys_descr: String,
104/// sys_uptime: u32,
105/// }
106///
107/// impl MibHandler for SystemInfoHandler {
108/// fn get<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
109/// Box::pin(async move {
110/// // sysDescr.0
111/// if oid == &oid!(1, 3, 6, 1, 2, 1, 1, 1, 0) {
112/// return GetResult::Value(Value::OctetString(self.sys_descr.clone().into()));
113/// }
114/// // sysUpTime.0
115/// if oid == &oid!(1, 3, 6, 1, 2, 1, 1, 3, 0) {
116/// return GetResult::Value(Value::TimeTicks(self.sys_uptime));
117/// }
118/// GetResult::NoSuchObject
119/// })
120/// }
121///
122/// fn get_next<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetNextResult> {
123/// Box::pin(async move {
124/// let sys_descr = oid!(1, 3, 6, 1, 2, 1, 1, 1, 0);
125/// let sys_uptime = oid!(1, 3, 6, 1, 2, 1, 1, 3, 0);
126///
127/// // Return the next OID in lexicographic order
128/// if oid < &sys_descr {
129/// return GetNextResult::Value(VarBind::new(
130/// sys_descr,
131/// Value::OctetString("My System".into())
132/// ));
133/// }
134/// if oid < &sys_uptime {
135/// return GetNextResult::Value(VarBind::new(
136/// sys_uptime,
137/// Value::TimeTicks(12345)
138/// ));
139/// }
140/// GetNextResult::EndOfMibView
141/// })
142/// }
143/// }
144/// ```
145///
146/// # Example: Writable Handler
147///
148/// ```rust
149/// use async_snmp::handler::{
150/// MibHandler, RequestContext, GetResult, GetNextResult, SetResult, BoxFuture
151/// };
152/// use async_snmp::{Oid, Value, VarBind, oid};
153/// use std::sync::atomic::{AtomicI32, Ordering};
154///
155/// struct WritableHandler {
156/// counter: AtomicI32,
157/// }
158///
159/// impl MibHandler for WritableHandler {
160/// fn get<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
161/// Box::pin(async move {
162/// if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
163/// return GetResult::Value(Value::Integer(
164/// self.counter.load(Ordering::Relaxed)
165/// ));
166/// }
167/// GetResult::NoSuchObject
168/// })
169/// }
170///
171/// fn get_next<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetNextResult> {
172/// Box::pin(async move {
173/// let my_oid = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
174/// if oid < &my_oid {
175/// return GetNextResult::Value(VarBind::new(
176/// my_oid,
177/// Value::Integer(self.counter.load(Ordering::Relaxed))
178/// ));
179/// }
180/// GetNextResult::EndOfMibView
181/// })
182/// }
183///
184/// fn test_set<'a>(
185/// &'a self,
186/// _ctx: &'a RequestContext,
187/// oid: &'a Oid,
188/// value: &'a Value,
189/// ) -> BoxFuture<'a, SetResult> {
190/// Box::pin(async move {
191/// if oid != &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
192/// return SetResult::NotWritable;
193/// }
194/// // Validate the value type
195/// match value {
196/// Value::Integer(_) => SetResult::Ok,
197/// _ => SetResult::WrongType,
198/// }
199/// })
200/// }
201///
202/// fn commit_set<'a>(
203/// &'a self,
204/// _ctx: &'a RequestContext,
205/// _oid: &'a Oid,
206/// value: &'a Value,
207/// ) -> BoxFuture<'a, SetResult> {
208/// Box::pin(async move {
209/// if let Value::Integer(v) = value {
210/// self.counter.store(*v, Ordering::Relaxed);
211/// SetResult::Ok
212/// } else {
213/// SetResult::CommitFailed
214/// }
215/// })
216/// }
217/// }
218/// ```
219pub trait MibHandler: Send + Sync + 'static {
220 /// Handle a GET request for a specific OID.
221 ///
222 /// Return [`GetResult::Value`] if the OID exists, [`GetResult::NoSuchObject`]
223 /// if the object type is not implemented, or [`GetResult::NoSuchInstance`]
224 /// if the object type exists but this specific instance doesn't.
225 ///
226 /// See [`GetResult`] documentation for details on when to use each variant.
227 fn get<'a>(&'a self, ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult>;
228
229 /// Handle a GETNEXT request.
230 ///
231 /// Return [`GetNextResult::Value`] with the lexicographically next OID and value
232 /// after `oid`, or [`GetNextResult::EndOfMibView`] if there are no more OIDs
233 /// in this handler's subtree.
234 fn get_next<'a>(
235 &'a self,
236 ctx: &'a RequestContext,
237 oid: &'a Oid,
238 ) -> BoxFuture<'a, GetNextResult>;
239
240 /// Test if a SET operation would succeed (phase 1 of two-phase commit).
241 ///
242 /// Called for ALL varbinds before any commits. Must NOT modify state.
243 /// Return `SetResult::Ok` if the SET would succeed, or an appropriate
244 /// error otherwise.
245 ///
246 /// Default implementation returns `NotWritable` (read-only handler).
247 fn test_set<'a>(
248 &'a self,
249 _ctx: &'a RequestContext,
250 _oid: &'a Oid,
251 _value: &'a Value,
252 ) -> BoxFuture<'a, SetResult> {
253 Box::pin(async { SetResult::NotWritable })
254 }
255
256 /// Commit a SET operation (phase 2 of two-phase commit).
257 ///
258 /// Only called after ALL `test_set` calls succeed. Should apply the change.
259 /// If this fails, `undo_set` will be called for all previously committed
260 /// varbinds in this request.
261 ///
262 /// Default implementation returns `NotWritable` (read-only handler).
263 fn commit_set<'a>(
264 &'a self,
265 _ctx: &'a RequestContext,
266 _oid: &'a Oid,
267 _value: &'a Value,
268 ) -> BoxFuture<'a, SetResult> {
269 Box::pin(async { SetResult::NotWritable })
270 }
271
272 /// Undo a committed SET operation (rollback on partial failure).
273 ///
274 /// Called if a later `commit_set` fails. Should restore the previous value.
275 /// This is best-effort: if undo fails, log a warning but continue.
276 ///
277 /// Default implementation does nothing (no rollback support).
278 fn undo_set<'a>(
279 &'a self,
280 _ctx: &'a RequestContext,
281 _oid: &'a Oid,
282 _value: &'a Value,
283 ) -> BoxFuture<'a, SetResult> {
284 Box::pin(async { SetResult::Ok })
285 }
286
287 /// Free resources allocated during test_set (cleanup on test failure).
288 ///
289 /// Called for varbinds whose test_set succeeded when a later varbind's
290 /// test_set fails. This allows handlers to release any resources
291 /// (locks, temporary allocations) acquired during the test phase.
292 ///
293 /// Called in reverse order, matching the undo_set convention.
294 ///
295 /// Default implementation does nothing.
296 fn free_set<'a>(
297 &'a self,
298 _ctx: &'a RequestContext,
299 _oid: &'a Oid,
300 _value: &'a Value,
301 ) -> BoxFuture<'a, ()> {
302 Box::pin(async {})
303 }
304
305 /// Check if this handler handles the given OID.
306 ///
307 /// Default implementation returns true if the OID starts with
308 /// the registered prefix (i.e., the OID is within this handler's subtree).
309 /// Override for more complex matching.
310 ///
311 /// This method is used to route GET and SET requests. GETNEXT and GETBULK
312 /// consult all handlers regardless of this method.
313 fn handles(&self, registered_prefix: &Oid, oid: &Oid) -> bool {
314 oid.starts_with(registered_prefix)
315 }
316}