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}