Skip to content

Commit bd3fb57

Browse files
committed
Implement FromBer for all top-level messages
1 parent 2f4478c commit bd3fb57

File tree

4 files changed

+189
-37
lines changed

4 files changed

+189
-37
lines changed

src/generic.rs

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,70 @@ use asn1_rs::{Any, FromBer, Tag};
55
use nom::combinator::map_res;
66
use nom::{Err, IResult};
77

8+
/// An SNMP messsage parser, accepting v1, v2c or v3 messages
9+
///
10+
/// # Examples
11+
///
12+
/// ```rust
13+
/// use snmp_parser::{ScopedPduData, SecurityModel, SnmpGenericMessage};
14+
/// use snmp_parser::asn1_rs::FromBer;
15+
///
16+
/// static SNMPV3_REQ: &[u8] = include_bytes!("../assets/snmpv3_req.bin");
17+
///
18+
/// match SnmpGenericMessage::from_ber(&SNMPV3_REQ) {
19+
/// Ok((_, msg)) => {
20+
/// match msg {
21+
/// SnmpGenericMessage::V1(_) => todo!(),
22+
/// SnmpGenericMessage::V2(_) => todo!(),
23+
/// SnmpGenericMessage::V3(msgv3) => {
24+
/// assert!(msgv3.version == 3);
25+
/// assert!(msgv3.header_data.msg_security_model == SecurityModel::USM);
26+
/// match msgv3.data {
27+
/// ScopedPduData::Plaintext(_pdu) => { },
28+
/// ScopedPduData::Encrypted(_) => (),
29+
/// }
30+
/// }
31+
/// }
32+
/// },
33+
/// Err(e) => panic!("{}", e),
34+
/// }
35+
/// ```
836
#[derive(Debug, PartialEq)]
937
pub enum SnmpGenericMessage<'a> {
38+
/// SNMP Version 1 (SNMPv1) message
1039
V1(SnmpMessage<'a>),
40+
/// SNMP Version 2c (SNMPv2c) message
1141
V2(SnmpMessage<'a>),
42+
/// SNMP Version 3 (SNMPv3) message
1243
V3(SnmpV3Message<'a>),
1344
}
1445

46+
impl<'a> FromBer<'a, SnmpError> for SnmpGenericMessage<'a> {
47+
fn from_ber(bytes: &'a [u8]) -> asn1_rs::ParseResult<'a, Self, SnmpError> {
48+
let (rem, any) = Any::from_ber(bytes).or(Err(Err::Error(SnmpError::InvalidMessage)))?;
49+
if any.tag() != Tag::Sequence {
50+
return Err(Err::Error(SnmpError::InvalidMessage));
51+
}
52+
let (r, version) = u32::from_ber(any.data).map_err(Err::convert)?;
53+
let (_, msg) = match version {
54+
0 => {
55+
let (rem, msg) = parse_snmp_v1_pdu_content(r)?;
56+
(rem, SnmpGenericMessage::V1(msg))
57+
}
58+
1 => {
59+
let (rem, msg) = parse_snmp_v2c_pdu_content(r)?;
60+
(rem, SnmpGenericMessage::V2(msg))
61+
}
62+
3 => {
63+
let (rem, msg) = parse_snmp_v3_pdu_content(r)?;
64+
(rem, SnmpGenericMessage::V3(msg))
65+
}
66+
_ => return Err(Err::Error(SnmpError::InvalidVersion)),
67+
};
68+
Ok((rem, msg))
69+
}
70+
}
71+
1572
fn parse_snmp_v1_pdu_content(i: &[u8]) -> IResult<&[u8], SnmpMessage, SnmpError> {
1673
let (i, community) = parse_ber_octetstring_as_str(i).map_err(Err::convert)?;
1774
let (i, pdu) = parse_snmp_v1_pdu(i)?;
@@ -47,26 +104,9 @@ fn parse_snmp_v3_pdu_content(i: &[u8]) -> IResult<&[u8], SnmpV3Message, SnmpErro
47104
Ok((i, msg))
48105
}
49106

107+
/// Parse an SNMP messsage, accepting v1, v2c or v3 messages
108+
///
109+
/// This function is equivalent to `SnmpGenericMessage::from_ber`
50110
pub fn parse_snmp_generic_message(i: &[u8]) -> IResult<&[u8], SnmpGenericMessage, SnmpError> {
51-
let (rem, any) = Any::from_ber(i).or(Err(Err::Error(SnmpError::InvalidMessage)))?;
52-
if any.tag() != Tag::Sequence {
53-
return Err(Err::Error(SnmpError::InvalidMessage));
54-
}
55-
let (r, version) = u32::from_ber(any.data).map_err(Err::convert)?;
56-
let (_, msg) = match version {
57-
0 => {
58-
let (rem, msg) = parse_snmp_v1_pdu_content(r)?;
59-
(rem, SnmpGenericMessage::V1(msg))
60-
}
61-
1 => {
62-
let (rem, msg) = parse_snmp_v2c_pdu_content(r)?;
63-
(rem, SnmpGenericMessage::V2(msg))
64-
}
65-
3 => {
66-
let (rem, msg) = parse_snmp_v3_pdu_content(r)?;
67-
(rem, SnmpGenericMessage::V3(msg))
68-
}
69-
_ => return Err(Err::Error(SnmpError::InvalidVersion)),
70-
};
71-
Ok((rem, msg))
111+
SnmpGenericMessage::from_ber(i)
72112
}

src/lib.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,28 @@
55
//!
66
//! # SNMP Parser
77
//!
8-
//! A SNMP parser, implemented with the [nom](https://github.com/Geal/nom)
8+
//! An SNMP parser, implemented with the [nom](https://github.com/Geal/nom)
99
//! parser combinator framework.
1010
//!
11+
//! It is written in pure Rust, fast, and makes extensive use of zero-copy.
12+
//! It also aims to be panic-free.
13+
//!
1114
//! The goal of this parser is to implement SNMP messages analysis, for example
1215
//! to use rules from a network IDS.
1316
//!
1417
//! To read a message, different functions must be used depending on the expected message
15-
//! version. The main functions for parsing are [`parse_snmp_v1`](snmp/fn.parse_snmp_v1.html),
18+
//! version.
19+
//! This crate implements the [`asn1_rs::FromBer`] trait, so to parse a message, use the
20+
//! expected object and call function `from_ber`.
21+
//!
22+
//! For example, to parse a SNMP v1 or v2c message (message structure is the same), use
23+
//! [`SnmpMessage`]`::from_ber(input)`.
24+
//! To parse a SNMP v3 message, use [`SnmpV3Message`]`::from_ber(input)`.
25+
//! If you don't know the version of the message and want to parse a generic SNMP message,
26+
//! use [`SnmpGenericMessage`]`::from_ber(input)`.
27+
//!
28+
//! Other methods of parsing (functions) are provided for compatibility:
29+
//! these functions are [`parse_snmp_v1`](snmp/fn.parse_snmp_v1.html),
1630
//! [`parse_snmp_v2c`](snmp/fn.parse_snmp_v2c.html) and
1731
//! [`parse_snmp_v3`](snmpv3/fn.parse_snmp_v3.html).
1832
//! If you don't know the version of the message and want to parse a generic SNMP message,
@@ -50,4 +64,23 @@ pub use snmp::*;
5064
pub use snmpv3::*;
5165

5266
// re-exports to prevent public dependency on asn1_rs
67+
pub use asn1_rs;
5368
pub use asn1_rs::{Oid, OidParseError};
69+
70+
#[cfg(test)]
71+
mod tests {
72+
use asn1_rs::FromBer;
73+
74+
use super::{SnmpGenericMessage, SnmpMessage, SnmpV3Message, SnmpVariable};
75+
76+
#[allow(dead_code)]
77+
fn assert_is_fromber<'a, E, T: FromBer<'a, E>>() {}
78+
79+
#[test]
80+
fn check_traits() {
81+
assert_is_fromber::<_, SnmpVariable>();
82+
assert_is_fromber::<_, SnmpMessage>();
83+
assert_is_fromber::<_, SnmpV3Message>();
84+
assert_is_fromber::<_, SnmpGenericMessage>();
85+
}
86+
}

src/snmp.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,31 @@ pub enum SnmpPdu<'a> {
172172
}
173173

174174
/// An SNMPv1 or SNMPv2c message
175+
///
176+
/// Use the [`FromBer`] trait to parse messages. The method returns the
177+
/// remaining (unparsed) bytes and the object, or an error.
178+
///
179+
/// Function [`parse_snmp_v1`] and [`parse_snmp_v2c`] are also provided, for convenience.
180+
///
181+
/// # Examples
182+
///
183+
/// ```rust
184+
/// use snmp_parser::SnmpMessage;
185+
/// use snmp_parser::asn1_rs::FromBer;
186+
///
187+
/// static SNMPV1_REQ: &[u8] = include_bytes!("../assets/snmpv1_req.bin");
188+
///
189+
/// # fn main() {
190+
/// match SnmpMessage::from_ber(&SNMPV1_REQ) {
191+
/// Ok((_, ref r)) => {
192+
/// assert!(r.version == 0);
193+
/// assert!(r.community == String::from("public"));
194+
/// assert!(r.vars_iter().count() == 1);
195+
/// },
196+
/// Err(e) => panic!("{}", e),
197+
/// }
198+
/// # }
199+
/// ```
175200
#[derive(Debug, PartialEq)]
176201
pub struct SnmpMessage<'a> {
177202
/// Version, as raw-encoded: 0 for SNMPv1, 1 for SNMPv2c
@@ -220,6 +245,31 @@ impl<'a> SnmpMessage<'a> {
220245
}
221246
}
222247

