async_snmp/handler/results.rs
1//! Result types for MIB handler operations.
2
3use crate::error::ErrorStatus;
4use crate::value::Value;
5use crate::varbind::VarBind;
6
7/// Result of a SET operation phase.
8///
9/// This enum is used by the three-phase SET protocol:
10/// - `test_set`: Returns Ok if the SET would succeed
11/// - `commit_set`: Returns Ok if the change was applied
12/// - `undo_set`: Does not return SetResult (best-effort rollback)
13///
14/// The variants map to RFC 3416 error status codes.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum SetResult {
17 /// Operation succeeded.
18 Ok,
19 /// Access denied (security/authorization failure).
20 ///
21 /// Use this when the request lacks sufficient access rights to modify
22 /// the object, based on the security context (user, community, etc.).
23 /// Maps to RFC 3416 error status code 6 (noAccess).
24 NoAccess,
25 /// Object is inherently read-only (not writable by design).
26 ///
27 /// Use this when the object cannot be modified regardless of who
28 /// is making the request. Maps to RFC 3416 error status code 17 (notWritable).
29 NotWritable,
30 /// Value has wrong ASN.1 type for this OID.
31 WrongType,
32 /// Value has wrong length for this OID.
33 WrongLength,
34 /// Value encoding is incorrect.
35 WrongEncoding,
36 /// Value is not valid for this OID (semantic check failed).
37 WrongValue,
38 /// Cannot create new row (table doesn't support row creation).
39 NoCreation,
40 /// Value is inconsistent with other values in the same SET.
41 InconsistentValue,
42 /// Resource unavailable (memory, locks, etc.).
43 ResourceUnavailable,
44 /// Commit failed (internal error during apply).
45 CommitFailed,
46 /// Undo failed (internal error during rollback).
47 UndoFailed,
48 /// Row name is inconsistent with existing data.
49 InconsistentName,
50}
51
52impl SetResult {
53 /// Check if this result indicates success.
54 pub fn is_ok(&self) -> bool {
55 matches!(self, SetResult::Ok)
56 }
57
58 /// Convert to an ErrorStatus code.
59 pub fn to_error_status(&self) -> ErrorStatus {
60 match self {
61 SetResult::Ok => ErrorStatus::NoError,
62 SetResult::NoAccess => ErrorStatus::NoAccess,
63 SetResult::NotWritable => ErrorStatus::NotWritable,
64 SetResult::WrongType => ErrorStatus::WrongType,
65 SetResult::WrongLength => ErrorStatus::WrongLength,
66 SetResult::WrongEncoding => ErrorStatus::WrongEncoding,
67 SetResult::WrongValue => ErrorStatus::WrongValue,
68 SetResult::NoCreation => ErrorStatus::NoCreation,
69 SetResult::InconsistentValue => ErrorStatus::InconsistentValue,
70 SetResult::ResourceUnavailable => ErrorStatus::ResourceUnavailable,
71 SetResult::CommitFailed => ErrorStatus::CommitFailed,
72 SetResult::UndoFailed => ErrorStatus::UndoFailed,
73 SetResult::InconsistentName => ErrorStatus::InconsistentName,
74 }
75 }
76}
77
78/// Response to return from a handler.
79///
80/// This is typically built internally by the agent based on handler results.
81#[derive(Debug, Clone)]
82pub struct Response {
83 /// Variable bindings in the response
84 pub varbinds: Vec<VarBind>,
85 /// Error status (0 = no error)
86 pub error_status: ErrorStatus,
87 /// Error index (1-based index of problematic varbind, 0 if no error)
88 pub error_index: i32,
89}
90
91impl Response {
92 /// Create a successful response with the given varbinds.
93 pub fn success(varbinds: Vec<VarBind>) -> Self {
94 Self {
95 varbinds,
96 error_status: ErrorStatus::NoError,
97 error_index: 0,
98 }
99 }
100
101 /// Create an error response.
102 pub fn error(error_status: ErrorStatus, error_index: i32, varbinds: Vec<VarBind>) -> Self {
103 Self {
104 varbinds,
105 error_status,
106 error_index,
107 }
108 }
109}
110
111/// Result of a GET operation on a specific OID.
112///
113/// This enum distinguishes between the RFC 3416-mandated exception types:
114/// - `Value`: The OID exists and has the given value
115/// - `NoSuchObject`: The OID's object type is not supported (agent doesn't implement this MIB)
116/// - `NoSuchInstance`: The object type exists but this specific instance doesn't
117/// (e.g., table row doesn't exist)
118///
119/// For SNMPv1, both exception types result in a `noSuchName` error response.
120/// For SNMPv2c/v3, they result in the appropriate exception value in the response.
121///
122/// # Example
123///
124/// ```rust
125/// use async_snmp::handler::{MibHandler, RequestContext, GetResult, GetNextResult, BoxFuture};
126/// use async_snmp::{Oid, Value, VarBind, oid};
127///
128/// struct IfTableHandler {
129/// // Simulates interface table with indices 1 and 2
130/// interfaces: Vec<u32>,
131/// }
132///
133/// impl MibHandler for IfTableHandler {
134/// fn get<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
135/// Box::pin(async move {
136/// let if_descr_prefix = oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 2);
137///
138/// if oid.starts_with(&if_descr_prefix) {
139/// // Extract index from OID
140/// if let Some(&index) = oid.arcs().get(if_descr_prefix.len()) {
141/// if self.interfaces.contains(&index) {
142/// return GetResult::Value(Value::OctetString(
143/// format!("eth{}", index - 1).into()
144/// ));
145/// }
146/// // Index exists in MIB but not in our table
147/// return GetResult::NoSuchInstance;
148/// }
149/// }
150/// // OID is not in our MIB at all
151/// GetResult::NoSuchObject
152/// })
153/// }
154///
155/// fn get_next<'a>(&'a self, _ctx: &'a RequestContext, _oid: &'a Oid) -> BoxFuture<'a, GetNextResult> {
156/// Box::pin(async move { GetNextResult::EndOfMibView }) // Simplified
157/// }
158/// }
159/// ```
160#[derive(Debug, Clone, PartialEq)]
161pub enum GetResult {
162 /// The OID exists and has this value.
163 Value(Value),
164 /// The object type is not implemented by this agent.
165 ///
166 /// Use this when the OID prefix (object type) is not recognized.
167 NoSuchObject,
168 /// The object type exists but this specific instance doesn't.
169 ///
170 /// Use this when the OID prefix is valid but the instance identifier
171 /// (e.g., table index) doesn't exist.
172 NoSuchInstance,
173}
174
175impl GetResult {
176 /// Create a `GetResult` from an `Option<Value>`.
177 ///
178 /// This is a convenience method for migrating from the previous
179 /// `Option<Value>` interface. `None` is treated as `NoSuchObject`.
180 pub fn from_option(value: Option<Value>) -> Self {
181 match value {
182 Some(v) => GetResult::Value(v),
183 None => GetResult::NoSuchObject,
184 }
185 }
186}
187
188impl From<Value> for GetResult {
189 fn from(value: Value) -> Self {
190 GetResult::Value(value)
191 }
192}
193
194impl From<Option<Value>> for GetResult {
195 fn from(value: Option<Value>) -> Self {
196 GetResult::from_option(value)
197 }
198}
199
200/// Result of a GETNEXT operation.
201///
202/// This enum provides symmetry with [`GetResult`] for the GETNEXT operation:
203/// - `Value`: Returns the next OID/value pair in the MIB tree
204/// - `EndOfMibView`: No more OIDs after the given one in this handler's subtree
205///
206/// For SNMPv1, `EndOfMibView` results in a `noSuchName` error response.
207/// For SNMPv2c/v3, it results in the `endOfMibView` exception value.
208#[derive(Debug, Clone, PartialEq)]
209pub enum GetNextResult {
210 /// The next OID/value pair in the MIB tree.
211 Value(VarBind),
212 /// No more OIDs after the given one (end of MIB view).
213 EndOfMibView,
214}
215
216impl GetNextResult {
217 /// Create a `GetNextResult` from an `Option<VarBind>`.
218 ///
219 /// This is a convenience method for migrating from the previous
220 /// `Option<VarBind>` interface. `None` is treated as `EndOfMibView`.
221 pub fn from_option(value: Option<VarBind>) -> Self {
222 match value {
223 Some(vb) => GetNextResult::Value(vb),
224 None => GetNextResult::EndOfMibView,
225 }
226 }
227
228 /// Returns `true` if this is a value result.
229 pub fn is_value(&self) -> bool {
230 matches!(self, GetNextResult::Value(_))
231 }
232
233 /// Returns `true` if this is end of MIB view.
234 pub fn is_end_of_mib_view(&self) -> bool {
235 matches!(self, GetNextResult::EndOfMibView)
236 }
237
238 /// Converts to an `Option<VarBind>`.
239 pub fn into_option(self) -> Option<VarBind> {
240 match self {
241 GetNextResult::Value(vb) => Some(vb),
242 GetNextResult::EndOfMibView => None,
243 }
244 }
245}
246
247impl From<VarBind> for GetNextResult {
248 fn from(vb: VarBind) -> Self {
249 GetNextResult::Value(vb)
250 }
251}
252
253impl From<Option<VarBind>> for GetNextResult {
254 fn from(value: Option<VarBind>) -> Self {
255 GetNextResult::from_option(value)
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262 use crate::oid;
263
264 #[test]
265 fn test_response_success() {
266 let response = Response::success(vec![VarBind::new(oid!(1, 3, 6, 1), Value::Integer(1))]);
267 assert_eq!(response.error_status, ErrorStatus::NoError);
268 assert_eq!(response.error_index, 0);
269 assert_eq!(response.varbinds.len(), 1);
270 }
271
272 #[test]
273 fn test_response_error() {
274 let response = Response::error(
275 ErrorStatus::NoSuchName,
276 1,
277 vec![VarBind::new(oid!(1, 3, 6, 1), Value::Null)],
278 );
279 assert_eq!(response.error_status, ErrorStatus::NoSuchName);
280 assert_eq!(response.error_index, 1);
281 }
282
283 #[test]
284 fn test_get_result_from_option() {
285 let result = GetResult::from_option(Some(Value::Integer(42)));
286 assert!(matches!(result, GetResult::Value(Value::Integer(42))));
287
288 let result = GetResult::from_option(None);
289 assert!(matches!(result, GetResult::NoSuchObject));
290 }
291
292 #[test]
293 fn test_get_result_from_value() {
294 let result: GetResult = Value::Integer(42).into();
295 assert!(matches!(result, GetResult::Value(Value::Integer(42))));
296 }
297
298 #[test]
299 fn test_get_next_result_from_option() {
300 let vb = VarBind::new(oid!(1, 3, 6, 1), Value::Integer(42));
301 let result = GetNextResult::from_option(Some(vb.clone()));
302 assert!(result.is_value());
303 assert_eq!(result.into_option(), Some(vb));
304
305 let result = GetNextResult::from_option(None);
306 assert!(result.is_end_of_mib_view());
307 assert_eq!(result.into_option(), None);
308 }
309
310 #[test]
311 fn test_get_next_result_from_varbind() {
312 let vb = VarBind::new(oid!(1, 3, 6, 1), Value::Integer(42));
313 let result: GetNextResult = vb.clone().into();
314 assert!(result.is_value());
315 if let GetNextResult::Value(inner) = result {
316 assert_eq!(inner.oid, oid!(1, 3, 6, 1));
317 }
318 }
319
320 #[test]
321 fn test_set_result_to_error_status() {
322 assert_eq!(SetResult::Ok.to_error_status(), ErrorStatus::NoError);
323 assert_eq!(SetResult::NoAccess.to_error_status(), ErrorStatus::NoAccess);
324 assert_eq!(
325 SetResult::NotWritable.to_error_status(),
326 ErrorStatus::NotWritable
327 );
328 assert_eq!(
329 SetResult::WrongType.to_error_status(),
330 ErrorStatus::WrongType
331 );
332 assert_eq!(
333 SetResult::CommitFailed.to_error_status(),
334 ErrorStatus::CommitFailed
335 );
336 }
337
338 #[test]
339 fn test_set_result_is_ok() {
340 assert!(SetResult::Ok.is_ok());
341 assert!(!SetResult::NoAccess.is_ok());
342 assert!(!SetResult::NotWritable.is_ok());
343 }
344}