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}