248+
impl<'a> FromBer<'a, SnmpError> for SnmpMessage<'a> {
249+
fn from_ber(bytes: &'a [u8]) -> asn1_rs::ParseResult<'a, Self, SnmpError> {
250+
// parse a SNMP v1 or V2 message
251+
Sequence::from_der_and_then(bytes, |i| {
252+
let (i, version) = u32::from_ber(i).map_err(Err::convert)?;
253+
if version > 1 {
254+
return Err(Err::Error(SnmpError::InvalidVersion));
255+
}
256+
let (i, community) = parse_ber_octetstring_as_str(i).map_err(Err::convert)?;
257+
let (i, pdu) = if version == 0 {
258+
parse_snmp_v1_pdu(i)?
259+
} else {
260+
parse_snmp_v2c_pdu(i)?
261+
};
262+
let msg = SnmpMessage {
263+
version,
264+
community: community.to_string(),
265+
pdu,
266+
};
267+
Ok((i, msg))
268+
})
269+
//.map_err(Err::convert)
270+
}
271+
}
272+
223273
#[derive(Debug, PartialEq)]
224274
pub struct SnmpVariable<'a> {
225275
pub oid: Oid<'a>,

src/snmpv3.rs

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,29 @@ pub enum SecurityParameters<'a> {
5252
}
5353

