async_snmp/
error.rs

1//! Error types for async-snmp.
2//!
3//! This module provides comprehensive error handling for SNMP operations, including:
4//!
5//! - [`Error`] - The main error type for all library operations
6//! - [`ErrorStatus`] - SNMP protocol errors returned by agents (RFC 3416)
7//! - Helper types for authentication, encryption, and encoding errors
8//!
9//! All errors are `#[non_exhaustive]` to allow adding new variants without breaking changes.
10//!
11//! # Error Handling Patterns
12//!
13//! ## Basic Error Matching
14//!
15//! Most applications should match on specific error variants to provide appropriate responses:
16//!
17//! ```no_run
18//! use async_snmp::{Auth, Client, Error, ErrorStatus, oid};
19//!
20//! # async fn example() -> async_snmp::Result<()> {
21//! let client = Client::builder("192.168.1.1:161", Auth::v2c("public"))
22//!     .connect()
23//!     .await?;
24//!
25//! match client.get(&oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)).await {
26//!     Ok(varbind) => {
27//!         println!("Value: {:?}", varbind.value);
28//!     }
29//!     Err(Error::Timeout { elapsed, retries, .. }) => {
30//!         println!("Request timed out after {:?} ({} retries)", elapsed, retries);
31//!     }
32//!     Err(Error::Snmp { status, index, .. }) => {
33//!         println!("SNMP error: {} at index {}", status, index);
34//!     }
35//!     Err(e) => {
36//!         println!("Other error: {}", e);
37//!     }
38//! }
39//! # Ok(())
40//! # }
41//! ```
42//!
43//! ## SNMP Protocol Errors
44//!
45//! [`ErrorStatus`] represents errors returned by SNMP agents. Common cases include:
46//!
47//! ```no_run
48//! use async_snmp::{Auth, Client, Error, ErrorStatus, Value, oid};
49//!
50//! # async fn example() -> async_snmp::Result<()> {
51//! let client = Client::builder("192.168.1.1:161", Auth::v2c("private"))
52//!     .connect()
53//!     .await?;
54//!
55//! let result = client.set(&oid!(1, 3, 6, 1, 2, 1, 1, 4, 0), Value::from("admin@example.com")).await;
56//!
57//! if let Err(Error::Snmp { status, oid, .. }) = result {
58//!     match status {
59//!         ErrorStatus::NoSuchName => {
60//!             println!("OID does not exist");
61//!         }
62//!         ErrorStatus::NotWritable => {
63//!             println!("Object is read-only");
64//!         }
65//!         ErrorStatus::AuthorizationError => {
66//!             println!("Access denied - check community string");
67//!         }
68//!         ErrorStatus::WrongType | ErrorStatus::WrongValue => {
69//!             println!("Invalid value for this OID");
70//!         }
71//!         _ => {
72//!             println!("SNMP error: {}", status);
73//!         }
74//!     }
75//!     if let Some(oid) = oid {
76//!         println!("Problematic OID: {}", oid);
77//!     }
78//! }
79//! # Ok(())
80//! # }
81//! ```
82//!
83//! ## Timeout Handling
84//!
85//! Timeouts include retry information to help diagnose connectivity issues:
86//!
87//! ```no_run
88//! use async_snmp::{Auth, Client, Error, Retry, oid};
89//! use std::time::Duration;
90//!
91//! # async fn example() {
92//! let client = Client::builder("192.168.1.1:161", Auth::v2c("public"))
93//!     .timeout(Duration::from_secs(2))
94//!     .retry(Retry::fixed(3, Duration::ZERO))
95//!     .connect()
96//!     .await
97//!     .expect("failed to create client");
98//!
99//! match client.get(&oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)).await {
100//!     Err(Error::Timeout { target, elapsed, request_id, retries }) => {
101//!         if let Some(addr) = target {
102//!             println!("No response from {} after {:?}", addr, elapsed);
103//!         }
104//!         println!("Request ID {} failed after {} retries", request_id, retries);
105//!         // Consider: is the host reachable? Is SNMP enabled? Is the port correct?
106//!     }
107//!     _ => {}
108//! }
109//! # }
110//! ```
111//!
112//! ## SNMPv3 Errors
113//!
114//! SNMPv3 operations can fail with authentication or encryption errors:
115//!
116//! ```no_run
117//! use async_snmp::{Auth, AuthProtocol, Client, Error, AuthErrorKind, oid};
118//!
119//! # async fn example() {
120//! let client = Client::builder(
121//!     "192.168.1.1:161",
122//!     Auth::usm("admin").auth(AuthProtocol::Sha256, "wrongpassword"),
123//! )
124//! .connect()
125//! .await
126//! .expect("failed to create client");
127//!
128//! match client.get(&oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)).await {
129//!     Err(Error::AuthenticationFailed { kind, .. }) => {
130//!         match kind {
131//!             AuthErrorKind::HmacMismatch => {
132//!                 println!("Wrong password or credentials");
133//!             }
134//!             AuthErrorKind::NoUser => {
135//!                 println!("User not configured on agent");
136//!             }
137//!             _ => {
138//!                 println!("Auth failed: {}", kind);
139//!             }
140//!         }
141//!     }
142//!     Err(Error::NotInTimeWindow { .. }) => {
143//!         println!("Clock skew between client and agent");
144//!     }
145//!     Err(Error::UnknownEngineId { .. }) => {
146//!         println!("Engine discovery failed");
147//!     }
148//!     _ => {}
149//! }
150//! # }
151//! ```
152
153use std::net::SocketAddr;
154use std::time::Duration;
155
156/// Result type alias using the library's Error type.
157pub type Result<T> = std::result::Result<T, Error>;
158
159/// Authentication error kinds (SNMPv3).
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum AuthErrorKind {
162    /// No credentials configured for this operation.
163    NoCredentials,
164    /// No authentication key available.
165    NoAuthKey,
166    /// User not found in USM table.
167    NoUser,
168    /// HMAC verification failed.
169    HmacMismatch,
170    /// Authentication parameters wrong length.
171    WrongMacLength { expected: usize, actual: usize },
172    /// Could not locate auth params in message.
173    AuthParamsNotFound,
174}
175
176impl std::fmt::Display for AuthErrorKind {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        match self {
179            Self::NoCredentials => write!(f, "no credentials configured"),
180            Self::NoAuthKey => write!(f, "no authentication key available"),
181            Self::NoUser => write!(f, "user not found"),
182            Self::HmacMismatch => write!(f, "HMAC verification failed"),
183            Self::WrongMacLength { expected, actual } => {
184                write!(f, "wrong MAC length: expected {}, got {}", expected, actual)
185            }
186            Self::AuthParamsNotFound => write!(f, "could not locate auth params in message"),
187        }
188    }
189}
190
191/// Cryptographic error kinds (encryption/decryption).
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193pub enum CryptoErrorKind {
194    /// No privacy key available.
195    NoPrivKey,
196    /// Invalid padding in decrypted data.
197    InvalidPadding,
198    /// Invalid key length for cipher.
199    InvalidKeyLength,
200    /// Invalid IV length for cipher.
201    InvalidIvLength,
202    /// Cipher operation failed.
203    CipherError,
204    /// Unsupported privacy protocol.
205    UnsupportedProtocol,
206    /// Invalid priv params length.
207    InvalidPrivParamsLength { expected: usize, actual: usize },
208    /// Ciphertext length not a multiple of block size.
209    InvalidCiphertextLength { length: usize, block_size: usize },
210}
211
212impl std::fmt::Display for CryptoErrorKind {
213    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214        match self {
215            Self::NoPrivKey => write!(f, "no privacy key available"),
216            Self::InvalidPadding => write!(f, "invalid padding"),
217            Self::InvalidKeyLength => write!(f, "invalid key length"),
218            Self::InvalidIvLength => write!(f, "invalid IV length"),
219            Self::CipherError => write!(f, "cipher operation failed"),
220            Self::UnsupportedProtocol => write!(f, "unsupported privacy protocol"),
221            Self::InvalidPrivParamsLength { expected, actual } => {
222                write!(
223                    f,
224                    "invalid privParameters length: expected {}, got {}",
225                    expected, actual
226                )
227            }
228            Self::InvalidCiphertextLength { length, block_size } => {
229                write!(
230                    f,
231                    "ciphertext length {} not multiple of block size {}",
232                    length, block_size
233                )
234            }
235        }
236    }
237}
238
239/// BER decode error kinds.
240#[derive(Debug, Clone, Copy, PartialEq, Eq)]
241pub enum DecodeErrorKind {
242    /// Expected different tag.
243    UnexpectedTag { expected: u8, actual: u8 },
244    /// Data truncated unexpectedly.
245    TruncatedData,
246    /// Invalid BER length encoding.
247    InvalidLength,
248    /// Indefinite length not supported.
249    IndefiniteLength,
250    /// Integer value overflow.
251    IntegerOverflow,
252    /// Zero-length integer.
253    ZeroLengthInteger,
254    /// Invalid OID encoding.
255    InvalidOidEncoding,
256    /// Unknown SNMP version.
257    UnknownVersion(i32),
258    /// Unknown PDU type.
259    UnknownPduType(u8),
260    /// Constructed OCTET STRING not supported.
261    ConstructedOctetString,
262    /// Missing required PDU.
263    MissingPdu,
264    /// Invalid msgFlags (priv without auth).
265    InvalidMsgFlags,
266    /// Unknown security model.
267    UnknownSecurityModel(i32),
268    /// msgMaxSize below RFC 3412 minimum (484 octets).
269    MsgMaxSizeTooSmall { value: i32, minimum: i32 },
270    /// NULL with non-zero length.
271    InvalidNull,
272    /// Expected plaintext, got encrypted.
273    UnexpectedEncryption,
274    /// Expected encrypted, got plaintext.
275    ExpectedEncryption,
276    /// Invalid IP address length.
277    InvalidIpAddressLength { length: usize },
278    /// Length field too long.
279    LengthTooLong { octets: usize },
280    /// Length exceeds maximum.
281    LengthExceedsMax { length: usize, max: usize },
282    /// Integer64 too long.
283    Integer64TooLong { length: usize },
284    /// Empty response.
285    EmptyResponse,
286    /// TLV extends past end of data.
287    TlvOverflow,
288    /// Insufficient data for read.
289    InsufficientData { needed: usize, available: usize },
290    /// Invalid OID in notification varbinds.
291    InvalidOid,
292}
293
294impl std::fmt::Display for DecodeErrorKind {
295    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296        match self {
297            Self::UnexpectedTag { expected, actual } => {
298                write!(f, "expected tag 0x{:02X}, got 0x{:02X}", expected, actual)
299            }
300            Self::TruncatedData => write!(f, "unexpected end of data"),
301            Self::InvalidLength => write!(f, "invalid length encoding"),
302            Self::IndefiniteLength => write!(f, "indefinite length encoding not supported"),
303            Self::IntegerOverflow => write!(f, "integer overflow"),
304            Self::ZeroLengthInteger => write!(f, "zero-length integer"),
305            Self::InvalidOidEncoding => write!(f, "invalid OID encoding"),
306            Self::UnknownVersion(v) => write!(f, "unknown SNMP version: {}", v),
307            Self::UnknownPduType(t) => write!(f, "unknown PDU type: 0x{:02X}", t),
308            Self::ConstructedOctetString => {
309                write!(f, "constructed OCTET STRING (0x24) not supported")
310            }
311            Self::MissingPdu => write!(f, "missing PDU in message"),
312            Self::InvalidMsgFlags => write!(f, "invalid msgFlags: privacy without authentication"),
313            Self::UnknownSecurityModel(m) => write!(f, "unknown security model: {}", m),
314            Self::MsgMaxSizeTooSmall { value, minimum } => {
315                write!(f, "msgMaxSize {} below RFC 3412 minimum {}", value, minimum)
316            }
317            Self::InvalidNull => write!(f, "NULL with non-zero length"),
318            Self::UnexpectedEncryption => write!(f, "expected plaintext scoped PDU"),
319            Self::ExpectedEncryption => write!(f, "expected encrypted scoped PDU"),
320            Self::InvalidIpAddressLength { length } => {
321                write!(f, "IP address must be 4 bytes, got {}", length)
322            }
323            Self::LengthTooLong { octets } => {
324                write!(f, "length encoding too long ({} octets)", octets)
325            }
326            Self::LengthExceedsMax { length, max } => {
327                write!(f, "length {} exceeds maximum {}", length, max)
328            }
329            Self::Integer64TooLong { length } => {
330                write!(f, "integer64 too long: {} bytes", length)
331            }
332            Self::EmptyResponse => write!(f, "empty response"),
333            Self::TlvOverflow => write!(f, "TLV extends past end of data"),
334            Self::InsufficientData { needed, available } => {
335                write!(f, "need {} bytes but only {} remaining", needed, available)
336            }
337            Self::InvalidOid => write!(f, "invalid OID in notification varbinds"),
338        }
339    }
340}
341
342/// BER encode error kinds.
343#[derive(Debug, Clone, Copy, PartialEq, Eq)]
344pub enum EncodeErrorKind {
345    /// V3 security not configured.
346    NoSecurityConfig,
347    /// Engine not discovered.
348    EngineNotDiscovered,
349    /// Keys not derived.
350    KeysNotDerived,
351    /// Auth key not available for encoding.
352    MissingAuthKey,
353    /// Privacy key not available.
354    NoPrivKey,
355    /// Could not locate auth params position in encoded message.
356    MissingAuthParams,
357}
358
359impl std::fmt::Display for EncodeErrorKind {
360    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361        match self {
362            Self::NoSecurityConfig => write!(f, "V3 security config not set"),
363            Self::EngineNotDiscovered => write!(f, "engine not discovered"),
364            Self::KeysNotDerived => write!(f, "keys not derived"),
365            Self::MissingAuthKey => write!(f, "auth key not available for encoding"),
366            Self::NoPrivKey => write!(f, "privacy key not available"),
367            Self::MissingAuthParams => {
368                write!(f, "could not find auth params position in encoded message")
369            }
370        }
371    }
372}
373
374/// OID validation error kinds.
375#[derive(Debug, Clone, Copy, PartialEq, Eq)]
376pub enum OidErrorKind {
377    /// Empty OID string.
378    Empty,
379    /// Invalid arc value.
380    InvalidArc,
381    /// First arc must be 0, 1, or 2.
382    InvalidFirstArc(u32),
383    /// Second arc too large for first arc value.
384    InvalidSecondArc { first: u32, second: u32 },
385    /// OID too short (minimum 2 arcs).
386    TooShort,
387    /// OID has too many arcs (exceeds MAX_OID_LEN).
388    TooManyArcs { count: usize, max: usize },
389    /// Subidentifier overflow during encoding.
390    SubidentifierOverflow,
391}
392
393impl std::fmt::Display for OidErrorKind {
394    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395        match self {
396            Self::Empty => write!(f, "empty OID"),
397            Self::InvalidArc => write!(f, "invalid arc value"),
398            Self::InvalidFirstArc(v) => write!(f, "first arc must be 0, 1, or 2, got {}", v),
399            Self::InvalidSecondArc { first, second } => {
400                write!(f, "second arc {} too large for first arc {}", second, first)
401            }
402            Self::TooShort => write!(f, "OID must have at least 2 arcs"),
403            Self::TooManyArcs { count, max } => {
404                write!(f, "OID has {} arcs, exceeds maximum {}", count, max)
405            }
406            Self::SubidentifierOverflow => write!(f, "subidentifier overflow"),
407        }
408    }
409}
410
411/// SNMP protocol error status codes (RFC 3416).
412///
413/// These codes are returned by SNMP agents to indicate the result of an operation.
414/// The error status is included in the [`Error::Snmp`] variant along with an error
415/// index indicating which varbind caused the error.
416///
417/// # Error Categories
418///
419/// ## SNMPv1 Errors (0-5)
420///
421/// - `NoError` - Operation succeeded
422/// - `TooBig` - Response too large for transport
423/// - `NoSuchName` - OID not found (v1 only; v2c+ uses exceptions)
424/// - `BadValue` - Invalid value in SET
425/// - `ReadOnly` - Attempted write to read-only object
426/// - `GenErr` - Unspecified error
427///
428/// ## SNMPv2c/v3 Errors (6-18)
429///
430/// These provide more specific error information for SET operations:
431///
432/// - `NoAccess` - Object not accessible (access control)
433/// - `WrongType` - Value has wrong ASN.1 type
434/// - `WrongLength` - Value has wrong length
435/// - `WrongValue` - Value out of range or invalid
436/// - `NotWritable` - Object does not support SET
437/// - `AuthorizationError` - Access denied by VACM
438///
439/// # Example
440///
441/// ```
442/// use async_snmp::ErrorStatus;
443///
444/// let status = ErrorStatus::from_i32(2);
445/// assert_eq!(status, ErrorStatus::NoSuchName);
446/// assert_eq!(status.as_i32(), 2);
447/// println!("Error: {}", status); // prints "noSuchName"
448/// ```
449#[derive(Debug, Clone, Copy, PartialEq, Eq)]
450#[non_exhaustive]
451pub enum ErrorStatus {
452    /// Operation completed successfully (status = 0).
453    NoError,
454    /// Response message would be too large for transport (status = 1).
455    TooBig,
456    /// Requested OID not found (status = 2). SNMPv1 only; v2c+ uses exception values.
457    NoSuchName,
458    /// Invalid value provided in SET request (status = 3).
459    BadValue,
460    /// Attempted to SET a read-only object (status = 4).
461    ReadOnly,
462    /// Unspecified error occurred (status = 5).
463    GenErr,
464    /// Object exists but access is denied (status = 6).
465    NoAccess,
466    /// SET value has wrong ASN.1 type (status = 7).
467    WrongType,
468    /// SET value has incorrect length (status = 8).
469    WrongLength,
470    /// SET value uses wrong encoding (status = 9).
471    WrongEncoding,
472    /// SET value is out of range or otherwise invalid (status = 10).
473    WrongValue,
474    /// Object does not support row creation (status = 11).
475    NoCreation,
476    /// Value is inconsistent with other managed objects (status = 12).
477    InconsistentValue,
478    /// Resource required for SET is unavailable (status = 13).
479    ResourceUnavailable,
480    /// SET commit phase failed (status = 14).
481    CommitFailed,
482    /// SET undo phase failed (status = 15).
483    UndoFailed,
484    /// Access denied by VACM (status = 16).
485    AuthorizationError,
486    /// Object does not support modification (status = 17).
487    NotWritable,
488    /// Named object cannot be created (status = 18).
489    InconsistentName,
490    /// Unknown or future error status code.
491    Unknown(i32),
492}
493
494impl ErrorStatus {
495    /// Create from raw status code.
496    pub fn from_i32(value: i32) -> Self {
497        match value {
498            0 => Self::NoError,
499            1 => Self::TooBig,
500            2 => Self::NoSuchName,
501            3 => Self::BadValue,
502            4 => Self::ReadOnly,
503            5 => Self::GenErr,
504            6 => Self::NoAccess,
505            7 => Self::WrongType,
506            8 => Self::WrongLength,
507            9 => Self::WrongEncoding,
508            10 => Self::WrongValue,
509            11 => Self::NoCreation,
510            12 => Self::InconsistentValue,
511            13 => Self::ResourceUnavailable,
512            14 => Self::CommitFailed,
513            15 => Self::UndoFailed,
514            16 => Self::AuthorizationError,
515            17 => Self::NotWritable,
516            18 => Self::InconsistentName,
517            other => Self::Unknown(other),
518        }
519    }
520
521    /// Convert to raw status code.
522    pub fn as_i32(&self) -> i32 {
523        match self {
524            Self::NoError => 0,
525            Self::TooBig => 1,
526            Self::NoSuchName => 2,
527            Self::BadValue => 3,
528            Self::ReadOnly => 4,
529            Self::GenErr => 5,
530            Self::NoAccess => 6,
531            Self::WrongType => 7,
532            Self::WrongLength => 8,
533            Self::WrongEncoding => 9,
534            Self::WrongValue => 10,
535            Self::NoCreation => 11,
536            Self::InconsistentValue => 12,
537            Self::ResourceUnavailable => 13,
538            Self::CommitFailed => 14,
539            Self::UndoFailed => 15,
540            Self::AuthorizationError => 16,
541            Self::NotWritable => 17,
542            Self::InconsistentName => 18,
543            Self::Unknown(code) => *code,
544        }
545    }
546}
547
548impl std::fmt::Display for ErrorStatus {
549    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
550        match self {
551            Self::NoError => write!(f, "noError"),
552            Self::TooBig => write!(f, "tooBig"),
553            Self::NoSuchName => write!(f, "noSuchName"),
554            Self::BadValue => write!(f, "badValue"),
555            Self::ReadOnly => write!(f, "readOnly"),
556            Self::GenErr => write!(f, "genErr"),
557            Self::NoAccess => write!(f, "noAccess"),
558            Self::WrongType => write!(f, "wrongType"),
559            Self::WrongLength => write!(f, "wrongLength"),
560            Self::WrongEncoding => write!(f, "wrongEncoding"),
561            Self::WrongValue => write!(f, "wrongValue"),
562            Self::NoCreation => write!(f, "noCreation"),
563            Self::InconsistentValue => write!(f, "inconsistentValue"),
564            Self::ResourceUnavailable => write!(f, "resourceUnavailable"),
565            Self::CommitFailed => write!(f, "commitFailed"),
566            Self::UndoFailed => write!(f, "undoFailed"),
567            Self::AuthorizationError => write!(f, "authorizationError"),
568            Self::NotWritable => write!(f, "notWritable"),
569            Self::InconsistentName => write!(f, "inconsistentName"),
570            Self::Unknown(code) => write!(f, "unknown({})", code),
571        }
572    }
573}
574
575/// The main error type for all async-snmp operations.
576///
577/// This enum covers all possible error conditions including network issues,
578/// protocol errors, encoding/decoding failures, and SNMPv3 security errors.
579///
580/// # Common Patterns
581///
582/// ## Checking Error Type
583///
584/// Use pattern matching to handle specific error conditions:
585///
586/// ```
587/// use async_snmp::{Error, ErrorStatus};
588///
589/// fn is_retriable(error: &Error) -> bool {
590///     matches!(error,
591///         Error::Timeout { .. } |
592///         Error::Io { .. } |
593///         Error::NotInTimeWindow { .. }
594///     )
595/// }
596///
597/// fn is_access_error(error: &Error) -> bool {
598///     matches!(error,
599///         Error::Snmp { status: ErrorStatus::NoAccess | ErrorStatus::AuthorizationError, .. } |
600///         Error::AuthenticationFailed { .. } |
601///         Error::InvalidCommunity { .. }
602///     )
603/// }
604/// ```
605///
606/// ## Extracting Target Address
607///
608/// Many errors include the target address for diagnostics:
609///
610/// ```
611/// use async_snmp::Error;
612///
613/// fn log_error(error: &Error) {
614///     if let Some(addr) = error.target() {
615///         println!("Error from {}: {}", addr, error);
616///     } else {
617///         println!("Error: {}", error);
618///     }
619/// }
620/// ```
621#[derive(Debug, thiserror::Error)]
622#[non_exhaustive]
623pub enum Error {
624    /// I/O error during network communication.
625    #[error("I/O error{}: {source}", target.map(|t| format!(" communicating with {}", t)).unwrap_or_default())]
626    Io {
627        target: Option<SocketAddr>,
628        #[source]
629        source: std::io::Error,
630    },
631
632    /// Request timed out (after retries if configured).
633    #[error("timeout after {elapsed:?}{} (request_id={request_id}, retries={retries})", target.map(|t| format!(" waiting for {}", t)).unwrap_or_default())]
634    Timeout {
635        target: Option<SocketAddr>,
636        elapsed: Duration,
637        request_id: i32,
638        retries: u32,
639    },
640
641    /// SNMP protocol error returned by agent.
642    #[error("SNMP error{}: {status} at index {index}", target.map(|t| format!(" from {}", t)).unwrap_or_default())]
643    Snmp {
644        target: Option<SocketAddr>,
645        status: ErrorStatus,
646        index: u32,
647        oid: Option<crate::oid::Oid>,
648    },
649
650    /// Invalid OID format.
651    #[error("invalid OID: {kind}")]
652    InvalidOid {
653        kind: OidErrorKind,
654        input: Option<Box<str>>, // Only allocated when parsing string input
655    },
656
657    /// BER decoding error.
658    #[error("decode error at offset {offset}: {kind}")]
659    Decode {
660        offset: usize,
661        kind: DecodeErrorKind,
662    },
663
664    /// BER encoding error.
665    #[error("encode error: {kind}")]
666    Encode { kind: EncodeErrorKind },
667
668    /// Response request ID doesn't match.
669    #[error("request ID mismatch: expected {expected}, got {actual}")]
670    RequestIdMismatch { expected: i32, actual: i32 },
671
672    /// Response version doesn't match request.
673    #[error("version mismatch: expected {expected:?}, got {actual:?}")]
674    VersionMismatch {
675        expected: crate::version::Version,
676        actual: crate::version::Version,
677    },
678
679    /// Message exceeds maximum size.
680    #[error("message too large: {size} bytes exceeds maximum {max}")]
681    MessageTooLarge { size: usize, max: usize },
682
683    /// Unknown engine ID (SNMPv3).
684    #[error("unknown engine ID")]
685    UnknownEngineId { target: Option<SocketAddr> },
686
687    /// Message outside time window (SNMPv3).
688    #[error("message not in time window")]
689    NotInTimeWindow { target: Option<SocketAddr> },
690
691    /// Authentication failed (SNMPv3).
692    #[error("authentication failed: {kind}")]
693    AuthenticationFailed {
694        target: Option<SocketAddr>,
695        kind: AuthErrorKind,
696    },
697
698    /// Decryption failed (SNMPv3).
699    #[error("decryption failed: {kind}")]
700    DecryptionFailed {
701        target: Option<SocketAddr>,
702        kind: CryptoErrorKind,
703    },
704
705    /// Encryption failed (SNMPv3).
706    #[error("encryption failed: {kind}")]
707    EncryptionFailed {
708        target: Option<SocketAddr>,
709        kind: CryptoErrorKind,
710    },
711
712    /// Invalid community string.
713    #[error("invalid community")]
714    InvalidCommunity { target: Option<SocketAddr> },
715
716    /// Non-increasing OID detected during walk (agent misbehavior).
717    ///
718    /// Returned when a walk operation receives an OID that is not
719    /// lexicographically greater than the previous OID, which would
720    /// cause an infinite loop. This indicates a non-conformant SNMP agent.
721    ///
722    /// Only occurs with `OidOrdering::Strict` (the default).
723    #[error("walk detected non-increasing OID: {previous} >= {current}")]
724    NonIncreasingOid {
725        previous: crate::oid::Oid,
726        current: crate::oid::Oid,
727    },
728
729    /// Walk detected a cycle (same OID returned twice).
730    ///
731    /// Only occurs with `OidOrdering::AllowNonIncreasing`, which uses
732    /// a HashSet to track all seen OIDs and detect cycles.
733    #[error("walk cycle detected: OID {oid} returned twice")]
734    DuplicateOid { oid: crate::oid::Oid },
735
736    /// GETBULK not supported in SNMPv1.
737    ///
738    /// Returned when `WalkMode::GetBulk` is explicitly requested with an SNMPv1 client.
739    /// GETBULK is only available in SNMPv2c and SNMPv3.
740    #[error("GETBULK is not supported in SNMPv1")]
741    GetBulkNotSupportedInV1,
742
743    /// Configuration error.
744    ///
745    /// Returned when client configuration is invalid (e.g., privacy
746    /// without authentication, missing passwords).
747    #[error("configuration error: {0}")]
748    Config(String),
749}
750
751impl Error {
752    /// Create a decode error.
753    pub fn decode(offset: usize, kind: DecodeErrorKind) -> Self {
754        Self::Decode { offset, kind }
755    }
756
757    /// Create an encode error.
758    pub fn encode(kind: EncodeErrorKind) -> Self {
759        Self::Encode { kind }
760    }
761
762    /// Create an authentication error.
763    pub fn auth(target: Option<SocketAddr>, kind: AuthErrorKind) -> Self {
764        Self::AuthenticationFailed { target, kind }
765    }
766
767    /// Create a decryption error.
768    pub fn decrypt(target: Option<SocketAddr>, kind: CryptoErrorKind) -> Self {
769        Self::DecryptionFailed { target, kind }
770    }
771
772    /// Create an encryption error.
773    pub fn encrypt(target: Option<SocketAddr>, kind: CryptoErrorKind) -> Self {
774        Self::EncryptionFailed { target, kind }
775    }
776
777    /// Create an invalid OID error from a kind (no input string).
778    pub fn invalid_oid(kind: OidErrorKind) -> Self {
779        Self::InvalidOid { kind, input: None }
780    }
781
782    /// Create an invalid OID error with the input string that failed.
783    pub fn invalid_oid_with_input(kind: OidErrorKind, input: impl Into<Box<str>>) -> Self {
784        Self::InvalidOid {
785            kind,
786            input: Some(input.into()),
787        }
788    }
789
790    /// Get the target address if this error has one.
791    ///
792    /// Returns `Some(addr)` for network-related errors that have a known target,
793    /// `None` for errors like OID parsing or encoding that aren't target-specific.
794    ///
795    /// # Example
796    ///
797    /// ```
798    /// use async_snmp::Error;
799    /// use std::time::Duration;
800    ///
801    /// let error = Error::Timeout {
802    ///     target: Some("192.168.1.1:161".parse().unwrap()),
803    ///     elapsed: Duration::from_secs(5),
804    ///     request_id: 42,
805    ///     retries: 3,
806    /// };
807    ///
808    /// assert_eq!(
809    ///     error.target().map(|a| a.to_string()),
810    ///     Some("192.168.1.1:161".to_string())
811    /// );
812    /// ```
813    pub fn target(&self) -> Option<SocketAddr> {
814        match self {
815            Self::Io { target, .. } => *target,
816            Self::Timeout { target, .. } => *target,
817            Self::Snmp { target, .. } => *target,
818            Self::UnknownEngineId { target } => *target,
819            Self::NotInTimeWindow { target } => *target,
820            Self::AuthenticationFailed { target, .. } => *target,
821            Self::DecryptionFailed { target, .. } => *target,
822            Self::EncryptionFailed { target, .. } => *target,
823            Self::InvalidCommunity { target } => *target,
824            _ => None,
825        }
826    }
827}