1use 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#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
23pub enum NotificationVarbindValidation {
24 #[default]
29 Tolerant,
30 Strict,
35}
36
37pub(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#[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 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 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 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 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 ],
324 );
325
326 assert!(!validate_notification_varbinds(&pdu));
327 }
328}