use super::types::{SnmpMessage, SnmpPduKind, SnmpVersion};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseError {
Decode,
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Decode => f.write_str("SNMP decode failed"),
}
}
}
impl std::error::Error for ParseError {}
impl From<ParseError> for crate::Error {
fn from(e: ParseError) -> Self {
use crate::error::{ErrorCode, Module};
let code = match &e {
ParseError::Decode => ErrorCode::Parse,
};
crate::Error::with_code(Module::Snmp, code, e.to_string())
}
}
pub fn parse(payload: &[u8]) -> Result<SnmpMessage, ParseError> {
let (_rem, generic) =
snmp_parser::parse_snmp_generic_message(payload).map_err(|_| ParseError::Decode)?;
match generic {
snmp_parser::SnmpGenericMessage::V1(msg) | snmp_parser::SnmpGenericMessage::V2(msg) => {
decode_v1_v2c(msg).ok_or(ParseError::Decode)
}
snmp_parser::SnmpGenericMessage::V3(msg) => Ok(SnmpMessage {
version: SnmpVersion::V3,
community: String::new(),
pdu_kind: SnmpPduKind::Other,
request_id: msg.header_data.msg_id as i32,
varbind_oids: Vec::new(),
}),
}
}
fn decode_v1_v2c(msg: snmp_parser::SnmpMessage<'_>) -> Option<SnmpMessage> {
let version = match msg.version {
0 => SnmpVersion::V1,
1 => SnmpVersion::V2c,
n => SnmpVersion::Other(n as i64),
};
let community = msg.community.clone();
let (pdu_kind, request_id, varbind_oids) = match &msg.pdu {
snmp_parser::SnmpPdu::Generic(g) => (
map_pdu_type(g.pdu_type),
g.req_id as i32,
g.var.iter().map(|v| v.oid.to_id_string()).collect(),
),
snmp_parser::SnmpPdu::Bulk(b) => (
SnmpPduKind::GetBulkRequest,
b.req_id as i32,
b.var.iter().map(|v| v.oid.to_id_string()).collect(),
),
snmp_parser::SnmpPdu::TrapV1(t) => (
SnmpPduKind::TrapV1,
0,
t.var.iter().map(|v| v.oid.to_id_string()).collect(),
),
};
Some(SnmpMessage {
version,
community,
pdu_kind,
request_id,
varbind_oids,
})
}
fn map_pdu_type(t: snmp_parser::PduType) -> SnmpPduKind {
use snmp_parser::PduType;
match t {
PduType::GetRequest => SnmpPduKind::GetRequest,
PduType::GetNextRequest => SnmpPduKind::GetNextRequest,
PduType::Response => SnmpPduKind::GetResponse,
PduType::SetRequest => SnmpPduKind::SetRequest,
PduType::GetBulkRequest => SnmpPduKind::GetBulkRequest,
PduType::InformRequest => SnmpPduKind::InformRequest,
PduType::TrapV2 => SnmpPduKind::TrapV2,
PduType::Report => SnmpPduKind::Report,
_ => SnmpPduKind::Other,
}
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE_V1_GETREQUEST: &[u8] = &[
0x30, 0x29, 0x02, 0x01, 0x00, 0x04, 0x06, b'p', b'u', b'b', b'l', b'i', b'c', 0xa0, 0x1c, 0x02, 0x04, 0x12, 0x34, 0x56, 0x78, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x30, 0x0c, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x01, 0x00, 0x05, 0x00, ];
#[test]
fn parses_v1_get_request() {
let msg = parse(SAMPLE_V1_GETREQUEST).expect("parse");
assert_eq!(msg.version, SnmpVersion::V1);
assert_eq!(msg.community, "public");
assert_eq!(msg.pdu_kind, SnmpPduKind::GetRequest);
assert_eq!(msg.request_id, 0x12345678);
assert_eq!(msg.varbind_oids.len(), 1);
assert_eq!(msg.varbind_oids[0], "1.3.6.1.2.1.1.1.0");
}
#[test]
fn rejects_truncated_payload() {
assert!(parse(&SAMPLE_V1_GETREQUEST[..5]).is_err());
}
#[test]
fn rejects_non_snmp_payload() {
assert!(parse(b"this is not SNMP").is_err());
}
#[test]
fn pdu_kind_slugs_match() {
assert_eq!(SnmpPduKind::GetRequest.as_str(), "get_request");
assert_eq!(SnmpPduKind::GetResponse.as_str(), "get_response");
assert_eq!(SnmpPduKind::TrapV2.as_str(), "trap_v2");
assert_eq!(SnmpPduKind::GetBulkRequest.as_str(), "get_bulk_request");
assert_eq!(SnmpPduKind::Report.as_str(), "report");
}
}