5454
/// An SNMPv3 message
55+
///
56+
/// # Examples
57+
///
58+
/// ```rust
59+
/// use snmp_parser::{ScopedPduData, SecurityModel, SnmpV3Message};
60+
/// use snmp_parser::asn1_rs::FromBer;
61+
///
62+
/// static SNMPV3_REQ: &[u8] = include_bytes!("../assets/snmpv3_req.bin");
63+
///
64+
/// # fn main() {
65+
/// match SnmpV3Message::from_ber(&SNMPV3_REQ) {
66+
/// Ok((_, ref r)) => {
67+
/// assert!(r.version == 3);
68+
/// assert!(r.header_data.msg_security_model == SecurityModel::USM);
69+
/// match r.data {
70+
/// ScopedPduData::Plaintext(ref _pdu) => { },
71+
/// ScopedPduData::Encrypted(_) => (),
72+
/// }
73+
/// },
74+
/// Err(e) => panic!("{}", e),
75+
/// }
76+
/// # }
77+
/// ```
5578
#[derive(Debug, PartialEq)]
5679
pub struct SnmpV3Message<'a> {
5780
/// Version, as raw-encoded: 3 for SNMPv3
@@ -61,6 +84,25 @@ pub struct SnmpV3Message<'a> {
6184
pub data: ScopedPduData<'a>,
6285
}
6386

87+
impl<'a> FromBer<'a, SnmpError> for SnmpV3Message<'a> {
88+
fn from_ber(bytes: &'a [u8]) -> asn1_rs::ParseResult<'a, Self, SnmpError> {
89+
Sequence::from_der_and_then(bytes, |i| {
90+
let (i, version) = u32::from_ber(i).map_err(Err::convert)?;
91+
let (i, header_data) = parse_snmp_v3_headerdata(i)?;
92+
let (i, secp) = map_res(<&[u8]>::from_ber, |x| parse_secp(x, &header_data))(i)
93+
.map_err(Err::convert)?;
94+
let (i, data) = parse_snmp_v3_data(i, &header_data)?;
95+
let msg = SnmpV3Message {
96+
version,
97+
header_data,
98+
security_params: secp,
99+
data,
100+
};
101+
Ok((i, msg))
102+
})
103+
}
104+
}
105+
64106
#[derive(Clone, Copy, Debug, PartialEq)]
65107
pub struct HeaderData {
66108
pub msg_id: u32,
@@ -168,20 +210,7 @@ pub(crate) fn parse_secp<'a>(
168210
/// # }
169211
/// ```
170212
pub fn parse_snmp_v3(bytes: &[u8]) -> IResult<&[u8], SnmpV3Message, SnmpError> {
171-
Sequence::from_der_and_then(bytes, |i| {
172-
let (i, version) = u32::from_ber(i).map_err(Err::convert)?;
173-
let (i, header_data) = parse_snmp_v3_headerdata(i)?;
174-
let (i, secp) =
175-
map_res(<&[u8]>::from_ber, |x| parse_secp(x, &header_data))(i).map_err(Err::convert)?;
176-
let (i, data) = parse_snmp_v3_data(i, &header_data)?;
177-
let msg = SnmpV3Message {
178-
version,
179-
header_data,
180-
security_params: secp,
181-
data,
182-
};
183-
Ok((i, msg))
184-
})
213+
SnmpV3Message::from_ber(bytes)
185214
}
186215

187216
#[inline]

0 commit comments

Comments
 (0)