Skip to main content

async_snmp/message/
mod.rs

1//! SNMP message wrappers.
2//!
3//! Messages encapsulate PDUs with version and authentication information.
4//!
5//! # Message Types
6//!
7//! - [`CommunityMessage`] - V1/V2c messages with community string auth
8//! - [`V3Message`] - V3 messages with USM security
9
10mod community;
11mod v3;
12
13pub use community::{CommunityMessage, CommunityPdu};
14pub(crate) use v3::{MpdFailure, classify_mpd_failure};
15pub use v3::{
16    MsgFlags, MsgGlobalData, ScopedPdu, SecurityLevel, SecurityModel, V3Message, V3MessageData,
17};
18
19use crate::ber::Decoder;
20use crate::error::internal::DecodeErrorKind;
21use crate::error::{Error, Result, UNKNOWN_TARGET};
22use crate::pdu::Pdu;
23use crate::version::Version;
24use bytes::Bytes;
25
26/// Decoded SNMP message (any version).
27///
28/// This enum provides a unified interface for working with SNMP messages
29/// regardless of version. Use [`Message::decode`] to parse incoming data.
30#[derive(Debug)]
31pub enum Message {
32    /// `SNMPv1` or `SNMPv2c` message with community string
33    Community(CommunityMessage),
34    /// `SNMPv3` message with USM security
35    V3(V3Message),
36}
37
38impl Message {
39    /// Get a reference to the PDU.
40    ///
41    /// Returns `None` for encrypted V3 messages or `SNMPv1` Trap messages.
42    pub fn pdu(&self) -> Option<&Pdu> {
43        match self {
44            Message::Community(m) => m.pdu.standard(),
45            Message::V3(m) => m.pdu(),
46        }
47    }
48
49    /// Consume and return the PDU.
50    ///
51    /// Returns `None` for encrypted V3 messages or `SNMPv1` Trap messages.
52    pub fn into_pdu(self) -> Option<Pdu> {
53        match self {
54            Message::Community(m) => match m.pdu {
55                CommunityPdu::Standard(p) => Some(p),
56                CommunityPdu::TrapV1(_) => None,
57            },
58            Message::V3(m) => m.into_pdu(),
59        }
60    }
61
62    /// Get the SNMP version.
63    pub fn version(&self) -> Version {
64        match self {
65            Message::Community(m) => m.version,
66            Message::V3(_) => Version::V3,
67        }
68    }
69
70    /// Decode a message from bytes.
71    ///
72    /// Automatically detects the SNMP version and parses accordingly.
73    pub fn decode(data: Bytes) -> Result<Self> {
74        let mut decoder = Decoder::new(data);
75        let mut seq = decoder.read_sequence()?;
76
77        // Read version to determine message type
78        let version_num = seq.read_integer()?;
79        let version = Version::from_i32(version_num).ok_or_else(|| {
80            tracing::debug!(target: "async_snmp::message", { offset = seq.offset(), kind = %DecodeErrorKind::UnknownVersion(version_num) }, "decode error");
81            Error::MalformedResponse {
82                target: UNKNOWN_TARGET,
83            }
84            .boxed()
85        })?;
86
87        // Decode remainder using version-specific handler
88        match version {
89            Version::V1 | Version::V2c => {
90                let msg = CommunityMessage::decode_from_sequence(&mut seq, version)?;
91                Ok(Message::Community(msg))
92            }
93            Version::V3 => {
94                let msg = V3Message::decode_from_sequence(&mut seq)?;
95                Ok(Message::V3(msg))
96            }
97        }
98    }
99}
100
101/// Peek at the version integer of an encoded SNMP message without decoding
102/// the rest, for version-based dispatch. `target` is only used as the
103/// error's target address.
104pub(crate) fn peek_version(data: Bytes, target: std::net::SocketAddr) -> Result<Version> {
105    let mut decoder = Decoder::with_target(data, target);
106    let mut seq = decoder.read_sequence()?;
107    let version_num = seq.read_integer()?;
108    Version::from_i32(version_num).ok_or_else(|| {
109        tracing::debug!(target: "async_snmp::message", { source = %target, kind = %DecodeErrorKind::UnknownVersion(version_num) }, "unknown SNMP version");
110        Error::MalformedResponse { target }.boxed()
111    })
112}
113
114// Convenience conversions
115impl From<CommunityMessage> for Message {
116    fn from(msg: CommunityMessage) -> Self {
117        Message::Community(msg)
118    }
119}
120
121impl From<V3Message> for Message {
122    fn from(msg: V3Message) -> Self {
123        Message::V3(msg)
124    }
125}