pub(crate) mod internal;
use std::net::SocketAddr;
use std::time::Duration;
use crate::oid::Oid;
pub(crate) const UNKNOWN_TARGET: SocketAddr =
SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)), 0);
pub type Result<T> = std::result::Result<T, Box<Error>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WalkAbortReason {
NonIncreasing,
Cycle,
}
impl std::fmt::Display for WalkAbortReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NonIncreasing => write!(f, "non-increasing OID"),
Self::Cycle => write!(f, "cycle detected"),
}
}
}
impl std::error::Error for WalkAbortReason {}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("network error communicating with {target}: {source}")]
Network {
target: SocketAddr,
#[source]
source: std::io::Error,
},
#[error("timeout after {elapsed:?} waiting for {target} ({retries} retries)")]
Timeout {
target: SocketAddr,
elapsed: Duration,
retries: u32,
},
#[error("SNMP error from {target}: {status} at index {index}")]
Snmp {
target: SocketAddr,
status: ErrorStatus,
index: u32,
oid: Option<Oid>,
},
#[error("authentication failed for {target}")]
Auth { target: SocketAddr },
#[error("malformed response from {target}")]
MalformedResponse { target: SocketAddr },
#[error("walk aborted for {target}: {reason}")]
WalkAborted {
target: SocketAddr,
reason: WalkAbortReason,
},
#[error("configuration error: {0}")]
Config(Box<str>),
#[error("invalid OID: {0}")]
InvalidOid(Box<str>),
}
impl Error {
pub fn boxed(self) -> Box<Self> {
Box::new(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorStatus {
NoError,
TooBig,
NoSuchName,
BadValue,
ReadOnly,
GenErr,
NoAccess,
WrongType,
WrongLength,
WrongEncoding,
WrongValue,
NoCreation,
InconsistentValue,
ResourceUnavailable,
CommitFailed,
UndoFailed,
AuthorizationError,
NotWritable,
InconsistentName,
Unknown(i32),
}
impl ErrorStatus {
pub fn from_i32(value: i32) -> Self {
match value {
0 => Self::NoError,
1 => Self::TooBig,
2 => Self::NoSuchName,
3 => Self::BadValue,
4 => Self::ReadOnly,
5 => Self::GenErr,
6 => Self::NoAccess,
7 => Self::WrongType,
8 => Self::WrongLength,
9 => Self::WrongEncoding,
10 => Self::WrongValue,
11 => Self::NoCreation,
12 => Self::InconsistentValue,
13 => Self::ResourceUnavailable,
14 => Self::CommitFailed,
15 => Self::UndoFailed,
16 => Self::AuthorizationError,
17 => Self::NotWritable,
18 => Self::InconsistentName,
other => {
tracing::warn!(target: "async_snmp::error", { snmp.error_status = other }, "unknown SNMP error status");
Self::Unknown(other)
}
}
}
pub fn as_i32(&self) -> i32 {
match self {
Self::NoError => 0,
Self::TooBig => 1,
Self::NoSuchName => 2,
Self::BadValue => 3,
Self::ReadOnly => 4,
Self::GenErr => 5,
Self::NoAccess => 6,
Self::WrongType => 7,
Self::WrongLength => 8,
Self::WrongEncoding => 9,
Self::WrongValue => 10,
Self::NoCreation => 11,
Self::InconsistentValue => 12,
Self::ResourceUnavailable => 13,
Self::CommitFailed => 14,
Self::UndoFailed => 15,
Self::AuthorizationError => 16,
Self::NotWritable => 17,
Self::InconsistentName => 18,
Self::Unknown(code) => *code,
}
}
pub fn to_v1(&self) -> Self {
match self {
Self::NoError
| Self::TooBig
| Self::NoSuchName
| Self::BadValue
| Self::ReadOnly
| Self::GenErr => *self,
Self::WrongType
| Self::WrongLength
| Self::WrongEncoding
| Self::WrongValue
| Self::InconsistentValue => Self::BadValue,
Self::NoAccess
| Self::NotWritable
| Self::NoCreation
| Self::InconsistentName
| Self::AuthorizationError => Self::NoSuchName,
Self::ResourceUnavailable | Self::CommitFailed | Self::UndoFailed => Self::GenErr,
Self::Unknown(_) => Self::GenErr,
}
}
pub fn as_str(&self) -> Option<&'static str> {
match self {
Self::NoError => Some("noError"),
Self::TooBig => Some("tooBig"),
Self::NoSuchName => Some("noSuchName"),
Self::BadValue => Some("badValue"),
Self::ReadOnly => Some("readOnly"),
Self::GenErr => Some("genErr"),
Self::NoAccess => Some("noAccess"),
Self::WrongType => Some("wrongType"),
Self::WrongLength => Some("wrongLength"),
Self::WrongEncoding => Some("wrongEncoding"),
Self::WrongValue => Some("wrongValue"),
Self::NoCreation => Some("noCreation"),
Self::InconsistentValue => Some("inconsistentValue"),
Self::ResourceUnavailable => Some("resourceUnavailable"),
Self::CommitFailed => Some("commitFailed"),
Self::UndoFailed => Some("undoFailed"),
Self::AuthorizationError => Some("authorizationError"),
Self::NotWritable => Some("notWritable"),
Self::InconsistentName => Some("inconsistentName"),
Self::Unknown(_) => None,
}
}
}
impl std::fmt::Display for ErrorStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.as_str() {
Some(name) => f.write_str(name),
None => write!(f, "unknown({})", self.as_i32()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn walk_abort_reason_is_error() {
let reason = WalkAbortReason::NonIncreasing;
let err: &dyn std::error::Error = &reason;
assert_eq!(err.to_string(), "non-increasing OID");
}
#[test]
fn error_status_to_v1_mapping() {
assert_eq!(ErrorStatus::NoError.to_v1(), ErrorStatus::NoError);
assert_eq!(ErrorStatus::TooBig.to_v1(), ErrorStatus::TooBig);
assert_eq!(ErrorStatus::NoSuchName.to_v1(), ErrorStatus::NoSuchName);
assert_eq!(ErrorStatus::BadValue.to_v1(), ErrorStatus::BadValue);
assert_eq!(ErrorStatus::ReadOnly.to_v1(), ErrorStatus::ReadOnly);
assert_eq!(ErrorStatus::GenErr.to_v1(), ErrorStatus::GenErr);
assert_eq!(ErrorStatus::WrongValue.to_v1(), ErrorStatus::BadValue);
assert_eq!(ErrorStatus::WrongType.to_v1(), ErrorStatus::BadValue);
assert_eq!(ErrorStatus::WrongLength.to_v1(), ErrorStatus::BadValue);
assert_eq!(ErrorStatus::WrongEncoding.to_v1(), ErrorStatus::BadValue);
assert_eq!(
ErrorStatus::InconsistentValue.to_v1(),
ErrorStatus::BadValue
);
assert_eq!(ErrorStatus::NoAccess.to_v1(), ErrorStatus::NoSuchName);
assert_eq!(ErrorStatus::NotWritable.to_v1(), ErrorStatus::NoSuchName);
assert_eq!(ErrorStatus::NoCreation.to_v1(), ErrorStatus::NoSuchName);
assert_eq!(
ErrorStatus::InconsistentName.to_v1(),
ErrorStatus::NoSuchName
);
assert_eq!(
ErrorStatus::AuthorizationError.to_v1(),
ErrorStatus::NoSuchName
);
assert_eq!(
ErrorStatus::ResourceUnavailable.to_v1(),
ErrorStatus::GenErr
);
assert_eq!(ErrorStatus::CommitFailed.to_v1(), ErrorStatus::GenErr);
assert_eq!(ErrorStatus::UndoFailed.to_v1(), ErrorStatus::GenErr);
}
#[test]
fn error_size_budget() {
assert!(
std::mem::size_of::<Error>() <= 128,
"Error size {} exceeds 128-byte budget",
std::mem::size_of::<Error>()
);
assert_eq!(
std::mem::size_of::<Result<()>>(),
std::mem::size_of::<*const ()>(),
"Result<()> should be pointer-sized"
);
}
}