1use std::net::SocketAddr;
6use std::time::Duration;
7
8pub type Result<T> = std::result::Result<T, Error>;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum AuthErrorKind {
14 NoCredentials,
16 NoAuthKey,
18 NoUser,
20 HmacMismatch,
22 WrongMacLength { expected: usize, actual: usize },
24 AuthParamsNotFound,
26}
27
28impl std::fmt::Display for AuthErrorKind {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 Self::NoCredentials => write!(f, "no credentials configured"),
32 Self::NoAuthKey => write!(f, "no authentication key available"),
33 Self::NoUser => write!(f, "user not found"),
34 Self::HmacMismatch => write!(f, "HMAC verification failed"),
35 Self::WrongMacLength { expected, actual } => {
36 write!(f, "wrong MAC length: expected {}, got {}", expected, actual)
37 }
38 Self::AuthParamsNotFound => write!(f, "could not locate auth params in message"),
39 }
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum CryptoErrorKind {
46 NoPrivKey,
48 InvalidPadding,
50 InvalidKeyLength,
52 InvalidIvLength,
54 CipherError,
56 UnsupportedProtocol,
58 InvalidPrivParamsLength { expected: usize, actual: usize },
60 InvalidCiphertextLength { length: usize, block_size: usize },
62}
63
64impl std::fmt::Display for CryptoErrorKind {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 match self {
67 Self::NoPrivKey => write!(f, "no privacy key available"),
68 Self::InvalidPadding => write!(f, "invalid padding"),
69 Self::InvalidKeyLength => write!(f, "invalid key length"),
70 Self::InvalidIvLength => write!(f, "invalid IV length"),
71 Self::CipherError => write!(f, "cipher operation failed"),
72 Self::UnsupportedProtocol => write!(f, "unsupported privacy protocol"),
73 Self::InvalidPrivParamsLength { expected, actual } => {
74 write!(
75 f,
76 "invalid privParameters length: expected {}, got {}",
77 expected, actual
78 )
79 }
80 Self::InvalidCiphertextLength { length, block_size } => {
81 write!(
82 f,
83 "ciphertext length {} not multiple of block size {}",
84 length, block_size
85 )
86 }
87 }
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum DecodeErrorKind {
94 UnexpectedTag { expected: u8, actual: u8 },
96 TruncatedData,
98 InvalidLength,
100 IndefiniteLength,
102 IntegerOverflow,
104 ZeroLengthInteger,
106 InvalidOidEncoding,
108 UnknownVersion(i32),
110 UnknownPduType(u8),
112 ConstructedOctetString,
114 MissingPdu,
116 InvalidMsgFlags,
118 UnknownSecurityModel(i32),
120 MsgMaxSizeTooSmall { value: i32, minimum: i32 },
122 InvalidNull,
124 UnexpectedEncryption,
126 ExpectedEncryption,
128 InvalidIpAddressLength { length: usize },
130 LengthTooLong { octets: usize },
132 LengthExceedsMax { length: usize, max: usize },
134 Integer64TooLong { length: usize },
136 EmptyResponse,
138 TlvOverflow,
140 InsufficientData { needed: usize, available: usize },
142 InvalidOid,
144}
145
146impl std::fmt::Display for DecodeErrorKind {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 match self {
149 Self::UnexpectedTag { expected, actual } => {
150 write!(f, "expected tag 0x{:02X}, got 0x{:02X}", expected, actual)
151 }
152 Self::TruncatedData => write!(f, "unexpected end of data"),
153 Self::InvalidLength => write!(f, "invalid length encoding"),
154 Self::IndefiniteLength => write!(f, "indefinite length encoding not supported"),
155 Self::IntegerOverflow => write!(f, "integer overflow"),
156 Self::ZeroLengthInteger => write!(f, "zero-length integer"),
157 Self::InvalidOidEncoding => write!(f, "invalid OID encoding"),
158 Self::UnknownVersion(v) => write!(f, "unknown SNMP version: {}", v),
159 Self::UnknownPduType(t) => write!(f, "unknown PDU type: 0x{:02X}", t),
160 Self::ConstructedOctetString => {
161 write!(f, "constructed OCTET STRING (0x24) not supported")
162 }
163 Self::MissingPdu => write!(f, "missing PDU in message"),
164 Self::InvalidMsgFlags => write!(f, "invalid msgFlags: privacy without authentication"),
165 Self::UnknownSecurityModel(m) => write!(f, "unknown security model: {}", m),
166 Self::MsgMaxSizeTooSmall { value, minimum } => {
167 write!(f, "msgMaxSize {} below RFC 3412 minimum {}", value, minimum)
168 }
169 Self::InvalidNull => write!(f, "NULL with non-zero length"),
170 Self::UnexpectedEncryption => write!(f, "expected plaintext scoped PDU"),
171 Self::ExpectedEncryption => write!(f, "expected encrypted scoped PDU"),
172 Self::InvalidIpAddressLength { length } => {
173 write!(f, "IP address must be 4 bytes, got {}", length)
174 }
175 Self::LengthTooLong { octets } => {
176 write!(f, "length encoding too long ({} octets)", octets)
177 }
178 Self::LengthExceedsMax { length, max } => {
179 write!(f, "length {} exceeds maximum {}", length, max)
180 }
181 Self::Integer64TooLong { length } => {
182 write!(f, "integer64 too long: {} bytes", length)
183 }
184 Self::EmptyResponse => write!(f, "empty response"),
185 Self::TlvOverflow => write!(f, "TLV extends past end of data"),
186 Self::InsufficientData { needed, available } => {
187 write!(f, "need {} bytes but only {} remaining", needed, available)
188 }
189 Self::InvalidOid => write!(f, "invalid OID in notification varbinds"),
190 }
191 }
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub enum EncodeErrorKind {
197 NoSecurityConfig,
199 EngineNotDiscovered,
201 KeysNotDerived,
203 MissingAuthKey,
205 NoPrivKey,
207 MissingAuthParams,
209}
210
211impl std::fmt::Display for EncodeErrorKind {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 match self {
214 Self::NoSecurityConfig => write!(f, "V3 security config not set"),
215 Self::EngineNotDiscovered => write!(f, "engine not discovered"),
216 Self::KeysNotDerived => write!(f, "keys not derived"),
217 Self::MissingAuthKey => write!(f, "auth key not available for encoding"),
218 Self::NoPrivKey => write!(f, "privacy key not available"),
219 Self::MissingAuthParams => {
220 write!(f, "could not find auth params position in encoded message")
221 }
222 }
223 }
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub enum OidErrorKind {
229 Empty,
231 InvalidArc,
233 InvalidFirstArc(u32),
235 InvalidSecondArc { first: u32, second: u32 },
237 TooShort,
239 TooManyArcs { count: usize, max: usize },
241 SubidentifierOverflow,
243}
244
245impl std::fmt::Display for OidErrorKind {
246 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247 match self {
248 Self::Empty => write!(f, "empty OID"),
249 Self::InvalidArc => write!(f, "invalid arc value"),
250 Self::InvalidFirstArc(v) => write!(f, "first arc must be 0, 1, or 2, got {}", v),
251 Self::InvalidSecondArc { first, second } => {
252 write!(f, "second arc {} too large for first arc {}", second, first)
253 }
254 Self::TooShort => write!(f, "OID must have at least 2 arcs"),
255 Self::TooManyArcs { count, max } => {
256 write!(f, "OID has {} arcs, exceeds maximum {}", count, max)
257 }
258 Self::SubidentifierOverflow => write!(f, "subidentifier overflow"),
259 }
260 }
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265#[non_exhaustive]
266pub enum ErrorStatus {
267 NoError,
268 TooBig,
269 NoSuchName,
270 BadValue,
271 ReadOnly,
272 GenErr,
273 NoAccess,
274 WrongType,
275 WrongLength,
276 WrongEncoding,
277 WrongValue,
278 NoCreation,
279 InconsistentValue,
280 ResourceUnavailable,
281 CommitFailed,
282 UndoFailed,
283 AuthorizationError,
284 NotWritable,
285 InconsistentName,
286 Unknown(i32),
288}
289
290impl ErrorStatus {
291 pub fn from_i32(value: i32) -> Self {
293 match value {
294 0 => Self::NoError,
295 1 => Self::TooBig,
296 2 => Self::NoSuchName,
297 3 => Self::BadValue,
298 4 => Self::ReadOnly,
299 5 => Self::GenErr,
300 6 => Self::NoAccess,
301 7 => Self::WrongType,
302 8 => Self::WrongLength,
303 9 => Self::WrongEncoding,
304 10 => Self::WrongValue,
305 11 => Self::NoCreation,
306 12 => Self::InconsistentValue,
307 13 => Self::ResourceUnavailable,
308 14 => Self::CommitFailed,
309 15 => Self::UndoFailed,
310 16 => Self::AuthorizationError,
311 17 => Self::NotWritable,
312 18 => Self::InconsistentName,
313 other => Self::Unknown(other),
314 }
315 }
316
317 pub fn as_i32(&self) -> i32 {
319 match self {
320 Self::NoError => 0,
321 Self::TooBig => 1,
322 Self::NoSuchName => 2,
323 Self::BadValue => 3,
324 Self::ReadOnly => 4,
325 Self::GenErr => 5,
326 Self::NoAccess => 6,
327 Self::WrongType => 7,
328 Self::WrongLength => 8,
329 Self::WrongEncoding => 9,
330 Self::WrongValue => 10,
331 Self::NoCreation => 11,
332 Self::InconsistentValue => 12,
333 Self::ResourceUnavailable => 13,
334 Self::CommitFailed => 14,
335 Self::UndoFailed => 15,
336 Self::AuthorizationError => 16,
337 Self::NotWritable => 17,
338 Self::InconsistentName => 18,
339 Self::Unknown(code) => *code,
340 }
341 }
342}
343
344impl std::fmt::Display for ErrorStatus {
345 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
346 match self {
347 Self::NoError => write!(f, "noError"),
348 Self::TooBig => write!(f, "tooBig"),
349 Self::NoSuchName => write!(f, "noSuchName"),
350 Self::BadValue => write!(f, "badValue"),
351 Self::ReadOnly => write!(f, "readOnly"),
352 Self::GenErr => write!(f, "genErr"),
353 Self::NoAccess => write!(f, "noAccess"),
354 Self::WrongType => write!(f, "wrongType"),
355 Self::WrongLength => write!(f, "wrongLength"),
356 Self::WrongEncoding => write!(f, "wrongEncoding"),
357 Self::WrongValue => write!(f, "wrongValue"),
358 Self::NoCreation => write!(f, "noCreation"),
359 Self::InconsistentValue => write!(f, "inconsistentValue"),
360 Self::ResourceUnavailable => write!(f, "resourceUnavailable"),
361 Self::CommitFailed => write!(f, "commitFailed"),
362 Self::UndoFailed => write!(f, "undoFailed"),
363 Self::AuthorizationError => write!(f, "authorizationError"),
364 Self::NotWritable => write!(f, "notWritable"),
365 Self::InconsistentName => write!(f, "inconsistentName"),
366 Self::Unknown(code) => write!(f, "unknown({})", code),
367 }
368 }
369}
370
371#[derive(Debug, thiserror::Error)]
373#[non_exhaustive]
374pub enum Error {
375 #[error("I/O error{}: {source}", target.map(|t| format!(" communicating with {}", t)).unwrap_or_default())]
377 Io {
378 target: Option<SocketAddr>,
379 #[source]
380 source: std::io::Error,
381 },
382
383 #[error("timeout after {elapsed:?}{} (request_id={request_id}, retries={retries})", target.map(|t| format!(" waiting for {}", t)).unwrap_or_default())]
385 Timeout {
386 target: Option<SocketAddr>,
387 elapsed: Duration,
388 request_id: i32,
389 retries: u32,
390 },
391
392 #[error("SNMP error{}: {status} at index {index}", target.map(|t| format!(" from {}", t)).unwrap_or_default())]
394 Snmp {
395 target: Option<SocketAddr>,
396 status: ErrorStatus,
397 index: u32,
398 oid: Option<crate::oid::Oid>,
399 },
400
401 #[error("invalid OID: {kind}")]
403 InvalidOid {
404 kind: OidErrorKind,
405 input: Option<Box<str>>, },
407
408 #[error("decode error at offset {offset}: {kind}")]
410 Decode {
411 offset: usize,
412 kind: DecodeErrorKind,
413 },
414
415 #[error("encode error: {kind}")]
417 Encode { kind: EncodeErrorKind },
418
419 #[error("request ID mismatch: expected {expected}, got {actual}")]
421 RequestIdMismatch { expected: i32, actual: i32 },
422
423 #[error("version mismatch: expected {expected:?}, got {actual:?}")]
425 VersionMismatch {
426 expected: crate::version::Version,
427 actual: crate::version::Version,
428 },
429
430 #[error("message too large: {size} bytes exceeds maximum {max}")]
432 MessageTooLarge { size: usize, max: usize },
433
434 #[error("unknown engine ID")]
436 UnknownEngineId { target: Option<SocketAddr> },
437
438 #[error("message not in time window")]
440 NotInTimeWindow { target: Option<SocketAddr> },
441
442 #[error("authentication failed: {kind}")]
444 AuthenticationFailed {
445 target: Option<SocketAddr>,
446 kind: AuthErrorKind,
447 },
448
449 #[error("decryption failed: {kind}")]
451 DecryptionFailed {
452 target: Option<SocketAddr>,
453 kind: CryptoErrorKind,
454 },
455
456 #[error("encryption failed: {kind}")]
458 EncryptionFailed {
459 target: Option<SocketAddr>,
460 kind: CryptoErrorKind,
461 },
462
463 #[error("invalid community")]
465 InvalidCommunity { target: Option<SocketAddr> },
466
467 #[error("walk detected non-increasing OID: {previous} >= {current}")]
475 NonIncreasingOid {
476 previous: crate::oid::Oid,
477 current: crate::oid::Oid,
478 },
479
480 #[error("walk cycle detected: OID {oid} returned twice")]
485 DuplicateOid { oid: crate::oid::Oid },
486
487 #[error("GETBULK is not supported in SNMPv1")]
492 GetBulkNotSupportedInV1,
493
494 #[error("configuration error: {0}")]
499 Config(String),
500}
501
502impl Error {
503 pub fn decode(offset: usize, kind: DecodeErrorKind) -> Self {
505 Self::Decode { offset, kind }
506 }
507
508 pub fn encode(kind: EncodeErrorKind) -> Self {
510 Self::Encode { kind }
511 }
512
513 pub fn auth(target: Option<SocketAddr>, kind: AuthErrorKind) -> Self {
515 Self::AuthenticationFailed { target, kind }
516 }
517
518 pub fn decrypt(target: Option<SocketAddr>, kind: CryptoErrorKind) -> Self {
520 Self::DecryptionFailed { target, kind }
521 }
522
523 pub fn encrypt(target: Option<SocketAddr>, kind: CryptoErrorKind) -> Self {
525 Self::EncryptionFailed { target, kind }
526 }
527
528 pub fn invalid_oid(kind: OidErrorKind) -> Self {
530 Self::InvalidOid { kind, input: None }
531 }
532
533 pub fn invalid_oid_with_input(kind: OidErrorKind, input: impl Into<Box<str>>) -> Self {
535 Self::InvalidOid {
536 kind,
537 input: Some(input.into()),
538 }
539 }
540
541 pub fn target(&self) -> Option<SocketAddr> {
543 match self {
544 Self::Io { target, .. } => *target,
545 Self::Timeout { target, .. } => *target,
546 Self::Snmp { target, .. } => *target,
547 Self::UnknownEngineId { target } => *target,
548 Self::NotInTimeWindow { target } => *target,
549 Self::AuthenticationFailed { target, .. } => *target,
550 Self::DecryptionFailed { target, .. } => *target,
551 Self::EncryptionFailed { target, .. } => *target,
552 Self::InvalidCommunity { target } => *target,
553 _ => None,
554 }
555 }
556}