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}