Skip to main content

async_snmp/notification/
varbind.rs

1//! Varbind extraction and validation for SNMP notifications.
2//!
3//! Per RFC 3416, notification PDUs have a specific varbind structure:
4//! - First varbind: sysUpTime.0 (1.3.6.1.2.1.1.3.0) with `TimeTicks` value
5//! - Second varbind: snmpTrapOID.0 (1.3.6.1.6.3.1.1.4.1.0) with OID value
6//! - Remaining varbinds: notification-specific data
7
8use crate::error::internal::DecodeErrorKind;
9use crate::error::{Error, Result};
10use crate::oid::Oid;
11use crate::pdu::Pdu;
12use crate::value::Value;
13use crate::varbind::VarBind;
14
15use super::oids;
16
17/// Validation applied to the standard varbind prefix of received notifications.
18///
19/// This policy affects only SNMPv2c and SNMPv3 TrapV2 and Inform PDUs. Both
20/// modes require at least two varbinds whose values are `TimeTicks` followed by
21/// `ObjectIdentifier`.
22#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
23pub enum NotificationVarbindValidation {
24    /// Accept any names for the two mandatory values.
25    ///
26    /// This accommodates devices that send usable notification values under
27    /// non-standard OID names.
28    #[default]
29    Tolerant,
30    /// Require the names and order specified by RFC 3416.
31    ///
32    /// The first name must be `sysUpTime.0` and the second must be
33    /// `snmpTrapOID.0`.
34    Strict,
35}
36
37/// Extract uptime, trap OID, and additional varbinds from a notification PDU.
38pub(crate) fn extract_notification_varbinds(
39    pdu: &Pdu,
40    policy: NotificationVarbindValidation,
41) -> Result<(u32, Oid, Vec<VarBind>)> {
42    let (uptime, trap_oid) = notification_prefix(pdu, policy)?;
43    Ok((uptime, trap_oid, pdu.varbinds[2..].to_vec()))
44}
45
46fn notification_prefix(pdu: &Pdu, policy: NotificationVarbindValidation) -> Result<(u32, Oid)> {
47    if pdu.varbinds.len() < 2 {
48        tracing::debug!(target: "async_snmp::notification", { kind = %DecodeErrorKind::MissingPdu }, "notification has fewer than 2 varbinds");
49        return Err(
50            Error::InvalidMessage("notification has fewer than two varbinds".into()).boxed(),
51        );
52    }
53
54    if policy == NotificationVarbindValidation::Strict && pdu.varbinds[0].oid != oids::sys_uptime()
55    {
56        tracing::warn!(target: "async_snmp::notification", { expected = %oids::sys_uptime(), actual = %pdu.varbinds[0].oid }, "strict mode: first varbind OID is not sysUpTime.0");
57        tracing::debug!(target: "async_snmp::notification", { kind = %DecodeErrorKind::InvalidOid }, "invalid first varbind OID");
58        return Err(Error::InvalidMessage("notification sysUpTime OID is invalid".into()).boxed());
59    }
60    let uptime = match &pdu.varbinds[0].value {
61        Value::TimeTicks(ticks) => *ticks,
62        _ => {
63            tracing::debug!(target: "async_snmp::notification", { kind = %DecodeErrorKind::MissingPdu }, "first varbind is not TimeTicks");
64            return Err(Error::InvalidMessage(
65                "notification sysUpTime value is not TimeTicks".into(),
66            )
67            .boxed());
68        }
69    };
70
71    if policy == NotificationVarbindValidation::Strict
72        && pdu.varbinds[1].oid != oids::snmp_trap_oid()
73    {
74        tracing::warn!(target: "async_snmp::notification", { expected = %oids::snmp_trap_oid(), actual = %pdu.varbinds[1].oid }, "strict mode: second varbind OID is not snmpTrapOID.0");
75        tracing::debug!(target: "async_snmp::notification", { kind = %DecodeErrorKind::InvalidOid }, "invalid second varbind OID");
76        return Err(Error::InvalidMessage("notification trap OID name is invalid".into()).boxed());
77    }
78    let trap_oid = match &pdu.varbinds[1].value {
79        Value::ObjectIdentifier(oid) => oid.clone(),
80        _ => {
81            tracing::debug!(target: "async_snmp::notification", { kind = %DecodeErrorKind::MissingPdu }, "second varbind is not OID");
82            return Err(
83                Error::InvalidMessage("notification trap OID value is not an OID".into()).boxed(),
84            );
85        }
86    };
87
88    Ok((uptime, trap_oid))
89}
90
91/// Validate notification varbinds strictly per RFC 3416.
92///
93/// Returns `true` if the first two varbinds have the correct OIDs:
94/// - First: sysUpTime.0 (1.3.6.1.2.1.1.3.0) with `TimeTicks` value
95/// - Second: snmpTrapOID.0 (1.3.6.1.6.3.1.1.4.1.0) with OID value
96///
97/// This is useful for validating incoming notifications before processing.
98#[must_use]
99pub fn validate_notification_varbinds(pdu: &Pdu) -> bool {
100    notification_prefix(pdu, NotificationVarbindValidation::Strict).is_ok()
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use crate::oid;
107
108    #[test]
109    fn test_extract_notification_varbinds() {
110        let pdu = Pdu::standard(
111            crate::pdu::StandardPduType::TrapV2,
112            1,
113            0,
114            0,
115            vec![
116                VarBind::new(oids::sys_uptime(), Value::TimeTicks(12345)),
117                VarBind::new(
118                    oids::snmp_trap_oid(),
119                    Value::ObjectIdentifier(oids::link_down()),
120                ),
121                VarBind::new(oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1), Value::Integer(1)),
122            ],
123        );
124
125        let (uptime, trap_oid, varbinds) =
126            extract_notification_varbinds(&pdu, NotificationVarbindValidation::Tolerant).unwrap();
127        assert_eq!(uptime, 12345);
128        assert_eq!(trap_oid, oids::link_down());
129        assert_eq!(varbinds.len(), 1);
130    }
131
132    #[test]
133    fn test_extract_notification_varbinds_too_few() {
134        let pdu = Pdu::standard(
135            crate::pdu::StandardPduType::TrapV2,
136            1,
137            0,
138            0,
139            vec![VarBind::new(oids::sys_uptime(), Value::TimeTicks(12345))],
140        );
141
142        for policy in [
143            NotificationVarbindValidation::Tolerant,
144            NotificationVarbindValidation::Strict,
145        ] {
146            assert!(extract_notification_varbinds(&pdu, policy).is_err());
147        }
148    }
149
150    #[test]
151    fn tolerant_accepts_nonstandard_prefix_names_but_strict_rejects_them() {
152        let pdu = Pdu::standard(
153            crate::pdu::StandardPduType::TrapV2,
154            1,
155            0,
156            0,
157            vec![
158                VarBind::new(oid!(1, 2, 3, 4), Value::TimeTicks(12345)),
159                VarBind::new(oid!(1, 2, 3, 5), Value::ObjectIdentifier(oids::link_down())),
160            ],
161        );
162
163        let result =
164            extract_notification_varbinds(&pdu, NotificationVarbindValidation::Tolerant).unwrap();
165        assert_eq!(result.0, 12345);
166        assert_eq!(result.1, oids::link_down());
167        assert!(
168            extract_notification_varbinds(&pdu, NotificationVarbindValidation::Strict).is_err()
169        );
170        assert!(!validate_notification_varbinds(&pdu));
171    }
172
173    #[test]
174    fn strict_rejects_each_incorrect_prefix_name() {
175        for varbinds in [
176            vec![
177                VarBind::new(oid!(1, 2, 3, 4), Value::TimeTicks(12345)),
178                VarBind::new(
179                    oids::snmp_trap_oid(),
180                    Value::ObjectIdentifier(oids::link_down()),
181                ),
182            ],
183            vec![
184                VarBind::new(oids::sys_uptime(), Value::TimeTicks(12345)),
185                VarBind::new(oid!(1, 2, 3, 4), Value::ObjectIdentifier(oids::link_down())),
186            ],
187        ] {
188            let pdu = Pdu::standard(crate::pdu::StandardPduType::TrapV2, 1, 0, 0, varbinds);
189            assert!(
190                extract_notification_varbinds(&pdu, NotificationVarbindValidation::Strict).is_err()
191            );
192        }
193    }
194
195    #[test]
196    fn both_policies_reject_each_incorrect_prefix_value_type() {
197        for varbinds in [
198            vec![
199                VarBind::new(oids::sys_uptime(), Value::Integer(12345)),
200                VarBind::new(
201                    oids::snmp_trap_oid(),
202                    Value::ObjectIdentifier(oids::link_down()),
203                ),
204            ],
205            vec![
206                VarBind::new(oids::sys_uptime(), Value::TimeTicks(12345)),
207                VarBind::new(oids::snmp_trap_oid(), Value::Integer(1)),
208            ],
209        ] {
210            let pdu = Pdu::standard(crate::pdu::StandardPduType::TrapV2, 1, 0, 0, varbinds);
211            for policy in [
212                NotificationVarbindValidation::Tolerant,
213                NotificationVarbindValidation::Strict,
214            ] {
215                assert!(extract_notification_varbinds(&pdu, policy).is_err());
216            }
217        }
218    }
219
220    #[test]
221    fn test_validate_notification_varbinds_valid() {
222        let pdu = Pdu::standard(
223            crate::pdu::StandardPduType::TrapV2,
224            1,
225            0,
226            0,
227            vec![
228                VarBind::new(oids::sys_uptime(), Value::TimeTicks(12345)),
229                VarBind::new(
230                    oids::snmp_trap_oid(),
231                    Value::ObjectIdentifier(oids::link_down()),
232                ),
233            ],
234        );
235
236        assert!(validate_notification_varbinds(&pdu));
237    }
238
239    #[test]
240    fn test_validate_notification_varbinds_wrong_first_oid() {
241        let pdu = Pdu::standard(
242            crate::pdu::StandardPduType::TrapV2,
243            1,
244            0,
245            0,
246            vec![
247                // Wrong OID for first varbind
248                VarBind::new(oid!(1, 2, 3, 4), Value::TimeTicks(12345)),
249                VarBind::new(
250                    oids::snmp_trap_oid(),
251                    Value::ObjectIdentifier(oids::link_down()),
252                ),
253            ],
254        );
255
256        assert!(!validate_notification_varbinds(&pdu));
257    }
258
259    #[test]
260    fn test_validate_notification_varbinds_wrong_second_oid() {
261        let pdu = Pdu::standard(
262            crate::pdu::StandardPduType::TrapV2,
263            1,
264            0,
265            0,
266            vec![
267                VarBind::new(oids::sys_uptime(), Value::TimeTicks(12345)),
268                // Wrong OID for second varbind
269                VarBind::new(oid!(1, 2, 3, 4), Value::ObjectIdentifier(oids::link_down())),
270            ],
271        );
272
273        assert!(!validate_notification_varbinds(&pdu));
274    }
275
276    #[test]
277    fn test_validate_notification_varbinds_wrong_first_type() {
278        let pdu = Pdu::standard(
279            crate::pdu::StandardPduType::TrapV2,
280            1,
281            0,
282            0,
283            vec![
284                // Wrong value type for first varbind (should be TimeTicks)
285                VarBind::new(oids::sys_uptime(), Value::Integer(12345)),
286                VarBind::new(
287                    oids::snmp_trap_oid(),
288                    Value::ObjectIdentifier(oids::link_down()),
289                ),
290            ],
291        );
292
293        assert!(!validate_notification_varbinds(&pdu));
294    }
295
296    #[test]
297    fn test_validate_notification_varbinds_wrong_second_type() {
298        let pdu = Pdu::standard(
299            crate::pdu::StandardPduType::TrapV2,
300            1,
301            0,
302            0,
303            vec![
304                VarBind::new(oids::sys_uptime(), Value::TimeTicks(12345)),
305                // Wrong value type for second varbind (should be OID)
306                VarBind::new(oids::snmp_trap_oid(), Value::Integer(1)),
307            ],
308        );
309
310        assert!(!validate_notification_varbinds(&pdu));
311    }
312
313    #[test]
314    fn test_validate_notification_varbinds_too_few() {
315        let pdu = Pdu::standard(
316            crate::pdu::StandardPduType::TrapV2,
317            1,
318            0,
319            0,
320            vec![
321                VarBind::new(oids::sys_uptime(), Value::TimeTicks(12345)),
322                // Missing second varbind
323            ],
324        );
325
326        assert!(!validate_notification_varbinds(&pdu));
327    }
328}