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 /// msgMaxSize above RFC 3412 maximum (2147483647).
271 MsgMaxSizeTooLarge { value: i32 },
272 /// msgID outside RFC 3412 range (0..2147483647).
273 InvalidMsgId { value: i32 },
274 /// NULL with non-zero length.
275 InvalidNull,
276 /// Expected plaintext, got encrypted.
277 UnexpectedEncryption,
278 /// Expected encrypted, got plaintext.
279 ExpectedEncryption,
280 /// Invalid IP address length.
281 InvalidIpAddressLength { length: usize },
282 /// Length field too long.
283 LengthTooLong { octets: usize },
284 /// Length exceeds maximum.
285 LengthExceedsMax { length: usize, max: usize },
286 /// Integer64 too long.
287 Integer64TooLong { length: usize },
288 /// Empty response.
289 EmptyResponse,
290 /// TLV extends past end of data.
291 TlvOverflow,
292 /// Insufficient data for read.
293 InsufficientData { needed: usize, available: usize },
294 /// Invalid OID in notification varbinds.
295 InvalidOid,
296}
297
298impl std::fmt::Display for DecodeErrorKind {
299 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300 match self {
301 Self::UnexpectedTag { expected, actual } => {
302 write!(f, "expected tag 0x{:02X}, got 0x{:02X}", expected, actual)
303 }
304 Self::TruncatedData => write!(f, "unexpected end of data"),
305 Self::InvalidLength => write!(f, "invalid length encoding"),
306 Self::IndefiniteLength => write!(f, "indefinite length encoding not supported"),
307 Self::IntegerOverflow => write!(f, "integer overflow"),
308 Self::ZeroLengthInteger => write!(f, "zero-length integer"),
309 Self::InvalidOidEncoding => write!(f, "invalid OID encoding"),
310 Self::UnknownVersion(v) => write!(f, "unknown SNMP version: {}", v),
311 Self::UnknownPduType(t) => write!(f, "unknown PDU type: 0x{:02X}", t),
312 Self::ConstructedOctetString => {
313 write!(f, "constructed OCTET STRING (0x24) not supported")
314 }
315 Self::MissingPdu => write!(f, "missing PDU in message"),
316 Self::InvalidMsgFlags => write!(f, "invalid msgFlags: privacy without authentication"),
317 Self::UnknownSecurityModel(m) => write!(f, "unknown security model: {}", m),
318 Self::MsgMaxSizeTooSmall { value, minimum } => {
319 write!(f, "msgMaxSize {} below RFC 3412 minimum {}", value, minimum)
320 }
321 Self::MsgMaxSizeTooLarge { value } => {
322 write!(f, "msgMaxSize {} above RFC 3412 maximum 2147483647", value)
323 }
324 Self::InvalidMsgId { value } => {
325 write!(f, "msgID {} outside RFC 3412 range 0..2147483647", value)
326 }
327 Self::InvalidNull => write!(f, "NULL with non-zero length"),
328 Self::UnexpectedEncryption => write!(f, "expected plaintext scoped PDU"),
329 Self::ExpectedEncryption => write!(f, "expected encrypted scoped PDU"),
330 Self::InvalidIpAddressLength { length } => {
331 write!(f, "IP address must be 4 bytes, got {}", length)
332 }
333 Self::LengthTooLong { octets } => {
334 write!(f, "length encoding too long ({} octets)", octets)
335 }
336 Self::LengthExceedsMax { length, max } => {
337 write!(f, "length {} exceeds maximum {}", length, max)
338 }
339 Self::Integer64TooLong { length } => {
340 write!(f, "integer64 too long: {} bytes", length)
341 }
342 Self::EmptyResponse => write!(f, "empty response"),
343 Self::TlvOverflow => write!(f, "TLV extends past end of data"),
344 Self::InsufficientData { needed, available } => {
345 write!(f, "need {} bytes but only {} remaining", needed, available)
346 }
347 Self::InvalidOid => write!(f, "invalid OID in notification varbinds"),
348 }
349 }
350}
351
352/// BER encode error kinds.
353#[derive(Debug, Clone, Copy, PartialEq, Eq)]
354pub enum EncodeErrorKind {
355 /// V3 security not configured.
356 NoSecurityConfig,
357 /// Engine not discovered.
358 EngineNotDiscovered,
359 /// Keys not derived.
360 KeysNotDerived,
361 /// Auth key not available for encoding.
362 MissingAuthKey,
363 /// Privacy key not available.
364 NoPrivKey,
365 /// Could not locate auth params position in encoded message.
366 MissingAuthParams,
367}
368
369impl std::fmt::Display for EncodeErrorKind {
370 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
371 match self {
372 Self::NoSecurityConfig => write!(f, "V3 security config not set"),
373 Self::EngineNotDiscovered => write!(f, "engine not discovered"),
374 Self::KeysNotDerived => write!(f, "keys not derived"),
375 Self::MissingAuthKey => write!(f, "auth key not available for encoding"),
376 Self::NoPrivKey => write!(f, "privacy key not available"),
377 Self::MissingAuthParams => {
378 write!(f, "could not find auth params position in encoded message")
379 }
380 }
381 }
382}
383
384/// OID validation error kinds.
385#[derive(Debug, Clone, Copy, PartialEq, Eq)]
386pub enum OidErrorKind {
387 /// Empty OID string.
388 Empty,
389 /// Invalid arc value.
390 InvalidArc,
391 /// First arc must be 0, 1, or 2.
392 InvalidFirstArc(u32),
393 /// Second arc too large for first arc value.
394 InvalidSecondArc { first: u32, second: u32 },
395 /// OID too short (minimum 2 arcs).
396 TooShort,
397 /// OID has too many arcs (exceeds MAX_OID_LEN).
398 TooManyArcs { count: usize, max: usize },
399 /// Subidentifier overflow during encoding.
400 SubidentifierOverflow,
401}
402
403impl std::fmt::Display for OidErrorKind {
404 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
405 match self {
406 Self::Empty => write!(f, "empty OID"),
407 Self::InvalidArc => write!(f, "invalid arc value"),
408 Self::InvalidFirstArc(v) => write!(f, "first arc must be 0, 1, or 2, got {}", v),
409 Self::InvalidSecondArc { first, second } => {
410 write!(f, "second arc {} too large for first arc {}", second, first)
411 }
412 Self::TooShort => write!(f, "OID must have at least 2 arcs"),
413 Self::TooManyArcs { count, max } => {
414 write!(f, "OID has {} arcs, exceeds maximum {}", count, max)
415 }
416 Self::SubidentifierOverflow => write!(f, "subidentifier overflow"),
417 }
418 }
419}
420
421/// SNMP protocol error status codes (RFC 3416).
422///
423/// These codes are returned by SNMP agents to indicate the result of an operation.
424/// The error status is included in the [`Error::Snmp`] variant along with an error
425/// index indicating which varbind caused the error.
426///
427/// # Error Categories
428///
429/// ## SNMPv1 Errors (0-5)
430///
431/// - `NoError` - Operation succeeded
432/// - `TooBig` - Response too large for transport
433/// - `NoSuchName` - OID not found (v1 only; v2c+ uses exceptions)
434/// - `BadValue` - Invalid value in SET
435/// - `ReadOnly` - Attempted write to read-only object
436/// - `GenErr` - Unspecified error
437///
438/// ## SNMPv2c/v3 Errors (6-18)
439///
440/// These provide more specific error information for SET operations:
441///
442/// - `NoAccess` - Object not accessible (access control)
443/// - `WrongType` - Value has wrong ASN.1 type
444/// - `WrongLength` - Value has wrong length
445/// - `WrongValue` - Value out of range or invalid
446/// - `NotWritable` - Object does not support SET
447/// - `AuthorizationError` - Access denied by VACM
448///
449/// # Example
450///
451/// ```
452/// use async_snmp::ErrorStatus;
453///
454/// let status = ErrorStatus::from_i32(2);
455/// assert_eq!(status, ErrorStatus::NoSuchName);
456/// assert_eq!(status.as_i32(), 2);
457/// println!("Error: {}", status); // prints "noSuchName"
458/// ```
459#[derive(Debug, Clone, Copy, PartialEq, Eq)]
460#[non_exhaustive]
461pub enum ErrorStatus {
462 /// Operation completed successfully (status = 0).
463 NoError,
464 /// Response message would be too large for transport (status = 1).
465 TooBig,
466 /// Requested OID not found (status = 2). SNMPv1 only; v2c+ uses exception values.
467 NoSuchName,
468 /// Invalid value provided in SET request (status = 3).
469 BadValue,
470 /// Attempted to SET a read-only object (status = 4).
471 ReadOnly,
472 /// Unspecified error occurred (status = 5).
473 GenErr,
474 /// Object exists but access is denied (status = 6).
475 NoAccess,
476 /// SET value has wrong ASN.1 type (status = 7).
477 WrongType,
478 /// SET value has incorrect length (status = 8).
479 WrongLength,
480 /// SET value uses wrong encoding (status = 9).
481 WrongEncoding,
482 /// SET value is out of range or otherwise invalid (status = 10).
483 WrongValue,
484 /// Object does not support row creation (status = 11).
485 NoCreation,
486 /// Value is inconsistent with other managed objects (status = 12).
487 InconsistentValue,
488 /// Resource required for SET is unavailable (status = 13).
489 ResourceUnavailable,
490 /// SET commit phase failed (status = 14).
491 CommitFailed,
492 /// SET undo phase failed (status = 15).
493 UndoFailed,
494 /// Access denied by VACM (status = 16).
495 AuthorizationError,
496 /// Object does not support modification (status = 17).
497 NotWritable,
498 /// Named object cannot be created (status = 18).
499 InconsistentName,
500 /// Unknown or future error status code.
501 Unknown(i32),
502}
503
504impl ErrorStatus {
505 /// Create from raw status code.
506 pub fn from_i32(value: i32) -> Self {
507 match value {
508 0 => Self::NoError,
509 1 => Self::TooBig,
510 2 => Self::NoSuchName,
511 3 => Self::BadValue,
512 4 => Self::ReadOnly,
513 5 => Self::GenErr,
514 6 => Self::NoAccess,
515 7 => Self::WrongType,
516 8 => Self::WrongLength,
517 9 => Self::WrongEncoding,
518 10 => Self::WrongValue,
519 11 => Self::NoCreation,
520 12 => Self::InconsistentValue,
521 13 => Self::ResourceUnavailable,
522 14 => Self::CommitFailed,
523 15 => Self::UndoFailed,
524 16 => Self::AuthorizationError,
525 17 => Self::NotWritable,
526 18 => Self::InconsistentName,
527 other => Self::Unknown(other),
528 }
529 }
530
531 /// Convert to raw status code.
532 pub fn as_i32(&self) -> i32 {
533 match self {
534 Self::NoError => 0,
535 Self::TooBig => 1,
536 Self::NoSuchName => 2,
537 Self::BadValue => 3,
538 Self::ReadOnly => 4,
539 Self::GenErr => 5,
540 Self::NoAccess => 6,
541 Self::WrongType => 7,
542 Self::WrongLength => 8,
543 Self::WrongEncoding => 9,
544 Self::WrongValue => 10,
545 Self::NoCreation => 11,
546 Self::InconsistentValue => 12,
547 Self::ResourceUnavailable => 13,
548 Self::CommitFailed => 14,
549 Self::UndoFailed => 15,
550 Self::AuthorizationError => 16,
551 Self::NotWritable => 17,
552 Self::InconsistentName => 18,
553 Self::Unknown(code) => *code,
554 }
555 }
556}
557
558impl std::fmt::Display for ErrorStatus {
559 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
560 match self {
561 Self::NoError => write!(f, "noError"),
562 Self::TooBig => write!(f, "tooBig"),
563 Self::NoSuchName => write!(f, "noSuchName"),
564 Self::BadValue => write!(f, "badValue"),
565 Self::ReadOnly => write!(f, "readOnly"),
566 Self::GenErr => write!(f, "genErr"),
567 Self::NoAccess => write!(f, "noAccess"),
568 Self::WrongType => write!(f, "wrongType"),
569 Self::WrongLength => write!(f, "wrongLength"),
570 Self::WrongEncoding => write!(f, "wrongEncoding"),
571 Self::WrongValue => write!(f, "wrongValue"),
572 Self::NoCreation => write!(f, "noCreation"),
573 Self::InconsistentValue => write!(f, "inconsistentValue"),
574 Self::ResourceUnavailable => write!(f, "resourceUnavailable"),
575 Self::CommitFailed => write!(f, "commitFailed"),
576 Self::UndoFailed => write!(f, "undoFailed"),
577 Self::AuthorizationError => write!(f, "authorizationError"),
578 Self::NotWritable => write!(f, "notWritable"),
579 Self::InconsistentName => write!(f, "inconsistentName"),
580 Self::Unknown(code) => write!(f, "unknown({})", code),
581 }
582 }
583}
584
585/// The main error type for all async-snmp operations.
586///
587/// This enum covers all possible error conditions including network issues,
588/// protocol errors, encoding/decoding failures, and SNMPv3 security errors.
589///
590/// # Common Patterns
591///
592/// ## Checking Error Type
593///
594/// Use pattern matching to handle specific error conditions:
595///
596/// ```
597/// use async_snmp::{Error, ErrorStatus};
598///
599/// fn is_retriable(error: &Error) -> bool {
600/// matches!(error,
601/// Error::Timeout { .. } |
602/// Error::Io { .. } |
603/// Error::NotInTimeWindow { .. }
604/// )
605/// }
606///
607/// fn is_access_error(error: &Error) -> bool {
608/// matches!(error,
609/// Error::Snmp { status: ErrorStatus::NoAccess | ErrorStatus::AuthorizationError, .. } |
610/// Error::AuthenticationFailed { .. } |
611/// Error::InvalidCommunity { .. }
612/// )
613/// }
614/// ```
615///
616/// ## Extracting Target Address
617///
618/// Many errors include the target address for diagnostics:
619///
620/// ```
621/// use async_snmp::Error;
622///
623/// fn log_error(error: &Error) {
624/// if let Some(addr) = error.target() {
625/// println!("Error from {}: {}", addr, error);
626/// } else {
627/// println!("Error: {}", error);
628/// }
629/// }
630/// ```
631#[derive(Debug, thiserror::Error)]
632#[non_exhaustive]
633pub enum Error {
634 /// I/O error during network communication.
635 #[error("I/O error{}: {source}", target.map(|t| format!(" communicating with {}", t)).unwrap_or_default())]
636 Io {
637 target: Option<SocketAddr>,
638 #[source]
639 source: std::io::Error,
640 },
641
642 /// Request timed out (after retries if configured).
643 #[error("timeout after {elapsed:?}{} (request_id={request_id}, retries={retries})", target.map(|t| format!(" waiting for {}", t)).unwrap_or_default())]
644 Timeout {
645 target: Option<SocketAddr>,
646 elapsed: Duration,
647 request_id: i32,
648 retries: u32,
649 },
650
651 /// SNMP protocol error returned by agent.
652 #[error("SNMP error{}: {status} at index {index}", target.map(|t| format!(" from {}", t)).unwrap_or_default())]
653 Snmp {
654 target: Option<SocketAddr>,
655 status: ErrorStatus,
656 index: u32,
657 oid: Option<crate::oid::Oid>,
658 },
659
660 /// Invalid OID format.
661 #[error("invalid OID: {kind}")]
662 InvalidOid {
663 kind: OidErrorKind,
664 input: Option<Box<str>>, // Only allocated when parsing string input
665 },
666
667 /// BER decoding error.
668 #[error("decode error at offset {offset}: {kind}")]
669 Decode {
670 offset: usize,
671 kind: DecodeErrorKind,
672 },
673
674 /// BER encoding error.
675 #[error("encode error: {kind}")]
676 Encode { kind: EncodeErrorKind },
677
678 /// Response request ID doesn't match.
679 #[error("request ID mismatch: expected {expected}, got {actual}")]
680 RequestIdMismatch { expected: i32, actual: i32 },
681
682 /// Response version doesn't match request.
683 #[error("version mismatch: expected {expected:?}, got {actual:?}")]
684 VersionMismatch {
685 expected: crate::version::Version,
686 actual: crate::version::Version,
687 },
688
689 /// Message exceeds maximum size.
690 #[error("message too large: {size} bytes exceeds maximum {max}")]
691 MessageTooLarge { size: usize, max: usize },
692
693 /// Unknown engine ID (SNMPv3).
694 #[error("unknown engine ID")]
695 UnknownEngineId { target: Option<SocketAddr> },
696
697 /// Message outside time window (SNMPv3).
698 #[error("message not in time window")]
699 NotInTimeWindow { target: Option<SocketAddr> },
700
701 /// Authentication failed (SNMPv3).
702 #[error("authentication failed: {kind}")]
703 AuthenticationFailed {
704 target: Option<SocketAddr>,
705 kind: AuthErrorKind,
706 },
707
708 /// Decryption failed (SNMPv3).
709 #[error("decryption failed: {kind}")]
710 DecryptionFailed {
711 target: Option<SocketAddr>,
712 kind: CryptoErrorKind,
713 },
714
715 /// Encryption failed (SNMPv3).
716 #[error("encryption failed: {kind}")]
717 EncryptionFailed {
718 target: Option<SocketAddr>,
719 kind: CryptoErrorKind,
720 },
721
722 /// Invalid community string.
723 #[error("invalid community")]
724 InvalidCommunity { target: Option<SocketAddr> },
725
726 /// Non-increasing OID detected during walk (agent misbehavior).
727 ///
728 /// Returned when a walk operation receives an OID that is not
729 /// lexicographically greater than the previous OID, which would
730 /// cause an infinite loop. This indicates a non-conformant SNMP agent.
731 ///
732 /// Only occurs with `OidOrdering::Strict` (the default).
733 #[error("walk detected non-increasing OID: {previous} >= {current}")]
734 NonIncreasingOid {
735 previous: crate::oid::Oid,
736 current: crate::oid::Oid,
737 },
738
739 /// Walk detected a cycle (same OID returned twice).
740 ///
741 /// Only occurs with `OidOrdering::AllowNonIncreasing`, which uses
742 /// a HashSet to track all seen OIDs and detect cycles.
743 #[error("walk cycle detected: OID {oid} returned twice")]
744 DuplicateOid { oid: crate::oid::Oid },
745
746 /// GETBULK not supported in SNMPv1.
747 ///
748 /// Returned when `WalkMode::GetBulk` is explicitly requested with an SNMPv1 client.
749 /// GETBULK is only available in SNMPv2c and SNMPv3.
750 #[error("GETBULK is not supported in SNMPv1")]
751 GetBulkNotSupportedInV1,
752
753 /// Configuration error.
754 ///
755 /// Returned when client configuration is invalid (e.g., privacy
756 /// without authentication, missing passwords).
757 #[error("configuration error: {0}")]
758 Config(String),
759}
760
761impl Error {
762 /// Create a decode error.
763 pub fn decode(offset: usize, kind: DecodeErrorKind) -> Self {
764 Self::Decode { offset, kind }
765 }
766
767 /// Create an encode error.
768 pub fn encode(kind: EncodeErrorKind) -> Self {
769 Self::Encode { kind }
770 }
771
772 /// Create an authentication error.
773 pub fn auth(target: Option<SocketAddr>, kind: AuthErrorKind) -> Self {
774 Self::AuthenticationFailed { target, kind }
775 }
776
777 /// Create a decryption error.
778 pub fn decrypt(target: Option<SocketAddr>, kind: CryptoErrorKind) -> Self {
779 Self::DecryptionFailed { target, kind }
780 }
781
782 /// Create an encryption error.
783 pub fn encrypt(target: Option<SocketAddr>, kind: CryptoErrorKind) -> Self {
784 Self::EncryptionFailed { target, kind }
785 }
786
787 /// Create an invalid OID error from a kind (no input string).
788 pub fn invalid_oid(kind: OidErrorKind) -> Self {
789 Self::InvalidOid { kind, input: None }
790 }
791
792 /// Create an invalid OID error with the input string that failed.
793 pub fn invalid_oid_with_input(kind: OidErrorKind, input: impl Into<Box<str>>) -> Self {
794 Self::InvalidOid {
795 kind,
796 input: Some(input.into()),
797 }
798 }
799
800 /// Get the target address if this error has one.
801 ///
802 /// Returns `Some(addr)` for network-related errors that have a known target,
803 /// `None` for errors like OID parsing or encoding that aren't target-specific.
804 ///
805 /// # Example
806 ///
807 /// ```
808 /// use async_snmp::Error;
809 /// use std::time::Duration;
810 ///
811 /// let error = Error::Timeout {
812 /// target: Some("192.168.1.1:161".parse().unwrap()),
813 /// elapsed: Duration::from_secs(5),
814 /// request_id: 42,
815 /// retries: 3,
816 /// };
817 ///
818 /// assert_eq!(
819 /// error.target().map(|a| a.to_string()),
820 /// Some("192.168.1.1:161".to_string())
821 /// );
822 /// ```
823 pub fn target(&self) -> Option<SocketAddr> {
824 match self {
825 Self::Io { target, .. } => *target,
826 Self::Timeout { target, .. } => *target,
827 Self::Snmp { target, .. } => *target,
828 Self::UnknownEngineId { target } => *target,
829 Self::NotInTimeWindow { target } => *target,
830 Self::AuthenticationFailed { target, .. } => *target,
831 Self::DecryptionFailed { target, .. } => *target,
832 Self::EncryptionFailed { target, .. } => *target,
833 Self::InvalidCommunity { target } => *target,
834 _ => None,
835 }
836 }
837}