1use crate::ber::{Decoder, EncodeBuf, tag};
6use crate::error::internal::DecodeErrorKind;
7use crate::error::{Error, ErrorStatus, Result};
8use crate::oid::Oid;
9use crate::value::Value;
10use crate::varbind::{VarBind, decode_varbind_list, encode_varbind_list};
11use crate::version::Version;
12
13fn invalid_outbound(reason: impl Into<Box<str>>) -> Box<Error> {
14 Error::InvalidMessage(reason.into()).boxed()
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub(crate) enum PduDirection {
20 Request,
21 Response,
22 Notification,
23}
24
25pub(crate) fn pdu_type_valid_for_version(pdu_type: PduType, version: Version) -> bool {
26 match version {
27 Version::V1 => matches!(
28 pdu_type,
29 PduType::GetRequest | PduType::GetNextRequest | PduType::Response | PduType::SetRequest
30 ),
31 Version::V2c => matches!(
32 pdu_type,
33 PduType::GetRequest
34 | PduType::GetNextRequest
35 | PduType::Response
36 | PduType::SetRequest
37 | PduType::GetBulkRequest
38 | PduType::InformRequest
39 | PduType::TrapV2
40 ),
41 Version::V3 => pdu_type != PduType::TrapV1,
42 }
43}
44
45fn validate_outbound_values(
46 version: Version,
47 direction: PduDirection,
48 varbinds: &[VarBind],
49) -> Result<()> {
50 for varbind in varbinds {
51 varbind.oid.validate_for_wire()?;
52 let value = &varbind.value;
53 if let Value::ObjectIdentifier(oid) = value {
54 oid.validate_for_wire()?;
55 }
56 if matches!(value, Value::Unknown { .. }) {
57 return Err(invalid_outbound(
58 "Value::Unknown cannot be encoded by structured encoders",
59 ));
60 }
61 if matches!(value, Value::UInteger32(_) | Value::Nsap(_)) {
62 return Err(invalid_outbound(
63 "historic receive-only value type cannot be encoded",
64 ));
65 }
66 if version == Version::V1
67 && matches!(
68 value,
69 Value::Counter64(_)
70 | Value::NoSuchObject
71 | Value::NoSuchInstance
72 | Value::EndOfMibView
73 )
74 {
75 return Err(invalid_outbound("value type is not valid in SNMPv1"));
76 }
77 if direction != PduDirection::Response && value.is_exception() {
78 return Err(invalid_outbound(
79 "exception values are only valid in response PDUs",
80 ));
81 }
82 }
83 Ok(())
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
88#[repr(u8)]
89pub enum PduType {
90 GetRequest = 0xA0,
92 GetNextRequest = 0xA1,
94 Response = 0xA2,
96 SetRequest = 0xA3,
98 TrapV1 = 0xA4,
100 GetBulkRequest = 0xA5,
102 InformRequest = 0xA6,
104 TrapV2 = 0xA7,
106 Report = 0xA8,
108}
109
110impl PduType {
111 #[must_use]
113 pub fn from_tag(tag: u8) -> Option<Self> {
114 match tag {
115 0xA0 => Some(Self::GetRequest),
116 0xA1 => Some(Self::GetNextRequest),
117 0xA2 => Some(Self::Response),
118 0xA3 => Some(Self::SetRequest),
119 0xA4 => Some(Self::TrapV1),
120 0xA5 => Some(Self::GetBulkRequest),
121 0xA6 => Some(Self::InformRequest),
122 0xA7 => Some(Self::TrapV2),
123 0xA8 => Some(Self::Report),
124 _ => None,
125 }
126 }
127
128 #[must_use]
130 pub fn tag(self) -> u8 {
131 self as u8
132 }
133}
134
135impl std::fmt::Display for PduType {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 match self {
138 Self::GetRequest => write!(f, "GetRequest"),
139 Self::GetNextRequest => write!(f, "GetNextRequest"),
140 Self::Response => write!(f, "Response"),
141 Self::SetRequest => write!(f, "SetRequest"),
142 Self::TrapV1 => write!(f, "TrapV1"),
143 Self::GetBulkRequest => write!(f, "GetBulkRequest"),
144 Self::InformRequest => write!(f, "InformRequest"),
145 Self::TrapV2 => write!(f, "TrapV2"),
146 Self::Report => write!(f, "Report"),
147 }
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
156pub enum StandardPduType {
157 GetRequest,
159 GetNextRequest,
161 Response,
163 SetRequest,
165 InformRequest,
167 TrapV2,
169 Report,
171}
172
173impl StandardPduType {
174 #[must_use]
176 pub fn pdu_type(self) -> PduType {
177 match self {
178 Self::GetRequest => PduType::GetRequest,
179 Self::GetNextRequest => PduType::GetNextRequest,
180 Self::Response => PduType::Response,
181 Self::SetRequest => PduType::SetRequest,
182 Self::InformRequest => PduType::InformRequest,
183 Self::TrapV2 => PduType::TrapV2,
184 Self::Report => PduType::Report,
185 }
186 }
187}
188
189impl TryFrom<PduType> for StandardPduType {
190 type Error = PduType;
191
192 fn try_from(value: PduType) -> std::result::Result<Self, Self::Error> {
193 match value {
194 PduType::GetRequest => Ok(Self::GetRequest),
195 PduType::GetNextRequest => Ok(Self::GetNextRequest),
196 PduType::Response => Ok(Self::Response),
197 PduType::SetRequest => Ok(Self::SetRequest),
198 PduType::InformRequest => Ok(Self::InformRequest),
199 PduType::TrapV2 => Ok(Self::TrapV2),
200 PduType::Report => Ok(Self::Report),
201 PduType::TrapV1 | PduType::GetBulkRequest => Err(value),
202 }
203 }
204}
205
206#[derive(Debug, Clone, PartialEq, Eq)]
208pub enum PduBody {
209 Standard {
211 pdu_type: StandardPduType,
213 error_status: i32,
215 error_index: i32,
217 },
218 GetBulk {
223 non_repeaters: u32,
225 max_repetitions: u32,
227 },
228}
229
230pub(crate) const MAX_GET_BULK_VALUE: u32 = i32::MAX as u32;
232
233#[derive(Debug, Clone, PartialEq, Eq)]
235pub struct Pdu {
236 pub(crate) request_id: i32,
238 pub(crate) body: PduBody,
240 pub(crate) varbinds: Vec<VarBind>,
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
250pub enum OutboundErrorStatus {
251 NoError,
253 TooBig,
255 NoSuchName,
257 BadValue,
259 ReadOnly,
261 GenErr,
263 NoAccess,
265 WrongType,
267 WrongLength,
269 WrongEncoding,
271 WrongValue,
273 NoCreation,
275 InconsistentValue,
277 ResourceUnavailable,
279 CommitFailed,
281 UndoFailed,
283 AuthorizationError,
285 NotWritable,
287 InconsistentName,
289}
290
291impl OutboundErrorStatus {
292 #[must_use]
294 pub const fn as_i32(self) -> i32 {
295 match self {
296 Self::NoError => 0,
297 Self::TooBig => 1,
298 Self::NoSuchName => 2,
299 Self::BadValue => 3,
300 Self::ReadOnly => 4,
301 Self::GenErr => 5,
302 Self::NoAccess => 6,
303 Self::WrongType => 7,
304 Self::WrongLength => 8,
305 Self::WrongEncoding => 9,
306 Self::WrongValue => 10,
307 Self::NoCreation => 11,
308 Self::InconsistentValue => 12,
309 Self::ResourceUnavailable => 13,
310 Self::CommitFailed => 14,
311 Self::UndoFailed => 15,
312 Self::AuthorizationError => 16,
313 Self::NotWritable => 17,
314 Self::InconsistentName => 18,
315 }
316 }
317}
318
319impl TryFrom<ErrorStatus> for OutboundErrorStatus {
320 type Error = ErrorStatus;
321
322 fn try_from(value: ErrorStatus) -> std::result::Result<Self, Self::Error> {
323 match value {
324 ErrorStatus::NoError => Ok(Self::NoError),
325 ErrorStatus::TooBig => Ok(Self::TooBig),
326 ErrorStatus::NoSuchName => Ok(Self::NoSuchName),
327 ErrorStatus::BadValue => Ok(Self::BadValue),
328 ErrorStatus::ReadOnly => Ok(Self::ReadOnly),
329 ErrorStatus::GenErr => Ok(Self::GenErr),
330 ErrorStatus::NoAccess => Ok(Self::NoAccess),
331 ErrorStatus::WrongType => Ok(Self::WrongType),
332 ErrorStatus::WrongLength => Ok(Self::WrongLength),
333 ErrorStatus::WrongEncoding => Ok(Self::WrongEncoding),
334 ErrorStatus::WrongValue => Ok(Self::WrongValue),
335 ErrorStatus::NoCreation => Ok(Self::NoCreation),
336 ErrorStatus::InconsistentValue => Ok(Self::InconsistentValue),
337 ErrorStatus::ResourceUnavailable => Ok(Self::ResourceUnavailable),
338 ErrorStatus::CommitFailed => Ok(Self::CommitFailed),
339 ErrorStatus::UndoFailed => Ok(Self::UndoFailed),
340 ErrorStatus::AuthorizationError => Ok(Self::AuthorizationError),
341 ErrorStatus::NotWritable => Ok(Self::NotWritable),
342 ErrorStatus::InconsistentName => Ok(Self::InconsistentName),
343 ErrorStatus::Unknown(_) => Err(value),
344 }
345 }
346}
347
348#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
350pub struct ErrorIndex(std::num::NonZeroU32);
351
352impl ErrorIndex {
353 pub fn new(index: u32, varbind_count: usize) -> Result<Self> {
360 let index = std::num::NonZeroU32::new(index)
361 .ok_or_else(|| invalid_outbound("error_index must be nonzero"))?;
362 if index.get() > i32::MAX as u32 {
363 return Err(invalid_outbound("error_index exceeds i32::MAX"));
364 }
365 if usize::try_from(index.get()).map_or(true, |index| index > varbind_count) {
366 return Err(invalid_outbound(
367 "error_index does not identify a variable binding",
368 ));
369 }
370 Ok(Self(index))
371 }
372
373 #[must_use]
375 pub const fn get(self) -> u32 {
376 self.0.get()
377 }
378}
379
380#[derive(Debug, Clone, PartialEq, Eq)]
382pub struct RequestPdu {
383 version: Version,
384 pdu: Pdu,
385}
386
387#[derive(Debug, Clone, PartialEq, Eq)]
389pub struct GetBulkPdu {
390 version: Version,
391 pdu: Pdu,
392}
393
394#[derive(Debug, Clone, PartialEq, Eq)]
396pub struct ResponsePdu {
397 version: Version,
398 pdu: Pdu,
399}
400
401#[derive(Debug, Clone, PartialEq, Eq)]
403pub struct NotificationPdu {
404 version: Version,
405 pdu: Pdu,
406}
407
408#[derive(Debug, Clone, PartialEq, Eq)]
410pub struct TrapV1Notification {
411 pdu: TrapV1Pdu,
412}
413
414#[derive(Debug, Clone, PartialEq, Eq)]
416pub enum OutboundPdu {
417 Request(RequestPdu),
419 GetBulk(GetBulkPdu),
421 Response(ResponsePdu),
423 Notification(NotificationPdu),
425}
426
427impl Pdu {
428 #[must_use]
434 pub fn from_raw_parts(request_id: i32, body: PduBody, varbinds: Vec<VarBind>) -> Self {
435 Self {
436 request_id,
437 body,
438 varbinds,
439 }
440 }
441
442 #[must_use]
444 pub(crate) fn standard(
445 pdu_type: StandardPduType,
446 request_id: i32,
447 error_status: i32,
448 error_index: i32,
449 varbinds: Vec<VarBind>,
450 ) -> Self {
451 Self {
452 request_id,
453 body: PduBody::Standard {
454 pdu_type,
455 error_status,
456 error_index,
457 },
458 varbinds,
459 }
460 }
461
462 #[must_use]
464 pub(crate) fn response(
465 request_id: i32,
466 error_status: i32,
467 error_index: i32,
468 varbinds: Vec<VarBind>,
469 ) -> Self {
470 Self::standard(
471 StandardPduType::Response,
472 request_id,
473 error_status,
474 error_index,
475 varbinds,
476 )
477 }
478
479 #[must_use]
481 pub(crate) fn get_request(request_id: i32, oids: &[Oid]) -> Self {
482 Self::standard(
483 StandardPduType::GetRequest,
484 request_id,
485 0,
486 0,
487 oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
488 )
489 }
490
491 #[cfg(test)]
493 #[must_use]
494 pub(crate) fn get_next_request(request_id: i32, oids: &[Oid]) -> Self {
495 Self::standard(
496 StandardPduType::GetNextRequest,
497 request_id,
498 0,
499 0,
500 oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
501 )
502 }
503
504 #[cfg(test)]
506 #[must_use]
507 pub(crate) fn set_request(request_id: i32, varbinds: Vec<VarBind>) -> Self {
508 Self::standard(StandardPduType::SetRequest, request_id, 0, 0, varbinds)
509 }
510
511 #[must_use]
519 pub(crate) fn trap_v2(
520 request_id: i32,
521 uptime: u32,
522 trap_oid: &Oid,
523 varbinds: Vec<VarBind>,
524 ) -> Self {
525 let mut all_varbinds = Vec::with_capacity(2 + varbinds.len());
526 all_varbinds.push(VarBind::new(
527 crate::notification::oids::sys_uptime(),
528 crate::value::Value::TimeTicks(uptime),
529 ));
530 all_varbinds.push(VarBind::new(
531 crate::notification::oids::snmp_trap_oid(),
532 crate::value::Value::ObjectIdentifier(trap_oid.clone()),
533 ));
534 all_varbinds.extend(varbinds);
535 Self::standard(StandardPduType::TrapV2, request_id, 0, 0, all_varbinds)
536 }
537
538 #[must_use]
543 pub(crate) fn inform_request(
544 request_id: i32,
545 uptime: u32,
546 trap_oid: &Oid,
547 varbinds: Vec<VarBind>,
548 ) -> Self {
549 let mut all_varbinds = Vec::with_capacity(2 + varbinds.len());
550 all_varbinds.push(VarBind::new(
551 crate::notification::oids::sys_uptime(),
552 crate::value::Value::TimeTicks(uptime),
553 ));
554 all_varbinds.push(VarBind::new(
555 crate::notification::oids::snmp_trap_oid(),
556 crate::value::Value::ObjectIdentifier(trap_oid.clone()),
557 ));
558 all_varbinds.extend(varbinds);
559 Self::standard(
560 StandardPduType::InformRequest,
561 request_id,
562 0,
563 0,
564 all_varbinds,
565 )
566 }
567
568 pub(crate) fn get_bulk(
575 request_id: i32,
576 non_repeaters: u32,
577 max_repetitions: u32,
578 varbinds: Vec<VarBind>,
579 ) -> Result<Self> {
580 Self::checked_get_bulk_fields(non_repeaters, max_repetitions)?;
581 Ok(Self {
582 request_id,
583 body: PduBody::GetBulk {
584 non_repeaters,
585 max_repetitions,
586 },
587 varbinds,
588 })
589 }
590
591 pub(crate) fn checked_get_bulk_fields(non_repeaters: u32, max_repetitions: u32) -> Result<()> {
592 Self::get_bulk_fields_for_wire(non_repeaters, max_repetitions)?;
593 Ok(())
594 }
595
596 fn get_bulk_fields_for_wire(non_repeaters: u32, max_repetitions: u32) -> Result<(i32, i32)> {
597 let non_repeaters = i32::try_from(non_repeaters)
598 .map_err(|_| invalid_outbound("GETBULK non_repeaters exceeds i32::MAX"))?;
599 let max_repetitions = i32::try_from(max_repetitions)
600 .map_err(|_| invalid_outbound("GETBULK max_repetitions exceeds i32::MAX"))?;
601 Ok((non_repeaters, max_repetitions))
602 }
603
604 pub(crate) fn outbound_direction(&self) -> PduDirection {
605 match self.pdu_type() {
606 PduType::Response | PduType::Report => PduDirection::Response,
607 PduType::TrapV1 | PduType::TrapV2 => PduDirection::Notification,
608 PduType::GetRequest
609 | PduType::GetNextRequest
610 | PduType::SetRequest
611 | PduType::GetBulkRequest
612 | PduType::InformRequest => PduDirection::Request,
613 }
614 }
615
616 #[cfg(test)]
617 fn inferred_encode_version(&self) -> Version {
618 match self.pdu_type() {
619 PduType::Report => Version::V3,
620 _ => Version::V2c,
621 }
622 }
623
624 pub(crate) fn validate_outbound(
625 &self,
626 version: Version,
627 direction: PduDirection,
628 ) -> Result<()> {
629 let pdu_type = self.pdu_type();
630 if !pdu_type_valid_for_version(pdu_type, version) {
631 return Err(invalid_outbound(format!(
632 "{pdu_type} PDU is not valid for {version:?}"
633 )));
634 }
635 if self.outbound_direction() != direction {
636 return Err(invalid_outbound(format!(
637 "{pdu_type} PDU is not valid in the {direction:?} direction"
638 )));
639 }
640 validate_outbound_values(version, direction, &self.varbinds)?;
641
642 match self.body {
643 PduBody::GetBulk {
644 non_repeaters,
645 max_repetitions,
646 } => {
647 Self::get_bulk_fields_for_wire(non_repeaters, max_repetitions)?;
648 }
649 PduBody::Standard {
650 pdu_type: StandardPduType::Response,
651 error_status,
652 error_index,
653 } => {
654 let maximum_status = if version == Version::V1 { 5 } else { 18 };
655 if !(0..=maximum_status).contains(&error_status) {
656 return Err(invalid_outbound(
657 "Response error_status is not valid for the SNMP version",
658 ));
659 }
660 if error_index < 0
661 || usize::try_from(error_index)
662 .ok()
663 .is_none_or(|index| index > self.varbinds.len())
664 {
665 return Err(invalid_outbound(
666 "Response error_index does not identify a variable binding",
667 ));
668 }
669 if matches!(error_status, 0 | 1 | 15 | 16) {
670 if error_index != 0 {
671 return Err(invalid_outbound(
672 "noError, tooBig, undoFailed, and authorizationError Responses require error_index zero",
673 ));
674 }
675 } else if error_index == 0 {
676 return Err(invalid_outbound(
677 "this Response error_status requires a nonzero error_index",
678 ));
679 }
680 if version != Version::V1
681 && error_status == ErrorStatus::TooBig.as_i32()
682 && !self.varbinds.is_empty()
683 {
684 return Err(invalid_outbound(
685 "SNMPv2c and SNMPv3 tooBig Responses require an empty variable-binding list",
686 ));
687 }
688 }
689 PduBody::Standard {
690 error_status,
691 error_index,
692 ..
693 } => {
694 if error_status != 0 || error_index != 0 {
695 return Err(invalid_outbound(
696 "request and notification PDUs require zero error fields",
697 ));
698 }
699 }
700 }
701
702 match pdu_type {
703 PduType::GetRequest | PduType::GetNextRequest | PduType::GetBulkRequest => {}
704 PduType::SetRequest => {
705 if self
706 .varbinds
707 .iter()
708 .any(|varbind| varbind.value == Value::Null || varbind.value.is_exception())
709 {
710 return Err(invalid_outbound(
711 "SET request variable bindings require concrete values",
712 ));
713 }
714 }
715 PduType::InformRequest | PduType::TrapV2 => {
716 let [uptime, trap_oid, rest @ ..] = self.varbinds.as_slice() else {
717 return Err(invalid_outbound(
718 "notification PDU requires sysUpTime.0 and snmpTrapOID.0",
719 ));
720 };
721 if uptime.oid != crate::notification::oids::sys_uptime()
722 || !matches!(uptime.value, Value::TimeTicks(_))
723 || trap_oid.oid != crate::notification::oids::snmp_trap_oid()
724 || !matches!(trap_oid.value, Value::ObjectIdentifier(_))
725 || rest
726 .iter()
727 .any(|varbind| varbind.value == Value::Null || varbind.value.is_exception())
728 {
729 return Err(invalid_outbound(
730 "notification PDU has an invalid mandatory prefix or value",
731 ));
732 }
733 }
734 PduType::Report => {
735 if !matches!(
736 self.varbinds.as_slice(),
737 [VarBind {
738 value: Value::Counter32(_),
739 ..
740 }]
741 ) {
742 return Err(invalid_outbound(
743 "Report PDU requires exactly one Counter32 variable binding",
744 ));
745 }
746 }
747 PduType::Response | PduType::TrapV1 => {}
748 }
749
750 Ok(())
751 }
752
753 pub(crate) fn encode_for(
754 &self,
755 buf: &mut EncodeBuf,
756 version: Version,
757 direction: PduDirection,
758 ) -> Result<()> {
759 self.validate_outbound(version, direction)?;
760 self.encode_validated(buf)
761 }
762
763 fn encode_validated(&self, buf: &mut EncodeBuf) -> Result<()> {
764 let (pdu_type, first_field, second_field) = match self.body {
765 PduBody::Standard {
766 pdu_type,
767 error_status,
768 error_index,
769 } => (pdu_type.pdu_type(), error_status, error_index),
770 PduBody::GetBulk {
771 non_repeaters,
772 max_repetitions,
773 } => {
774 let (non_repeaters, max_repetitions) =
775 Self::get_bulk_fields_for_wire(non_repeaters, max_repetitions)?;
776 (PduType::GetBulkRequest, non_repeaters, max_repetitions)
777 }
778 };
779
780 buf.push_constructed(pdu_type.tag(), |buf| {
781 encode_varbind_list(buf, &self.varbinds)?;
782 buf.push_integer(second_field);
783 buf.push_integer(first_field);
784 buf.push_integer(self.request_id);
785 Ok(())
786 })
787 }
788
789 #[cfg(test)]
794 pub(crate) fn encode(&self, buf: &mut EncodeBuf) -> Result<()> {
795 self.encode_for(
796 buf,
797 self.inferred_encode_version(),
798 self.outbound_direction(),
799 )
800 }
801
802 pub fn decode(decoder: &mut Decoder) -> Result<Self> {
804 let tag_offset = decoder.local_offset();
805 let tag = decoder.read_tag()?;
806 let pdu_type = PduType::from_tag(tag).ok_or_else(|| {
807 tracing::debug!(target: "async_snmp::pdu", { offset = decoder.offset(), tag = tag, kind = %DecodeErrorKind::UnknownPduType(tag) }, "decode error");
808 decoder.malformed_at(tag_offset, DecodeErrorKind::UnknownPduType(tag))
809 })?;
810
811 if pdu_type == PduType::TrapV1 {
817 tracing::debug!(target: "async_snmp::pdu", { offset = decoder.offset(), tag = tag }, "TrapV1 PDU tag not valid in generic PDU context");
818 return Err(decoder.malformed_at(tag_offset, DecodeErrorKind::UnknownPduType(tag)));
819 }
820
821 let len = decoder.read_length()?;
822 let config = decoder.decode_config();
823 let mut pdu_decoder = decoder.sub_decoder(len)?;
824
825 let request_id = pdu_decoder.read_bounded_integer(i32::MIN, i32::MAX)?;
828 let mut first_field = pdu_decoder.read_bounded_integer(i32::MIN, i32::MAX)?;
829 let mut second_field = pdu_decoder.read_bounded_integer(i32::MIN, i32::MAX)?;
830 let varbinds = decode_varbind_list(&mut pdu_decoder)?;
831 if !pdu_decoder.is_empty() {
832 return Err(pdu_decoder.malformed(DecodeErrorKind::TrailingData {
833 remaining: pdu_decoder.remaining(),
834 }));
835 }
836
837 let body = if pdu_type == PduType::GetBulkRequest {
838 for (field, value) in [
839 ("non_repeaters", &mut first_field),
840 ("max_repetitions", &mut second_field),
841 ] {
842 if *value < 0 {
843 if !config.normalize_negative_get_bulk_fields {
844 return Err(pdu_decoder.malformed(DecodeErrorKind::InvalidValue));
845 }
846 tracing::warn!(target: "async_snmp::pdu", anomaly = "negative_get_bulk_field", direction = "decode", field, value = *value, normalized = 0, "normalized negative GETBULK field");
847 pdu_decoder.record_anomaly(crate::DecodeAnomaly::NegativeGetBulkField {
848 field: match field {
849 "non_repeaters" => crate::GetBulkField::NonRepeaters,
850 "max_repetitions" => crate::GetBulkField::MaxRepetitions,
851 _ => unreachable!("fixed GETBULK field name"),
852 },
853 original: *value,
854 canonical: 0,
855 });
856 *value = 0;
857 }
858 }
859 let non_repeaters = u32::try_from(first_field)
860 .map_err(|_| pdu_decoder.malformed(DecodeErrorKind::InvalidValue))?;
861 let max_repetitions = u32::try_from(second_field)
862 .map_err(|_| pdu_decoder.malformed(DecodeErrorKind::InvalidValue))?;
863 PduBody::GetBulk {
864 non_repeaters,
865 max_repetitions,
866 }
867 } else {
868 let standard_type = StandardPduType::try_from(pdu_type).map_err(|_| {
869 pdu_decoder.malformed(DecodeErrorKind::UnknownPduType(pdu_type.tag()))
870 })?;
871 PduBody::Standard {
872 pdu_type: standard_type,
873 error_status: first_field,
874 error_index: second_field,
875 }
876 };
877
878 Ok(Pdu {
881 request_id,
882 body,
883 varbinds,
884 })
885 }
886
887 #[must_use]
889 pub fn pdu_type(&self) -> PduType {
890 match self.body {
891 PduBody::Standard { pdu_type, .. } => pdu_type.pdu_type(),
892 PduBody::GetBulk { .. } => PduType::GetBulkRequest,
893 }
894 }
895
896 #[must_use]
898 pub const fn request_id(&self) -> i32 {
899 self.request_id
900 }
901
902 #[must_use]
904 pub const fn raw_body(&self) -> &PduBody {
905 &self.body
906 }
907
908 #[must_use]
910 pub fn varbinds(&self) -> &[VarBind] {
911 &self.varbinds
912 }
913
914 #[must_use]
916 pub fn into_varbinds(self) -> Vec<VarBind> {
917 self.varbinds
918 }
919
920 #[must_use]
922 pub fn into_raw_parts(self) -> (i32, PduBody, Vec<VarBind>) {
923 (self.request_id, self.body, self.varbinds)
924 }
925
926 pub(crate) fn set_request_id(&mut self, request_id: i32) {
927 self.request_id = request_id;
928 }
929
930 #[must_use]
932 pub fn error_fields(&self) -> Option<(i32, i32)> {
933 match self.body {
934 PduBody::Standard {
935 error_status,
936 error_index,
937 ..
938 } => Some((error_status, error_index)),
939 PduBody::GetBulk { .. } => None,
940 }
941 }
942
943 #[must_use]
945 pub fn get_bulk_fields(&self) -> Option<(u32, u32)> {
946 match self.body {
947 PduBody::GetBulk {
948 non_repeaters,
949 max_repetitions,
950 } => Some((non_repeaters, max_repetitions)),
951 PduBody::Standard { .. } => None,
952 }
953 }
954
955 #[must_use]
957 pub fn error_status(&self) -> i32 {
958 self.error_fields().map_or(0, |fields| fields.0)
959 }
960
961 #[must_use]
963 pub fn error_index(&self) -> i32 {
964 self.error_fields().map_or(0, |fields| fields.1)
965 }
966
967 #[cfg(test)]
971 pub(crate) fn set_standard_pdu_type(&mut self, value: StandardPduType) -> bool {
972 match &mut self.body {
973 PduBody::Standard { pdu_type, .. } => {
974 *pdu_type = value;
975 true
976 }
977 PduBody::GetBulk { .. } => false,
978 }
979 }
980
981 #[cfg(test)]
985 pub(crate) fn set_error_status(&mut self, value: i32) -> bool {
986 match &mut self.body {
987 PduBody::Standard { error_status, .. } => {
988 *error_status = value;
989 true
990 }
991 PduBody::GetBulk { .. } => false,
992 }
993 }
994
995 #[cfg(test)]
999 pub(crate) fn set_error_index(&mut self, value: i32) -> bool {
1000 match &mut self.body {
1001 PduBody::Standard { error_index, .. } => {
1002 *error_index = value;
1003 true
1004 }
1005 PduBody::GetBulk { .. } => false,
1006 }
1007 }
1008
1009 #[must_use]
1011 pub fn is_error(&self) -> bool {
1012 self.pdu_type() == PduType::Response && self.error_status() != 0
1013 }
1014
1015 #[must_use]
1017 pub fn error_status_enum(&self) -> ErrorStatus {
1018 ErrorStatus::from_i32(self.error_status())
1019 }
1020
1021 pub(crate) fn to_response(&self, version: Version) -> Result<Self> {
1026 Ok(ResponsePdu::success(version, self.request_id, self.varbinds.clone())?.into_raw())
1027 }
1028
1029 #[cfg(any(test, feature = "agent"))]
1031 pub(crate) fn to_error_response(
1032 &self,
1033 version: Version,
1034 error_status: ErrorStatus,
1035 error_index: usize,
1036 ) -> Result<Self> {
1037 let error_status = OutboundErrorStatus::try_from(error_status).map_err(|status| {
1038 invalid_outbound(format!(
1039 "unknown error-status {} is receive-only",
1040 status.as_i32()
1041 ))
1042 })?;
1043 let error_index = if error_index == 0 {
1044 None
1045 } else {
1046 let error_index = u32::try_from(error_index)
1047 .map_err(|_| invalid_outbound("error_index exceeds u32::MAX"))?;
1048 Some(ErrorIndex::new(error_index, self.varbinds.len())?)
1049 };
1050 Ok(ResponsePdu::new(
1051 version,
1052 self.request_id,
1053 error_status,
1054 error_index,
1055 self.varbinds.clone(),
1056 )?
1057 .into_raw())
1058 }
1059
1060 #[must_use]
1062 pub fn is_notification(&self) -> bool {
1063 matches!(
1064 self.pdu_type(),
1065 PduType::TrapV1 | PduType::TrapV2 | PduType::InformRequest
1066 )
1067 }
1068
1069 #[must_use]
1071 pub fn is_confirmed(&self) -> bool {
1072 matches!(
1073 self.pdu_type(),
1074 PduType::GetRequest
1075 | PduType::GetNextRequest
1076 | PduType::GetBulkRequest
1077 | PduType::SetRequest
1078 | PduType::InformRequest
1079 )
1080 }
1081}
1082
1083fn validate_no_receive_only_values(version: Version, varbinds: &[VarBind]) -> Result<()> {
1084 validate_outbound_values(version, PduDirection::Response, varbinds)
1085}
1086
1087fn validate_request_values(kind: StandardPduType, varbinds: &[VarBind]) -> Result<()> {
1088 if kind == StandardPduType::SetRequest
1089 && varbinds
1090 .iter()
1091 .any(|varbind| varbind.value == Value::Null || varbind.value.is_exception())
1092 {
1093 return Err(invalid_outbound(
1094 "SET request variable bindings require concrete values",
1095 ));
1096 }
1097 Ok(())
1098}
1099
1100impl RequestPdu {
1101 fn new(
1102 version: Version,
1103 kind: StandardPduType,
1104 request_id: i32,
1105 varbinds: Vec<VarBind>,
1106 ) -> Result<Self> {
1107 if !matches!(
1108 kind,
1109 StandardPduType::GetRequest
1110 | StandardPduType::GetNextRequest
1111 | StandardPduType::SetRequest
1112 ) {
1113 return Err(invalid_outbound("PDU type is not an ordinary request"));
1114 }
1115 validate_outbound_values(version, PduDirection::Request, &varbinds)?;
1116 validate_request_values(kind, &varbinds)?;
1117 let pdu = Pdu::standard(kind, request_id, 0, 0, varbinds);
1118 pdu.validate_outbound(version, PduDirection::Request)?;
1119 Ok(Self { version, pdu })
1120 }
1121
1122 pub fn get(version: Version, request_id: i32, oids: &[Oid]) -> Result<Self> {
1124 Self::new(
1125 version,
1126 StandardPduType::GetRequest,
1127 request_id,
1128 oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
1129 )
1130 }
1131
1132 pub fn get_next(version: Version, request_id: i32, oids: &[Oid]) -> Result<Self> {
1134 Self::new(
1135 version,
1136 StandardPduType::GetNextRequest,
1137 request_id,
1138 oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
1139 )
1140 }
1141
1142 pub fn set(version: Version, request_id: i32, varbinds: Vec<VarBind>) -> Result<Self> {
1144 Self::new(version, StandardPduType::SetRequest, request_id, varbinds)
1145 }
1146
1147 pub fn try_from_raw(version: Version, pdu: Pdu) -> Result<Self> {
1149 let kind = match &pdu.body {
1150 PduBody::Standard { pdu_type, .. } => *pdu_type,
1151 PduBody::GetBulk { .. } => {
1152 return Err(invalid_outbound("GETBULK requires GetBulkPdu"));
1153 }
1154 };
1155 validate_request_values(kind, &pdu.varbinds)?;
1156 pdu.validate_outbound(version, PduDirection::Request)?;
1157 if !matches!(
1158 kind,
1159 StandardPduType::GetRequest
1160 | StandardPduType::GetNextRequest
1161 | StandardPduType::SetRequest
1162 ) {
1163 return Err(invalid_outbound("PDU type is not an ordinary request"));
1164 }
1165 Ok(Self { version, pdu })
1166 }
1167
1168 pub fn encode(&self, buf: &mut EncodeBuf) -> Result<()> {
1170 self.pdu
1171 .encode_for(buf, self.version, PduDirection::Request)
1172 }
1173
1174 #[must_use]
1176 pub const fn version(&self) -> Version {
1177 self.version
1178 }
1179
1180 #[must_use]
1182 pub const fn request_id(&self) -> i32 {
1183 self.pdu.request_id
1184 }
1185
1186 #[must_use]
1188 pub fn pdu_type(&self) -> PduType {
1189 self.pdu.pdu_type()
1190 }
1191
1192 #[must_use]
1194 pub fn varbinds(&self) -> &[VarBind] {
1195 &self.pdu.varbinds
1196 }
1197
1198 #[must_use]
1200 pub const fn as_raw(&self) -> &Pdu {
1201 &self.pdu
1202 }
1203
1204 #[must_use]
1206 pub fn into_raw(self) -> Pdu {
1207 self.pdu
1208 }
1209
1210 pub fn set_request_id(&mut self, request_id: i32) {
1212 self.pdu.request_id = request_id;
1213 }
1214}
1215
1216impl GetBulkPdu {
1217 pub fn new(
1219 version: Version,
1220 request_id: i32,
1221 non_repeaters: u32,
1222 max_repetitions: u32,
1223 varbinds: Vec<VarBind>,
1224 ) -> Result<Self> {
1225 if version == Version::V1 {
1226 return Err(invalid_outbound("GETBULK is not valid in SNMPv1"));
1227 }
1228 validate_outbound_values(version, PduDirection::Request, &varbinds)?;
1229 let pdu = Pdu::get_bulk(request_id, non_repeaters, max_repetitions, varbinds)?;
1230 pdu.validate_outbound(version, PduDirection::Request)?;
1231 Ok(Self { version, pdu })
1232 }
1233
1234 pub fn try_from_raw(version: Version, pdu: Pdu) -> Result<Self> {
1236 if pdu.pdu_type() != PduType::GetBulkRequest {
1237 return Err(invalid_outbound("PDU is not GETBULK"));
1238 }
1239 pdu.validate_outbound(version, PduDirection::Request)?;
1240 Ok(Self { version, pdu })
1241 }
1242
1243 pub fn encode(&self, buf: &mut EncodeBuf) -> Result<()> {
1245 self.pdu
1246 .encode_for(buf, self.version, PduDirection::Request)
1247 }
1248
1249 #[must_use]
1251 pub const fn version(&self) -> Version {
1252 self.version
1253 }
1254
1255 #[must_use]
1257 pub const fn request_id(&self) -> i32 {
1258 self.pdu.request_id
1259 }
1260
1261 #[must_use]
1263 pub fn parameters(&self) -> (u32, u32) {
1264 self.pdu
1265 .get_bulk_fields()
1266 .expect("GetBulkPdu always has GETBULK fields")
1267 }
1268
1269 #[must_use]
1271 pub fn varbinds(&self) -> &[VarBind] {
1272 &self.pdu.varbinds
1273 }
1274
1275 #[must_use]
1277 pub const fn as_raw(&self) -> &Pdu {
1278 &self.pdu
1279 }
1280
1281 #[must_use]
1283 pub fn into_raw(self) -> Pdu {
1284 self.pdu
1285 }
1286
1287 pub fn set_request_id(&mut self, request_id: i32) {
1289 self.pdu.request_id = request_id;
1290 }
1291}
1292
1293impl ResponsePdu {
1294 pub fn new(
1299 version: Version,
1300 request_id: i32,
1301 status: OutboundErrorStatus,
1302 error_index: Option<ErrorIndex>,
1303 varbinds: Vec<VarBind>,
1304 ) -> Result<Self> {
1305 let status_value = status.as_i32();
1306 if version == Version::V1 && status_value > ErrorStatus::GenErr.as_i32() {
1307 return Err(invalid_outbound(
1308 "error status is not valid in an SNMPv1 response",
1309 ));
1310 }
1311 let requires_no_index = matches!(
1312 status,
1313 OutboundErrorStatus::NoError
1314 | OutboundErrorStatus::TooBig
1315 | OutboundErrorStatus::UndoFailed
1316 | OutboundErrorStatus::AuthorizationError
1317 );
1318 if requires_no_index != error_index.is_none() {
1319 return Err(invalid_outbound(
1320 "error status and error_index combination is invalid",
1321 ));
1322 }
1323 if let Some(index) = error_index
1324 && usize::try_from(index.get()).map_or(true, |index| index > varbinds.len())
1325 {
1326 return Err(invalid_outbound(
1327 "error_index does not identify a variable binding",
1328 ));
1329 }
1330 if version != Version::V1 && status == OutboundErrorStatus::TooBig && !varbinds.is_empty() {
1331 return Err(invalid_outbound(
1332 "SNMPv2c and SNMPv3 tooBig responses require an empty variable-binding list",
1333 ));
1334 }
1335 validate_no_receive_only_values(version, &varbinds)?;
1336 let wire_error_index = match error_index {
1337 Some(index) => i32::try_from(index.get())
1338 .map_err(|_| invalid_outbound("error_index exceeds i32::MAX"))?,
1339 None => 0,
1340 };
1341 let pdu = Pdu::response(request_id, status_value, wire_error_index, varbinds);
1342 pdu.validate_outbound(version, PduDirection::Response)?;
1343 Ok(Self { version, pdu })
1344 }
1345
1346 pub fn success(version: Version, request_id: i32, varbinds: Vec<VarBind>) -> Result<Self> {
1348 Self::new(
1349 version,
1350 request_id,
1351 OutboundErrorStatus::NoError,
1352 None,
1353 varbinds,
1354 )
1355 }
1356
1357 pub fn too_big(version: Version, request_id: i32, varbinds: Vec<VarBind>) -> Result<Self> {
1359 Self::new(
1360 version,
1361 request_id,
1362 OutboundErrorStatus::TooBig,
1363 None,
1364 varbinds,
1365 )
1366 }
1367
1368 pub fn report(request_id: i32, varbinds: Vec<VarBind>) -> Result<Self> {
1370 validate_outbound_values(Version::V3, PduDirection::Response, &varbinds)?;
1371 if varbinds
1372 .iter()
1373 .any(|varbind| varbind.value == Value::Null || varbind.value.is_exception())
1374 {
1375 return Err(invalid_outbound(
1376 "Report variable bindings require concrete values",
1377 ));
1378 }
1379 let pdu = Pdu::standard(StandardPduType::Report, request_id, 0, 0, varbinds);
1380 pdu.validate_outbound(Version::V3, PduDirection::Response)?;
1381 Ok(Self {
1382 version: Version::V3,
1383 pdu,
1384 })
1385 }
1386
1387 pub fn try_from_raw(version: Version, pdu: Pdu) -> Result<Self> {
1389 if !matches!(pdu.pdu_type(), PduType::Response | PduType::Report) {
1390 return Err(invalid_outbound("PDU is not response-class"));
1391 }
1392 if pdu.pdu_type() == PduType::Response {
1393 OutboundErrorStatus::try_from(pdu.error_status_enum()).map_err(|status| {
1394 invalid_outbound(format!(
1395 "unknown error-status {} is receive-only",
1396 status.as_i32()
1397 ))
1398 })?;
1399 }
1400 pdu.validate_outbound(version, PduDirection::Response)?;
1401 Ok(Self { version, pdu })
1402 }
1403
1404 pub fn encode(&self, buf: &mut EncodeBuf) -> Result<()> {
1406 self.pdu
1407 .encode_for(buf, self.version, PduDirection::Response)
1408 }
1409
1410 #[must_use]
1412 pub const fn version(&self) -> Version {
1413 self.version
1414 }
1415
1416 #[must_use]
1418 pub const fn request_id(&self) -> i32 {
1419 self.pdu.request_id
1420 }
1421
1422 #[must_use]
1424 pub fn status(&self) -> OutboundErrorStatus {
1425 OutboundErrorStatus::try_from(self.pdu.error_status_enum())
1426 .expect("ResponsePdu never contains an unknown error status")
1427 }
1428
1429 #[must_use]
1431 pub fn error_index(&self) -> Option<ErrorIndex> {
1432 let index = u32::try_from(self.pdu.error_index()).ok()?;
1433 let index = std::num::NonZeroU32::new(index)?;
1434 Some(ErrorIndex(index))
1435 }
1436
1437 #[must_use]
1439 pub fn varbinds(&self) -> &[VarBind] {
1440 &self.pdu.varbinds
1441 }
1442
1443 #[must_use]
1445 pub const fn as_raw(&self) -> &Pdu {
1446 &self.pdu
1447 }
1448
1449 #[must_use]
1451 pub fn into_raw(self) -> Pdu {
1452 self.pdu
1453 }
1454
1455 pub fn set_request_id(&mut self, request_id: i32) {
1457 self.pdu.request_id = request_id;
1458 }
1459}
1460
1461impl NotificationPdu {
1462 fn new(
1463 version: Version,
1464 kind: StandardPduType,
1465 request_id: i32,
1466 uptime: u32,
1467 trap_oid: &Oid,
1468 varbinds: Vec<VarBind>,
1469 ) -> Result<Self> {
1470 if version == Version::V1 {
1471 return Err(invalid_outbound(
1472 "SNMPv2 notification PDUs are not valid in SNMPv1",
1473 ));
1474 }
1475 if !matches!(
1476 kind,
1477 StandardPduType::TrapV2 | StandardPduType::InformRequest
1478 ) {
1479 return Err(invalid_outbound("PDU type is not a notification"));
1480 }
1481 validate_outbound_values(version, PduDirection::Notification, &varbinds)?;
1482 if varbinds
1483 .iter()
1484 .any(|varbind| varbind.value == Value::Null || varbind.value.is_exception())
1485 {
1486 return Err(invalid_outbound(
1487 "notification variable bindings require concrete values",
1488 ));
1489 }
1490 let pdu = match kind {
1491 StandardPduType::TrapV2 => Pdu::trap_v2(request_id, uptime, trap_oid, varbinds),
1492 StandardPduType::InformRequest => {
1493 Pdu::inform_request(request_id, uptime, trap_oid, varbinds)
1494 }
1495 _ => unreachable!("notification kind checked above"),
1496 };
1497 let direction = pdu.outbound_direction();
1498 pdu.validate_outbound(version, direction)?;
1499 Ok(Self { version, pdu })
1500 }
1501
1502 pub fn trap_v2(
1504 version: Version,
1505 request_id: i32,
1506 uptime: u32,
1507 trap_oid: &Oid,
1508 varbinds: Vec<VarBind>,
1509 ) -> Result<Self> {
1510 Self::new(
1511 version,
1512 StandardPduType::TrapV2,
1513 request_id,
1514 uptime,
1515 trap_oid,
1516 varbinds,
1517 )
1518 }
1519
1520 pub fn inform(
1522 version: Version,
1523 request_id: i32,
1524 uptime: u32,
1525 trap_oid: &Oid,
1526 varbinds: Vec<VarBind>,
1527 ) -> Result<Self> {
1528 Self::new(
1529 version,
1530 StandardPduType::InformRequest,
1531 request_id,
1532 uptime,
1533 trap_oid,
1534 varbinds,
1535 )
1536 }
1537
1538 pub fn try_from_raw(version: Version, pdu: Pdu) -> Result<Self> {
1540 if !matches!(pdu.pdu_type(), PduType::TrapV2 | PduType::InformRequest) {
1541 return Err(invalid_outbound("PDU is not an SNMPv2 notification"));
1542 }
1543 let direction = pdu.outbound_direction();
1544 pdu.validate_outbound(version, direction)?;
1545 Ok(Self { version, pdu })
1546 }
1547
1548 pub fn encode(&self, buf: &mut EncodeBuf) -> Result<()> {
1550 self.pdu
1551 .encode_for(buf, self.version, self.pdu.outbound_direction())
1552 }
1553
1554 #[must_use]
1556 pub const fn version(&self) -> Version {
1557 self.version
1558 }
1559
1560 #[must_use]
1562 pub const fn request_id(&self) -> i32 {
1563 self.pdu.request_id
1564 }
1565
1566 #[must_use]
1568 pub fn pdu_type(&self) -> PduType {
1569 self.pdu.pdu_type()
1570 }
1571
1572 #[must_use]
1574 pub fn varbinds(&self) -> &[VarBind] {
1575 &self.pdu.varbinds
1576 }
1577
1578 #[must_use]
1580 pub fn uptime(&self) -> u32 {
1581 match self.pdu.varbinds.first().map(|varbind| &varbind.value) {
1582 Some(Value::TimeTicks(value)) => *value,
1583 _ => unreachable!("NotificationPdu always has a TimeTicks prefix"),
1584 }
1585 }
1586
1587 #[must_use]
1589 pub fn trap_oid(&self) -> &Oid {
1590 match self.pdu.varbinds.get(1).map(|varbind| &varbind.value) {
1591 Some(Value::ObjectIdentifier(oid)) => oid,
1592 _ => unreachable!("NotificationPdu always has an OBJECT IDENTIFIER prefix"),
1593 }
1594 }
1595
1596 pub fn to_v1_trap(&self, default_addr: [u8; 4]) -> Result<TrapV1Notification> {
1607 use crate::notification::oids;
1608
1609 if self
1610 .pdu
1611 .varbinds
1612 .iter()
1613 .any(|varbind| matches!(varbind.value, Value::Counter64(_)))
1614 {
1615 return Err(invalid_outbound(
1616 "Counter64 notification values cannot be represented in SNMPv1",
1617 ));
1618 }
1619
1620 let trap_oid = self.trap_oid();
1621 let snmp_traps_prefix = oids::snmp_traps();
1622 let (generic_trap, specific_trap, enterprise) = if trap_oid.starts_with(&snmp_traps_prefix)
1623 && trap_oid.len() == snmp_traps_prefix.len() + 1
1624 && (1..=6).contains(&trap_oid.arcs()[trap_oid.len() - 1])
1625 {
1626 let last_arc = trap_oid.arcs()[trap_oid.len() - 1];
1627 let enterprise = self.pdu.varbinds[2..]
1628 .iter()
1629 .find(|varbind| varbind.oid == oids::snmp_trap_enterprise())
1630 .and_then(|varbind| match &varbind.value {
1631 Value::ObjectIdentifier(oid) => Some(oid.clone()),
1632 _ => None,
1633 })
1634 .unwrap_or_else(|| snmp_traps_prefix.clone());
1635 (GenericTrap::from_i32((last_arc - 1) as i32), 0, enterprise)
1636 } else if trap_oid.len() >= 2 {
1637 let arcs = trap_oid.arcs();
1638 let specific_trap = i32::try_from(arcs[arcs.len() - 1])
1639 .map_err(|_| invalid_outbound("trap OID specific value exceeds Integer32"))?;
1640 let enterprise = if arcs[arcs.len() - 2] == 0 {
1641 Oid::from_slice(&arcs[..arcs.len() - 2])
1642 } else {
1643 Oid::from_slice(&arcs[..arcs.len() - 1])
1644 };
1645 (GenericTrap::EnterpriseSpecific, specific_trap, enterprise)
1646 } else {
1647 return Err(invalid_outbound(
1648 "trap OID is too short for SNMPv1 conversion",
1649 ));
1650 };
1651
1652 let agent_addr = self.pdu.varbinds[2..]
1653 .iter()
1654 .find(|varbind| varbind.oid == oids::snmp_trap_address())
1655 .and_then(|varbind| match varbind.value {
1656 Value::IpAddress(address) => Some(address),
1657 _ => None,
1658 })
1659 .unwrap_or(default_addr);
1660
1661 TrapV1Notification::new(
1662 enterprise,
1663 agent_addr,
1664 generic_trap,
1665 specific_trap,
1666 self.uptime(),
1667 self.pdu.varbinds[2..].to_vec(),
1668 )
1669 }
1670
1671 #[must_use]
1673 pub const fn as_raw(&self) -> &Pdu {
1674 &self.pdu
1675 }
1676
1677 #[must_use]
1679 pub fn into_raw(self) -> Pdu {
1680 self.pdu
1681 }
1682
1683 pub fn set_request_id(&mut self, request_id: i32) {
1685 self.pdu.request_id = request_id;
1686 }
1687}
1688
1689impl From<RequestPdu> for Pdu {
1690 fn from(value: RequestPdu) -> Self {
1691 value.into_raw()
1692 }
1693}
1694
1695impl From<GetBulkPdu> for Pdu {
1696 fn from(value: GetBulkPdu) -> Self {
1697 value.into_raw()
1698 }
1699}
1700
1701impl From<ResponsePdu> for Pdu {
1702 fn from(value: ResponsePdu) -> Self {
1703 value.into_raw()
1704 }
1705}
1706
1707impl From<NotificationPdu> for Pdu {
1708 fn from(value: NotificationPdu) -> Self {
1709 value.into_raw()
1710 }
1711}
1712
1713impl OutboundPdu {
1714 pub fn try_from_raw(version: Version, pdu: Pdu) -> Result<Self> {
1716 match pdu.pdu_type() {
1717 PduType::GetRequest | PduType::GetNextRequest | PduType::SetRequest => {
1718 Ok(Self::Request(RequestPdu::try_from_raw(version, pdu)?))
1719 }
1720 PduType::GetBulkRequest => Ok(Self::GetBulk(GetBulkPdu::try_from_raw(version, pdu)?)),
1721 PduType::Response | PduType::Report => {
1722 Ok(Self::Response(ResponsePdu::try_from_raw(version, pdu)?))
1723 }
1724 PduType::InformRequest | PduType::TrapV2 => Ok(Self::Notification(
1725 NotificationPdu::try_from_raw(version, pdu)?,
1726 )),
1727 PduType::TrapV1 => Err(invalid_outbound("SNMPv1 Trap uses TrapV1Notification")),
1728 }
1729 }
1730
1731 #[must_use]
1733 pub fn as_raw(&self) -> &Pdu {
1734 match self {
1735 Self::Request(pdu) => pdu.as_raw(),
1736 Self::GetBulk(pdu) => pdu.as_raw(),
1737 Self::Response(pdu) => pdu.as_raw(),
1738 Self::Notification(pdu) => pdu.as_raw(),
1739 }
1740 }
1741
1742 #[must_use]
1744 pub fn into_raw(self) -> Pdu {
1745 match self {
1746 Self::Request(pdu) => pdu.into_raw(),
1747 Self::GetBulk(pdu) => pdu.into_raw(),
1748 Self::Response(pdu) => pdu.into_raw(),
1749 Self::Notification(pdu) => pdu.into_raw(),
1750 }
1751 }
1752
1753 pub fn encode(&self, buf: &mut EncodeBuf) -> Result<()> {
1755 match self {
1756 Self::Request(pdu) => pdu.encode(buf),
1757 Self::GetBulk(pdu) => pdu.encode(buf),
1758 Self::Response(pdu) => pdu.encode(buf),
1759 Self::Notification(pdu) => pdu.encode(buf),
1760 }
1761 }
1762}
1763
1764impl From<OutboundPdu> for Pdu {
1765 fn from(value: OutboundPdu) -> Self {
1766 value.into_raw()
1767 }
1768}
1769
1770#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1772pub enum GenericTrap {
1773 ColdStart,
1775 WarmStart,
1777 LinkDown,
1779 LinkUp,
1781 AuthenticationFailure,
1783 EgpNeighborLoss,
1785 EnterpriseSpecific,
1787 Unknown(i32),
1789}
1790
1791impl std::fmt::Display for GenericTrap {
1792 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1793 match self {
1794 Self::ColdStart => write!(f, "coldStart"),
1795 Self::WarmStart => write!(f, "warmStart"),
1796 Self::LinkDown => write!(f, "linkDown"),
1797 Self::LinkUp => write!(f, "linkUp"),
1798 Self::AuthenticationFailure => write!(f, "authenticationFailure"),
1799 Self::EgpNeighborLoss => write!(f, "egpNeighborLoss"),
1800 Self::EnterpriseSpecific => write!(f, "enterpriseSpecific"),
1801 Self::Unknown(v) => write!(f, "unknown({v})"),
1802 }
1803 }
1804}
1805
1806impl GenericTrap {
1807 #[must_use]
1809 pub fn from_i32(v: i32) -> Self {
1810 match v {
1811 0 => Self::ColdStart,
1812 1 => Self::WarmStart,
1813 2 => Self::LinkDown,
1814 3 => Self::LinkUp,
1815 4 => Self::AuthenticationFailure,
1816 5 => Self::EgpNeighborLoss,
1817 6 => Self::EnterpriseSpecific,
1818 _ => Self::Unknown(v),
1819 }
1820 }
1821
1822 #[must_use]
1824 pub fn as_i32(self) -> i32 {
1825 match self {
1826 Self::ColdStart => 0,
1827 Self::WarmStart => 1,
1828 Self::LinkDown => 2,
1829 Self::LinkUp => 3,
1830 Self::AuthenticationFailure => 4,
1831 Self::EgpNeighborLoss => 5,
1832 Self::EnterpriseSpecific => 6,
1833 Self::Unknown(v) => v,
1834 }
1835 }
1836}
1837
1838#[derive(Debug, Clone, PartialEq, Eq)]
1843pub struct TrapV1Pdu {
1844 pub(crate) enterprise: Oid,
1846 pub(crate) agent_addr: [u8; 4],
1848 pub(crate) generic_trap: GenericTrap,
1850 pub(crate) specific_trap: i32,
1852 pub(crate) time_stamp: u32,
1854 pub(crate) varbinds: Vec<VarBind>,
1856}
1857
1858impl TrapV1Pdu {
1859 #[must_use]
1861 pub fn from_raw_parts(
1862 enterprise: Oid,
1863 agent_addr: [u8; 4],
1864 generic_trap: GenericTrap,
1865 specific_trap: i32,
1866 time_stamp: u32,
1867 varbinds: Vec<VarBind>,
1868 ) -> Self {
1869 Self::new(
1870 enterprise,
1871 agent_addr,
1872 generic_trap,
1873 specific_trap,
1874 time_stamp,
1875 varbinds,
1876 )
1877 }
1878
1879 #[must_use]
1881 pub(crate) fn new(
1882 enterprise: Oid,
1883 agent_addr: [u8; 4],
1884 generic_trap: GenericTrap,
1885 specific_trap: i32,
1886 time_stamp: u32,
1887 varbinds: Vec<VarBind>,
1888 ) -> Self {
1889 Self {
1890 enterprise,
1891 agent_addr,
1892 generic_trap,
1893 specific_trap,
1894 time_stamp,
1895 varbinds,
1896 }
1897 }
1898
1899 #[must_use]
1901 pub fn enterprise(&self) -> &Oid {
1902 &self.enterprise
1903 }
1904
1905 #[must_use]
1907 pub const fn agent_addr(&self) -> [u8; 4] {
1908 self.agent_addr
1909 }
1910
1911 #[must_use]
1913 pub const fn generic_trap(&self) -> GenericTrap {
1914 self.generic_trap
1915 }
1916
1917 #[must_use]
1919 pub const fn specific_trap(&self) -> i32 {
1920 self.specific_trap
1921 }
1922
1923 #[must_use]
1925 pub const fn time_stamp(&self) -> u32 {
1926 self.time_stamp
1927 }
1928
1929 #[must_use]
1931 pub fn varbinds(&self) -> &[VarBind] {
1932 &self.varbinds
1933 }
1934
1935 #[must_use]
1937 pub fn is_enterprise_specific(&self) -> bool {
1938 self.generic_trap == GenericTrap::EnterpriseSpecific
1939 }
1940
1941 pub fn v2_trap_oid(&self) -> crate::Result<Oid> {
1996 if self.is_enterprise_specific() {
1997 if self.specific_trap < 0 {
1998 return Err(Error::InvalidOid("specific_trap cannot be negative".into()).boxed());
1999 }
2000 let mut arcs: Vec<u32> = self.enterprise.arcs().to_vec();
2001 arcs.push(0);
2002 arcs.push(self.specific_trap as u32);
2003 let oid = Oid::new(arcs);
2004 oid.validate_for_wire()?;
2005 Ok(oid)
2006 } else {
2007 let raw = self.generic_trap.as_i32();
2008 if raw < 0 {
2009 return Err(Error::InvalidOid("generic_trap cannot be negative".into()).boxed());
2010 }
2011 if raw == i32::MAX {
2012 return Err(Error::InvalidOid("generic_trap overflow".into()).boxed());
2013 }
2014 let trap_num = raw + 1;
2015 Ok(crate::oid!(1, 3, 6, 1, 6, 3, 1, 1, 5).child(trap_num as u32))
2016 }
2017 }
2018
2019 pub fn to_v2_pdu(&self) -> crate::Result<NotificationPdu> {
2036 let trap_oid = self.v2_trap_oid()?;
2037 NotificationPdu::trap_v2(
2038 Version::V2c,
2039 0,
2040 self.time_stamp,
2041 &trap_oid,
2042 self.varbinds.clone(),
2043 )
2044 }
2045
2046 pub(crate) fn validate_outbound(&self) -> Result<()> {
2047 self.enterprise.validate_for_wire()?;
2048 if matches!(self.generic_trap, GenericTrap::Unknown(_)) {
2049 return Err(invalid_outbound(
2050 "unknown generic-trap values are receive-only",
2051 ));
2052 }
2053 validate_outbound_values(Version::V1, PduDirection::Notification, &self.varbinds)
2054 }
2055
2056 pub(crate) fn encode(&self, buf: &mut EncodeBuf) -> Result<()> {
2058 self.validate_outbound()?;
2059 buf.push_constructed(tag::pdu::TRAP_V1, |buf| {
2060 encode_varbind_list(buf, &self.varbinds)?;
2061 buf.push_unsigned32(tag::application::TIMETICKS, self.time_stamp);
2062 buf.push_integer(self.specific_trap);
2063 buf.push_integer(self.generic_trap.as_i32());
2064 buf.push_bytes(&self.agent_addr);
2067 buf.push_length(4)?;
2068 buf.push_tag(tag::application::IP_ADDRESS);
2069 buf.push_oid(&self.enterprise)
2070 })
2071 }
2072
2073 pub fn decode(decoder: &mut Decoder) -> Result<Self> {
2075 let mut pdu = decoder.read_constructed(tag::pdu::TRAP_V1)?;
2076
2077 let enterprise = pdu.read_oid()?;
2079
2080 let agent_tag = pdu.read_tag()?;
2082 if agent_tag != tag::application::IP_ADDRESS {
2083 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), expected = 0x40_u8, actual = agent_tag, kind = %DecodeErrorKind::UnexpectedTag {
2084 expected: 0x40,
2085 actual: agent_tag,
2086 } }, "decode error");
2087 return Err(pdu.malformed_at(
2088 pdu.local_offset() - 1,
2089 DecodeErrorKind::UnexpectedTag {
2090 expected: tag::application::IP_ADDRESS,
2091 actual: agent_tag,
2092 },
2093 ));
2094 }
2095 let agent_len = pdu.read_length()?;
2096 if agent_len != 4 {
2097 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), length = agent_len, kind = %DecodeErrorKind::InvalidIpAddressLength { length: agent_len } }, "decode error");
2098 return Err(
2099 pdu.malformed(DecodeErrorKind::InvalidIpAddressLength { length: agent_len })
2100 );
2101 }
2102 let agent_bytes = pdu.read_bytes(4)?;
2103 let agent_addr = [
2104 agent_bytes[0],
2105 agent_bytes[1],
2106 agent_bytes[2],
2107 agent_bytes[3],
2108 ];
2109
2110 let generic_trap = GenericTrap::from_i32(pdu.read_bounded_integer(i32::MIN, i32::MAX)?);
2113 let specific_trap = pdu.read_bounded_integer(i32::MIN, i32::MAX)?;
2114
2115 let ts_tag = pdu.read_tag()?;
2117 if ts_tag != tag::application::TIMETICKS {
2118 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), expected = 0x43_u8, actual = ts_tag, kind = %DecodeErrorKind::UnexpectedTag {
2119 expected: 0x43,
2120 actual: ts_tag,
2121 } }, "decode error");
2122 return Err(pdu.malformed_at(
2123 pdu.local_offset() - 1,
2124 DecodeErrorKind::UnexpectedTag {
2125 expected: tag::application::TIMETICKS,
2126 actual: ts_tag,
2127 },
2128 ));
2129 }
2130 let ts_len = pdu.read_length()?;
2131 let time_stamp = pdu.read_bounded_unsigned32_value(ts_len)?;
2132
2133 let varbinds = decode_varbind_list(&mut pdu)?;
2135 if !pdu.is_empty() {
2136 return Err(pdu.malformed(DecodeErrorKind::TrailingData {
2137 remaining: pdu.remaining(),
2138 }));
2139 }
2140
2141 Ok(TrapV1Pdu {
2142 enterprise,
2143 agent_addr,
2144 generic_trap,
2145 specific_trap,
2146 time_stamp,
2147 varbinds,
2148 })
2149 }
2150}
2151
2152impl TrapV1Notification {
2153 pub fn new(
2162 enterprise: Oid,
2163 agent_addr: [u8; 4],
2164 generic_trap: GenericTrap,
2165 specific_trap: i32,
2166 time_stamp: u32,
2167 varbinds: Vec<VarBind>,
2168 ) -> Result<Self> {
2169 if matches!(generic_trap, GenericTrap::Unknown(_)) {
2170 return Err(invalid_outbound(
2171 "unknown generic-trap values are receive-only",
2172 ));
2173 }
2174 let pdu = TrapV1Pdu::new(
2175 enterprise,
2176 agent_addr,
2177 generic_trap,
2178 specific_trap,
2179 time_stamp,
2180 varbinds,
2181 );
2182 pdu.validate_outbound()?;
2183 Ok(Self { pdu })
2184 }
2185
2186 pub fn try_from_raw(pdu: TrapV1Pdu) -> Result<Self> {
2188 pdu.validate_outbound()?;
2189 Ok(Self { pdu })
2190 }
2191
2192 pub fn encode(&self, buf: &mut EncodeBuf) -> Result<()> {
2194 self.pdu.encode(buf)
2195 }
2196
2197 #[must_use]
2199 pub fn enterprise(&self) -> &Oid {
2200 &self.pdu.enterprise
2201 }
2202
2203 #[must_use]
2205 pub const fn agent_addr(&self) -> [u8; 4] {
2206 self.pdu.agent_addr
2207 }
2208
2209 #[must_use]
2211 pub const fn generic_trap(&self) -> GenericTrap {
2212 self.pdu.generic_trap
2213 }
2214
2215 #[must_use]
2217 pub const fn specific_trap(&self) -> i32 {
2218 self.pdu.specific_trap
2219 }
2220
2221 #[must_use]
2223 pub const fn time_stamp(&self) -> u32 {
2224 self.pdu.time_stamp
2225 }
2226
2227 #[must_use]
2229 pub fn varbinds(&self) -> &[VarBind] {
2230 &self.pdu.varbinds
2231 }
2232
2233 #[must_use]
2235 pub const fn as_raw(&self) -> &TrapV1Pdu {
2236 &self.pdu
2237 }
2238
2239 #[must_use]
2241 pub fn into_raw(self) -> TrapV1Pdu {
2242 self.pdu
2243 }
2244}
2245
2246impl From<TrapV1Notification> for TrapV1Pdu {
2247 fn from(value: TrapV1Notification) -> Self {
2248 value.into_raw()
2249 }
2250}
2251
2252#[cfg(test)]
2253mod tests {
2254 use super::*;
2255 use crate::DecodeConfig;
2256 use crate::oid;
2257
2258 fn compatibility_without_negative_bulk_normalization() -> DecodeConfig {
2259 DecodeConfig {
2260 normalize_negative_get_bulk_fields: false,
2261 ..DecodeConfig::DEFAULT
2262 }
2263 }
2264
2265 fn to_v1_trap(pdu: &Pdu, address: [u8; 4]) -> Result<TrapV1Pdu> {
2266 Ok(NotificationPdu::try_from_raw(Version::V2c, pdu.clone())?
2267 .to_v1_trap(address)?
2268 .into_raw())
2269 }
2270
2271 struct RawPdu {
2276 pdu_type: u8,
2277 request_id: i32,
2278 error_status: i32,
2279 error_index: i32,
2280 varbinds: Vec<VarBind>,
2281 }
2282
2283 impl RawPdu {
2284 fn response(
2285 request_id: i32,
2286 error_status: i32,
2287 error_index: i32,
2288 varbinds: Vec<VarBind>,
2289 ) -> Self {
2290 Self {
2291 pdu_type: PduType::Response.tag(),
2292 request_id,
2293 error_status,
2294 error_index,
2295 varbinds,
2296 }
2297 }
2298
2299 fn encode(&self) -> bytes::Bytes {
2300 let mut buf = EncodeBuf::new();
2301 buf.push_constructed(self.pdu_type, |buf| {
2302 encode_varbind_list(buf, &self.varbinds).unwrap();
2303 buf.push_integer(self.error_index);
2304 buf.push_integer(self.error_status);
2305 buf.push_integer(self.request_id);
2306 Ok(())
2307 })
2308 .unwrap();
2309 buf.finish()
2310 }
2311 }
2312
2313 struct RawBulkWirePdu {
2315 request_id: i32,
2316 non_repeaters: i32,
2317 max_repetitions: i32,
2318 varbinds: Vec<VarBind>,
2319 }
2320
2321 impl RawBulkWirePdu {
2322 fn new(
2323 request_id: i32,
2324 non_repeaters: i32,
2325 max_repetitions: i32,
2326 varbinds: Vec<VarBind>,
2327 ) -> Self {
2328 Self {
2329 request_id,
2330 non_repeaters,
2331 max_repetitions,
2332 varbinds,
2333 }
2334 }
2335
2336 fn encode(&self) -> bytes::Bytes {
2337 let mut buf = EncodeBuf::new();
2338 buf.push_constructed(tag::pdu::GET_BULK_REQUEST, |buf| {
2339 encode_varbind_list(buf, &self.varbinds).unwrap();
2340 buf.push_integer(self.max_repetitions);
2341 buf.push_integer(self.non_repeaters);
2342 buf.push_integer(self.request_id);
2343 Ok(())
2344 })
2345 .unwrap();
2346 buf.finish()
2347 }
2348 }
2349
2350 #[test]
2351 fn validated_requests_cover_versions_types_accessors_and_raw_conversion() {
2352 let name = oid!(1, 3, 6, 1, 2, 1, 1, 1, 0);
2353 for version in [Version::V1, Version::V2c, Version::V3] {
2354 for mut request in [
2355 RequestPdu::get(version, 7, std::slice::from_ref(&name)).unwrap(),
2356 RequestPdu::get_next(version, 7, std::slice::from_ref(&name)).unwrap(),
2357 RequestPdu::set(
2358 version,
2359 7,
2360 vec![VarBind::new(name.clone(), Value::Integer(4))],
2361 )
2362 .unwrap(),
2363 ] {
2364 assert_eq!(request.version(), version);
2365 assert_eq!(request.request_id(), 7);
2366 assert_eq!(request.varbinds().len(), 1);
2367 request.set_request_id(9);
2368 assert_eq!(request.request_id(), 9);
2369
2370 let raw = request.into_raw();
2371 let validated = RequestPdu::try_from_raw(version, raw.clone()).unwrap();
2372 assert_eq!(validated.as_raw(), &raw);
2373
2374 let mut buf = EncodeBuf::new();
2375 validated.encode(&mut buf).unwrap();
2376 let mut decoder = Decoder::new(buf.finish());
2377 assert_eq!(Pdu::decode(&mut decoder).unwrap(), raw);
2378 }
2379
2380 for kind in [StandardPduType::GetRequest, StandardPduType::GetNextRequest] {
2384 let raw = Pdu::standard(
2385 kind,
2386 11,
2387 0,
2388 0,
2389 vec![VarBind::new(name.clone(), Value::Integer(37))],
2390 );
2391 let request = RequestPdu::try_from_raw(version, raw.clone()).unwrap();
2392 let mut buf = EncodeBuf::new();
2393 request.encode(&mut buf).unwrap();
2394 let mut decoder = Decoder::new(buf.finish());
2395 assert_eq!(Pdu::decode(&mut decoder).unwrap(), raw);
2396 }
2397 }
2398
2399 assert!(RequestPdu::set(Version::V2c, 1, vec![VarBind::null(name.clone())]).is_err());
2400 assert!(
2401 RequestPdu::set(
2402 Version::V2c,
2403 1,
2404 vec![VarBind::new(name.clone(), Value::NoSuchObject)]
2405 )
2406 .is_err()
2407 );
2408 assert!(
2409 RequestPdu::set(
2410 Version::V1,
2411 1,
2412 vec![VarBind::new(name, Value::Counter64(1))]
2413 )
2414 .is_err()
2415 );
2416 }
2417
2418 #[test]
2419 fn validated_get_bulk_checks_version_ranges_values_and_mutation() {
2420 let name = oid!(1, 3, 6, 1);
2421 assert!(GetBulkPdu::new(Version::V1, 1, 0, 1, vec![VarBind::null(name.clone())]).is_err());
2422 assert!(
2423 GetBulkPdu::new(
2424 Version::V2c,
2425 1,
2426 MAX_GET_BULK_VALUE + 1,
2427 1,
2428 vec![VarBind::null(name.clone())]
2429 )
2430 .is_err()
2431 );
2432 for version in [Version::V2c, Version::V3] {
2433 let bulk = GetBulkPdu::new(
2434 version,
2435 1,
2436 0,
2437 1,
2438 vec![VarBind::new(name.clone(), Value::Integer(1))],
2439 )
2440 .unwrap();
2441 let expected = bulk.as_raw().clone();
2442 let mut buf = EncodeBuf::new();
2443 bulk.encode(&mut buf).unwrap();
2444 let mut decoder = Decoder::new(buf.finish());
2445 assert_eq!(Pdu::decode(&mut decoder).unwrap(), expected);
2446 }
2447
2448 let mut bulk = GetBulkPdu::new(Version::V3, 3, 1, 12, vec![VarBind::null(name)]).unwrap();
2449 assert_eq!(bulk.parameters(), (1, 12));
2450 assert_eq!(bulk.request_id(), 3);
2451 assert_eq!(bulk.varbinds().len(), 1);
2452 bulk.set_request_id(4);
2453 let raw = bulk.into_raw();
2454 assert_eq!(
2455 GetBulkPdu::try_from_raw(Version::V3, raw)
2456 .unwrap()
2457 .request_id(),
2458 4
2459 );
2460 }
2461
2462 #[test]
2463 fn validated_responses_tie_status_index_version_and_varbind_count() {
2464 let name = oid!(1, 3, 6, 1);
2465 let binding = VarBind::null(name.clone());
2466 assert!(ErrorIndex::new(0, 1).is_err());
2467 assert!(ErrorIndex::new(2, 1).is_err());
2468 assert!(ErrorIndex::new(i32::MAX as u32, usize::MAX).is_ok());
2469 assert!(ErrorIndex::new(i32::MAX as u32 + 1, usize::MAX).is_err());
2470 let index = ErrorIndex::new(1, 1).unwrap();
2471
2472 assert!(
2473 ResponsePdu::new(
2474 Version::V2c,
2475 1,
2476 OutboundErrorStatus::NoError,
2477 Some(index),
2478 vec![binding.clone()]
2479 )
2480 .is_err()
2481 );
2482 assert!(
2483 ResponsePdu::new(
2484 Version::V2c,
2485 1,
2486 OutboundErrorStatus::GenErr,
2487 None,
2488 vec![binding.clone()]
2489 )
2490 .is_err()
2491 );
2492 assert!(
2493 ResponsePdu::new(
2494 Version::V1,
2495 1,
2496 OutboundErrorStatus::NoAccess,
2497 Some(index),
2498 vec![binding.clone()]
2499 )
2500 .is_err()
2501 );
2502 assert!(ResponsePdu::too_big(Version::V2c, 1, vec![binding.clone()]).is_err());
2503 assert!(ResponsePdu::too_big(Version::V1, 1, vec![binding.clone()]).is_ok());
2504
2505 let mut response = ResponsePdu::new(
2506 Version::V3,
2507 8,
2508 OutboundErrorStatus::GenErr,
2509 Some(index),
2510 vec![binding],
2511 )
2512 .unwrap();
2513 assert_eq!(response.status(), OutboundErrorStatus::GenErr);
2514 assert_eq!(response.error_index(), Some(index));
2515 assert_eq!(response.varbinds().len(), 1);
2516 response.set_request_id(10);
2517 assert_eq!(response.request_id(), 10);
2518
2519 let unknown = Pdu::from_raw_parts(
2520 1,
2521 PduBody::Standard {
2522 pdu_type: StandardPduType::Response,
2523 error_status: 99,
2524 error_index: 0,
2525 },
2526 vec![],
2527 );
2528 assert_eq!(unknown.error_status_enum(), ErrorStatus::Unknown(99));
2529 assert!(ResponsePdu::try_from_raw(Version::V2c, unknown).is_err());
2530 }
2531
2532 #[test]
2533 fn response_value_rules_distinguish_versions_and_directions() {
2534 let name = oid!(1, 3, 6, 1);
2535 let exception = vec![VarBind::new(name.clone(), Value::NoSuchObject)];
2536 assert!(ResponsePdu::success(Version::V2c, 1, exception.clone()).is_ok());
2537 assert!(ResponsePdu::success(Version::V1, 1, exception).is_err());
2538 assert!(
2539 RequestPdu::set(
2540 Version::V2c,
2541 1,
2542 vec![VarBind::new(
2543 name,
2544 Value::Unknown {
2545 tag: 0x48,
2546 data: bytes::Bytes::from_static(b"raw"),
2547 },
2548 )]
2549 )
2550 .is_err()
2551 );
2552 }
2553
2554 #[test]
2555 fn validated_notifications_generate_and_check_the_mandatory_prefix() {
2556 let trap_oid = oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1);
2557 let extra = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 5, 0), Value::Integer(1));
2558 assert!(
2559 NotificationPdu::trap_v2(Version::V1, 1, 10, &trap_oid, vec![extra.clone()]).is_err()
2560 );
2561 assert!(
2562 NotificationPdu::trap_v2(
2563 Version::V2c,
2564 1,
2565 10,
2566 &trap_oid,
2567 vec![VarBind::null(oid!(1, 3, 6, 1))]
2568 )
2569 .is_err()
2570 );
2571
2572 for (kind, expected) in [
2573 (StandardPduType::TrapV2, PduType::TrapV2),
2574 (StandardPduType::InformRequest, PduType::InformRequest),
2575 ] {
2576 let mut notification =
2577 NotificationPdu::new(Version::V3, kind, 4, 10, &trap_oid, vec![extra.clone()])
2578 .unwrap();
2579 assert_eq!(notification.pdu_type(), expected);
2580 assert_eq!(notification.uptime(), 10);
2581 assert_eq!(notification.trap_oid(), &trap_oid);
2582 assert_eq!(notification.varbinds().len(), 3);
2583 notification.set_request_id(5);
2584 assert_eq!(notification.request_id(), 5);
2585
2586 let raw = notification.into_raw();
2587 let validated = NotificationPdu::try_from_raw(Version::V3, raw.clone()).unwrap();
2588 let mut buf = EncodeBuf::new();
2589 validated.encode(&mut buf).unwrap();
2590 let mut decoder = Decoder::new(buf.finish());
2591 assert_eq!(Pdu::decode(&mut decoder).unwrap(), raw);
2592 }
2593
2594 let malformed = Pdu::from_raw_parts(
2595 1,
2596 PduBody::Standard {
2597 pdu_type: StandardPduType::TrapV2,
2598 error_status: 0,
2599 error_index: 0,
2600 },
2601 vec![extra],
2602 );
2603 assert!(NotificationPdu::try_from_raw(Version::V2c, malformed).is_err());
2604 }
2605
2606 #[test]
2607 fn reports_are_v3_only_and_have_one_counter_binding() {
2608 let report_oid = oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 4, 0);
2609 assert!(ResponsePdu::report(1, vec![]).is_err());
2610 assert!(ResponsePdu::report(1, vec![VarBind::null(report_oid.clone())]).is_err());
2611 let report =
2612 ResponsePdu::report(1, vec![VarBind::new(report_oid, Value::Counter32(3))]).unwrap();
2613 assert_eq!(report.as_raw().pdu_type(), PduType::Report);
2614 assert!(ResponsePdu::try_from_raw(Version::V2c, report.as_raw().clone()).is_err());
2615 assert!(matches!(
2616 OutboundPdu::try_from_raw(Version::V3, report.into_raw()).unwrap(),
2617 OutboundPdu::Response(_)
2618 ));
2619 }
2620
2621 #[test]
2622 fn validated_v1_traps_check_fields_values_accessors_and_roundtrip() {
2623 let enterprise = oid!(1, 3, 6, 1, 4, 1, 9999);
2624 assert!(
2625 TrapV1Notification::new(
2626 enterprise.clone(),
2627 [127, 0, 0, 1],
2628 GenericTrap::Unknown(7),
2629 0,
2630 1,
2631 vec![]
2632 )
2633 .is_err()
2634 );
2635 let trap = TrapV1Notification::new(
2636 enterprise.clone(),
2637 [127, 0, 0, 1],
2638 GenericTrap::LinkDown,
2639 i32::MIN,
2640 100,
2641 vec![VarBind::null(oid!(1, 3, 6, 1))],
2642 )
2643 .unwrap();
2644 assert_eq!(trap.enterprise(), &enterprise);
2645 assert_eq!(trap.agent_addr(), [127, 0, 0, 1]);
2646 assert_eq!(trap.generic_trap(), GenericTrap::LinkDown);
2647 assert_eq!(trap.specific_trap(), i32::MIN);
2648 assert_eq!(trap.time_stamp(), 100);
2649 assert_eq!(trap.varbinds().len(), 1);
2650
2651 let mut buf = EncodeBuf::new();
2652 trap.encode(&mut buf).unwrap();
2653 let mut decoder = Decoder::new(buf.finish());
2654 assert_eq!(TrapV1Pdu::decode(&mut decoder).unwrap(), trap.into_raw());
2655
2656 let trap = TrapV1Notification::new(
2660 enterprise,
2661 [127, 0, 0, 1],
2662 GenericTrap::EnterpriseSpecific,
2663 -1,
2664 100,
2665 vec![VarBind::null(oid!(1, 3, 6, 1))],
2666 )
2667 .unwrap();
2668 let mut buf = EncodeBuf::new();
2669 trap.encode(&mut buf).unwrap();
2670 let mut decoder = Decoder::new(buf.finish());
2671 assert_eq!(TrapV1Pdu::decode(&mut decoder).unwrap(), trap.into_raw());
2672 }
2673
2674 #[test]
2675 fn test_get_request_roundtrip() {
2676 let pdu = Pdu::get_request(12345, &[oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)]);
2677
2678 let mut buf = EncodeBuf::new();
2679 pdu.encode(&mut buf).unwrap();
2680 let bytes = buf.finish();
2681
2682 let mut decoder = Decoder::new(bytes);
2683 let decoded = Pdu::decode(&mut decoder).unwrap();
2684
2685 assert_eq!(decoded.pdu_type(), PduType::GetRequest);
2686 assert_eq!(decoded.request_id, 12345);
2687 assert_eq!(decoded.varbinds.len(), 1);
2688 }
2689
2690 #[test]
2691 fn test_getbulk_roundtrip() {
2692 let pdu =
2693 Pdu::get_bulk(12345, 0, 10, vec![VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1))]).unwrap();
2694
2695 let mut buf = EncodeBuf::new();
2696 pdu.encode(&mut buf).unwrap();
2697 let bytes = buf.finish();
2698
2699 let mut decoder = Decoder::new(bytes);
2700 let decoded = Pdu::decode(&mut decoder).unwrap();
2701
2702 assert_eq!(decoded.request_id, 12345);
2703 assert_eq!(decoded.get_bulk_fields(), Some((0, 10)));
2704 }
2705
2706 #[test]
2707 fn get_bulk_constructor_checks_both_parameter_ranges() {
2708 let varbinds = || vec![VarBind::null(oid!(1, 3, 6, 1))];
2709
2710 for (non_repeaters, max_repetitions) in
2711 [(0, 0), (MAX_GET_BULK_VALUE, 0), (0, MAX_GET_BULK_VALUE)]
2712 {
2713 let pdu = Pdu::get_bulk(1, non_repeaters, max_repetitions, varbinds()).unwrap();
2714 assert_eq!(
2715 pdu.get_bulk_fields(),
2716 Some((non_repeaters, max_repetitions))
2717 );
2718 }
2719
2720 for (non_repeaters, max_repetitions) in
2721 [(MAX_GET_BULK_VALUE + 1, 0), (0, MAX_GET_BULK_VALUE + 1)]
2722 {
2723 let error = Pdu::get_bulk(1, non_repeaters, max_repetitions, varbinds()).unwrap_err();
2724 assert!(matches!(*error, Error::InvalidMessage(_)));
2725 }
2726 }
2727
2728 #[test]
2729 fn protocol_integer_fields_never_use_generic_truncation() {
2730 let encoded = bytes::Bytes::from_static(&[
2733 0xa0, 0x0f, 0x02, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x02, 0x01,
2734 0x00, 0x30, 0x00,
2735 ]);
2736 let mut decoder = Decoder::new(encoded);
2737 assert!(Pdu::decode(&mut decoder).is_err());
2738 }
2739
2740 #[test]
2741 fn test_trap_v1_roundtrip() {
2742 use crate::value::Value;
2743 use crate::varbind::VarBind;
2744
2745 let trap = TrapV1Pdu::new(
2746 oid!(1, 3, 6, 1, 4, 1, 9999), [192, 168, 1, 1], GenericTrap::LinkDown,
2749 0,
2750 1234_5678, vec![VarBind::new(
2752 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
2753 Value::Integer(1),
2754 )],
2755 );
2756
2757 let mut buf = EncodeBuf::new();
2758 trap.encode(&mut buf).unwrap();
2759 let bytes = buf.finish();
2760
2761 let mut decoder = Decoder::new(bytes);
2762 let decoded = TrapV1Pdu::decode(&mut decoder).unwrap();
2763
2764 assert_eq!(decoded.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999));
2765 assert_eq!(decoded.agent_addr, [192, 168, 1, 1]);
2766 assert_eq!(decoded.generic_trap, GenericTrap::LinkDown);
2767 assert_eq!(decoded.specific_trap, 0);
2768 assert_eq!(decoded.time_stamp, 1234_5678);
2769 assert_eq!(decoded.varbinds.len(), 1);
2770 }
2771
2772 #[test]
2773 fn trap_v1_decode_rejects_unconsumed_constructed_body() {
2774 let trap = TrapV1Pdu::new(
2775 oid!(1, 3, 6, 1, 4, 1, 9999),
2776 [192, 0, 2, 1],
2777 GenericTrap::ColdStart,
2778 0,
2779 1,
2780 vec![],
2781 );
2782 let mut buf = EncodeBuf::new();
2783 trap.encode(&mut buf).unwrap();
2784 let mut encoded = buf.finish().to_vec();
2785 assert_eq!(encoded[0], tag::pdu::TRAP_V1);
2786 assert!(encoded[1] < 0x80, "fixture uses short-form length");
2787 encoded[1] += 2;
2788 encoded.extend_from_slice(&[tag::universal::NULL, 0]);
2789
2790 let mut decoder = Decoder::new(bytes::Bytes::from(encoded));
2791 assert!(TrapV1Pdu::decode(&mut decoder).is_err());
2792 }
2793
2794 #[test]
2795 fn test_generic_pdu_decode_rejects_trap_v1_tag() {
2796 let trap = TrapV1Pdu::new(
2800 oid!(1, 3, 6, 1, 4, 1, 9999),
2801 [192, 168, 1, 1],
2802 GenericTrap::LinkDown,
2803 0,
2804 12345,
2805 vec![],
2806 );
2807 let mut buf = EncodeBuf::new();
2808 trap.encode(&mut buf).unwrap();
2809 let bytes = buf.finish();
2810
2811 let mut decoder = Decoder::new(bytes);
2812 let result = Pdu::decode(&mut decoder);
2813 assert!(
2814 result.is_err(),
2815 "generic Pdu::decode must reject TrapV1 tag, got {result:?}"
2816 );
2817 }
2818
2819 #[test]
2820 fn test_trap_v1_enterprise_specific() {
2821 let trap = TrapV1Pdu::new(
2822 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
2823 [10, 0, 0, 1],
2824 GenericTrap::EnterpriseSpecific,
2825 42, 100,
2827 vec![],
2828 );
2829
2830 assert!(trap.is_enterprise_specific());
2831 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
2832
2833 let mut buf = EncodeBuf::new();
2834 trap.encode(&mut buf).unwrap();
2835 let bytes = buf.finish();
2836
2837 let mut decoder = Decoder::new(bytes);
2838 let decoded = TrapV1Pdu::decode(&mut decoder).unwrap();
2839
2840 assert_eq!(decoded.specific_trap, 42);
2841 }
2842
2843 #[test]
2844 fn test_trap_v1_v2_trap_oid_generic_traps() {
2845 let test_cases = [
2849 (GenericTrap::ColdStart, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1)),
2850 (GenericTrap::WarmStart, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 2)),
2851 (GenericTrap::LinkDown, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3)),
2852 (GenericTrap::LinkUp, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 4)),
2853 (
2854 GenericTrap::AuthenticationFailure,
2855 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 5),
2856 ),
2857 (
2858 GenericTrap::EgpNeighborLoss,
2859 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 6),
2860 ),
2861 ];
2862
2863 for (generic_trap, expected_oid) in test_cases {
2864 let trap = TrapV1Pdu::new(
2865 oid!(1, 3, 6, 1, 4, 1, 9999),
2866 [192, 168, 1, 1],
2867 generic_trap,
2868 0,
2869 12345,
2870 vec![],
2871 );
2872 assert_eq!(
2873 trap.v2_trap_oid().unwrap(),
2874 expected_oid,
2875 "Failed for {generic_trap:?}"
2876 );
2877 }
2878 }
2879
2880 #[test]
2881 fn test_trap_v1_v2_trap_oid_enterprise_specific() {
2882 let trap = TrapV1Pdu::new(
2884 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
2885 [192, 168, 1, 1],
2886 GenericTrap::EnterpriseSpecific,
2887 42,
2888 12345,
2889 vec![],
2890 );
2891
2892 assert_eq!(
2894 trap.v2_trap_oid().unwrap(),
2895 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2, 0, 42)
2896 );
2897 }
2898
2899 #[test]
2900 fn test_trap_v1_v2_trap_oid_enterprise_specific_zero() {
2901 let trap = TrapV1Pdu::new(
2903 oid!(1, 3, 6, 1, 4, 1, 1234),
2904 [10, 0, 0, 1],
2905 GenericTrap::EnterpriseSpecific,
2906 0,
2907 100,
2908 vec![],
2909 );
2910
2911 assert_eq!(
2913 trap.v2_trap_oid().unwrap(),
2914 oid!(1, 3, 6, 1, 4, 1, 1234, 0, 0)
2915 );
2916 }
2917
2918 #[test]
2919 fn test_pdu_to_response() {
2920 use crate::value::Value;
2921 use crate::varbind::VarBind;
2922
2923 let inform = Pdu::standard(
2924 crate::pdu::StandardPduType::InformRequest,
2925 99999,
2926 0,
2927 0,
2928 vec![
2929 VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::TimeTicks(12345)),
2930 VarBind::new(
2931 oid!(1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0),
2932 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1)),
2933 ),
2934 ],
2935 );
2936
2937 let response = inform.to_response(Version::V2c).unwrap();
2938
2939 assert_eq!(response.pdu_type(), PduType::Response);
2940 assert_eq!(response.request_id, 99999);
2941 assert_eq!(response.error_status(), 0);
2942 assert_eq!(response.error_index(), 0);
2943 assert_eq!(response.varbinds.len(), 2);
2944 }
2945
2946 #[test]
2947 fn test_pdu_is_confirmed() {
2948 let get = Pdu::get_request(1, &[oid!(1, 3, 6, 1)]);
2949 assert!(get.is_confirmed());
2950
2951 let inform = Pdu::standard(crate::pdu::StandardPduType::InformRequest, 1, 0, 0, vec![]);
2952 assert!(inform.is_confirmed());
2953
2954 let trap = Pdu::standard(crate::pdu::StandardPduType::TrapV2, 1, 0, 0, vec![]);
2955 assert!(!trap.is_confirmed());
2956 assert!(trap.is_notification());
2957 }
2958
2959 #[test]
2960 fn test_decode_accepts_negative_error_index() {
2961 let raw = RawPdu::response(1, 0, -1, vec![VarBind::null(oid!(1, 3, 6, 1))]);
2967 let encoded = raw.encode();
2968
2969 let mut decoder = Decoder::new(encoded);
2970 let result = Pdu::decode(&mut decoder);
2971
2972 assert!(
2973 result.is_ok(),
2974 "negative error_index must be accepted to match net-snmp behavior, got {:?}",
2975 result.err()
2976 );
2977 assert_eq!(result.unwrap().error_index(), -1);
2978 }
2979
2980 #[test]
2981 fn test_decode_accepts_error_index_beyond_varbinds() {
2982 let raw = RawPdu::response(1, 5, 5, vec![VarBind::null(oid!(1, 3, 6, 1))]);
2988 let encoded = raw.encode();
2989
2990 let mut decoder = Decoder::new(encoded);
2991 let result = Pdu::decode(&mut decoder);
2992
2993 assert!(
2994 result.is_ok(),
2995 "error_index beyond varbind count must be accepted to match net-snmp behavior, got {:?}",
2996 result.err()
2997 );
2998 assert_eq!(result.unwrap().error_index(), 5);
2999 }
3000
3001 #[test]
3002 fn test_decode_accepts_valid_error_index_zero() {
3003 let raw = RawPdu::response(1, 0, 0, vec![VarBind::null(oid!(1, 3, 6, 1))]);
3005 let encoded = raw.encode();
3006
3007 let mut decoder = Decoder::new(encoded);
3008 let decoded = Pdu::decode(&mut decoder);
3009 assert!(decoded.is_ok(), "error_index=0 should be valid");
3010 }
3011
3012 #[test]
3013 fn decode_accepts_too_big_with_variable_bindings() {
3014 let raw = RawPdu::response(
3015 1,
3016 ErrorStatus::TooBig.as_i32(),
3017 0,
3018 vec![VarBind::null(oid!(1, 3, 6, 1))],
3019 );
3020
3021 let mut decoder = Decoder::new(raw.encode());
3022 let decoded = Pdu::decode(&mut decoder).expect("received PDUs remain permissive");
3023 assert_eq!(decoded.error_status(), ErrorStatus::TooBig.as_i32());
3024 assert_eq!(decoded.error_index(), 0);
3025 assert_eq!(decoded.varbinds.len(), 1);
3026 }
3027
3028 #[test]
3029 fn authorization_error_response_requires_zero_index() {
3030 let request = Pdu::get_request(1, &[oid!(1, 3, 6, 1)]);
3031 let valid = request.to_error_response(Version::V2c, ErrorStatus::AuthorizationError, 0);
3032 assert!(valid.is_ok());
3033
3034 let invalid = request.to_error_response(Version::V2c, ErrorStatus::AuthorizationError, 1);
3035 assert!(invalid.is_err());
3036 }
3037
3038 #[test]
3039 fn test_decode_accepts_error_index_within_bounds() {
3040 let raw = RawPdu::response(1, 5, 1, vec![VarBind::null(oid!(1, 3, 6, 1))]);
3042 let encoded = raw.encode();
3043
3044 let mut decoder = Decoder::new(encoded);
3045 let result = Pdu::decode(&mut decoder);
3046 assert!(
3047 result.is_ok(),
3048 "error_index=1 with 1 varbind should be valid"
3049 );
3050 }
3051
3052 #[test]
3053 fn test_decode_clamps_negative_non_repeaters() {
3054 let raw = RawBulkWirePdu::new(1, -1, 10, vec![VarBind::null(oid!(1, 3, 6, 1))]);
3058 let encoded = raw.encode();
3059
3060 let mut decoder = Decoder::new(encoded.clone());
3061 let decoded = Pdu::decode(&mut decoder).expect("negative non_repeaters clamps to 0");
3062 assert_eq!(decoded.get_bulk_fields(), Some((0, 10)));
3063
3064 let mut strict = Decoder::new(encoded)
3065 .with_decode_config(compatibility_without_negative_bulk_normalization());
3066 let error = Pdu::decode(&mut strict).unwrap_err();
3067 assert!(matches!(*error, Error::Decode(_)));
3068 }
3069
3070 #[test]
3071 fn test_decode_clamps_negative_max_repetitions() {
3072 let raw = RawBulkWirePdu::new(1, 0, -5, vec![VarBind::null(oid!(1, 3, 6, 1))]);
3073 let encoded = raw.encode();
3074
3075 let mut decoder = Decoder::new(encoded.clone());
3076 let decoded = Pdu::decode(&mut decoder).expect("negative max_repetitions clamps to 0");
3077 assert_eq!(decoded.get_bulk_fields(), Some((0, 0)));
3078
3079 let mut strict = Decoder::new(encoded)
3080 .with_decode_config(compatibility_without_negative_bulk_normalization());
3081 let error = Pdu::decode(&mut strict).unwrap_err();
3082 assert!(matches!(*error, Error::Decode(_)));
3083 }
3084
3085 #[test]
3086 fn test_pdu_decode_getbulk_clamps_negative_non_repeaters_repro() {
3087 let packet = [
3093 0xa5, 0x0b, 0x02, 0x01, 0x01, 0x02, 0x01, 0xff, 0x02, 0x01, 0x01, 0x30, 0x00,
3094 ];
3095 let encoded = bytes::Bytes::copy_from_slice(&packet);
3096 let mut decoder = Decoder::new(encoded.clone());
3097 let pdu = Pdu::decode(&mut decoder).expect("repro GETBULK packet must decode");
3098 assert_eq!(pdu.pdu_type(), PduType::GetBulkRequest);
3099 assert_eq!(pdu.request_id, 1);
3100 assert_eq!(pdu.get_bulk_fields(), Some((0, 1)));
3101
3102 let mut strict = Decoder::new(encoded)
3103 .with_decode_config(compatibility_without_negative_bulk_normalization());
3104 assert!(Pdu::decode(&mut strict).is_err());
3105 }
3106
3107 #[test]
3108 fn test_decode_accepts_valid_getbulk_params() {
3109 let raw = RawBulkWirePdu::new(1, 0, 10, vec![VarBind::null(oid!(1, 3, 6, 1))]);
3110 let encoded = raw.encode();
3111
3112 let mut decoder = Decoder::new(encoded);
3113 let result = Pdu::decode(&mut decoder);
3114 assert!(result.is_ok(), "valid GETBULK params should be accepted");
3115
3116 let pdu = result.unwrap();
3117 assert_eq!(pdu.get_bulk_fields(), Some((0, 10)));
3118 }
3119
3120 #[test]
3121 fn encode_rejects_out_of_range_get_bulk_fields_without_mutation() {
3122 for (non_repeaters, max_repetitions) in
3123 [(MAX_GET_BULK_VALUE + 1, 0), (0, MAX_GET_BULK_VALUE + 1)]
3124 {
3125 let pdu = Pdu {
3126 request_id: 1,
3127 body: PduBody::GetBulk {
3128 non_repeaters,
3129 max_repetitions,
3130 },
3131 varbinds: vec![VarBind::null(oid!(1, 3, 6, 1))],
3132 };
3133 let original = pdu.clone();
3134 let mut buf = EncodeBuf::new();
3135
3136 assert!(pdu.encode(&mut buf).is_err());
3137 assert!(buf.is_empty());
3138 assert_eq!(pdu, original);
3139 }
3140 }
3141
3142 #[test]
3143 fn test_encode_leaves_non_negative_non_repeaters_and_max_repetitions_unchanged() {
3144 let pdu = Pdu::get_bulk(1, 0, 10, vec![VarBind::null(oid!(1, 3, 6, 1))]).unwrap();
3145
3146 let mut buf = EncodeBuf::new();
3147 pdu.encode(&mut buf).unwrap();
3148 let bytes = buf.finish();
3149
3150 let mut decoder = Decoder::new(bytes);
3151 let decoded = Pdu::decode(&mut decoder).unwrap();
3152 assert_eq!(decoded.get_bulk_fields(), Some((0, 10)));
3153 }
3154
3155 #[test]
3156 fn compatible_decode_constructs_encodable_canonical_body() {
3157 let raw = RawBulkWirePdu::new(1, -1, -5, vec![VarBind::null(oid!(1, 3, 6, 1))]);
3158 let mut decoder = Decoder::new(raw.encode());
3159 let decoded = Pdu::decode(&mut decoder).unwrap();
3160 assert_eq!(decoded.get_bulk_fields(), Some((0, 0)));
3161
3162 let mut buf = EncodeBuf::new();
3163 decoded.encode(&mut buf).unwrap();
3164 let mut roundtrip_decoder = Decoder::new(buf.finish());
3165 let roundtrip = Pdu::decode(&mut roundtrip_decoder).unwrap();
3166 assert_eq!(roundtrip.get_bulk_fields(), Some((0, 0)));
3167 }
3168
3169 #[test]
3170 fn test_pdu_decode_getbulk_with_large_max_repetitions() {
3171 let raw = RawBulkWirePdu::new(12345, 0, 25, vec![VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1))]);
3175 let encoded = raw.encode();
3176
3177 let mut decoder = Decoder::new(encoded);
3178 let result = Pdu::decode(&mut decoder);
3179 assert!(
3180 result.is_ok(),
3181 "Pdu::decode should accept GETBULK with max_repetitions > varbinds.len(), got {:?}",
3182 result.err()
3183 );
3184
3185 let pdu = result.unwrap();
3186 assert_eq!(pdu.pdu_type(), PduType::GetBulkRequest);
3187 assert_eq!(pdu.request_id, 12345);
3188 assert_eq!(pdu.get_bulk_fields(), Some((0, 25)));
3189 assert_eq!(pdu.varbinds.len(), 1);
3190 }
3191
3192 #[test]
3193 fn test_getbulk_request_is_not_treated_as_error() {
3194 let pdu = Pdu::get_bulk(
3195 12345,
3196 2,
3197 10,
3198 vec![
3199 VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1)),
3200 VarBind::null(oid!(1, 3, 6, 1, 2, 1, 2)),
3201 ],
3202 )
3203 .unwrap();
3204
3205 assert!(!pdu.is_error());
3206 }
3207
3208 #[test]
3209 fn test_response_with_error_status_is_treated_as_error() {
3210 let pdu = Pdu::response(
3211 12345,
3212 ErrorStatus::TooBig.as_i32(),
3213 1,
3214 vec![VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1))],
3215 );
3216
3217 assert!(pdu.is_error());
3218 }
3219
3220 #[test]
3221 fn outbound_validation_rejects_nonzero_request_error_fields() {
3222 for (status, index) in [(1, 0), (0, 1), (1, 1)] {
3223 let pdu = Pdu::standard(
3224 StandardPduType::GetRequest,
3225 1,
3226 status,
3227 index,
3228 vec![VarBind::null(oid!(1, 3, 6, 1))],
3229 );
3230 assert!(pdu.encode(&mut EncodeBuf::new()).is_err());
3231 }
3232 }
3233
3234 #[test]
3235 fn outbound_validation_checks_response_status_and_index_combinations() {
3236 let varbinds = vec![VarBind::null(oid!(1, 3, 6, 1))];
3237 for (status, index) in [
3238 (0, 1),
3239 (1, 1),
3240 (-1, 0),
3241 (19, 0),
3242 (5, -1),
3243 (5, 0),
3244 (5, 2),
3245 (ErrorStatus::UndoFailed.as_i32(), 1),
3246 ] {
3247 let pdu = Pdu::response(1, status, index, varbinds.clone());
3248 assert!(
3249 pdu.encode(&mut EncodeBuf::new()).is_err(),
3250 "{status}/{index}"
3251 );
3252 }
3253
3254 let gen_err = Pdu::response(1, ErrorStatus::GenErr.as_i32(), 1, varbinds.clone());
3255 assert!(gen_err.encode(&mut EncodeBuf::new()).is_ok());
3256 let undo_failed = Pdu::response(1, ErrorStatus::UndoFailed.as_i32(), 0, varbinds.clone());
3257 assert!(undo_failed.encode(&mut EncodeBuf::new()).is_ok());
3258 assert!(
3259 undo_failed
3260 .encode_for(&mut EncodeBuf::new(), Version::V1, PduDirection::Response,)
3261 .is_err()
3262 );
3263 }
3264
3265 #[test]
3266 fn outbound_too_big_shape_depends_on_version() {
3267 let with_varbind = Pdu::response(
3268 1,
3269 ErrorStatus::TooBig.as_i32(),
3270 0,
3271 vec![VarBind::null(oid!(1, 3, 6, 1))],
3272 );
3273
3274 assert!(
3275 with_varbind
3276 .encode_for(&mut EncodeBuf::new(), Version::V1, PduDirection::Response)
3277 .is_ok()
3278 );
3279 for version in [Version::V2c, Version::V3] {
3280 assert!(
3281 with_varbind
3282 .encode_for(&mut EncodeBuf::new(), version, PduDirection::Response)
3283 .is_err(),
3284 "{version:?}"
3285 );
3286 }
3287 assert!(with_varbind.encode(&mut EncodeBuf::new()).is_err());
3288
3289 let empty = Pdu::response(1, ErrorStatus::TooBig.as_i32(), 0, vec![]);
3290 for version in [Version::V2c, Version::V3] {
3291 assert!(
3292 empty
3293 .encode_for(&mut EncodeBuf::new(), version, PduDirection::Response)
3294 .is_ok(),
3295 "{version:?}"
3296 );
3297 }
3298
3299 let nonzero_index = Pdu::response(
3300 1,
3301 ErrorStatus::TooBig.as_i32(),
3302 1,
3303 vec![VarBind::null(oid!(1, 3, 6, 1))],
3304 );
3305 for version in [Version::V1, Version::V2c, Version::V3] {
3306 assert!(
3307 nonzero_index
3308 .encode_for(&mut EncodeBuf::new(), version, PduDirection::Response)
3309 .is_err(),
3310 "{version:?}"
3311 );
3312 }
3313 }
3314
3315 #[test]
3316 fn outbound_validation_allows_exceptions_only_in_v2_or_v3_responses() {
3317 let varbinds = vec![VarBind::new(oid!(1, 3, 6, 1), Value::NoSuchObject)];
3318 let request = Pdu::get_request(1, &[]);
3319 let mut request = Pdu {
3320 varbinds: varbinds.clone(),
3321 ..request
3322 };
3323 let original = request.clone();
3324 assert!(request.encode(&mut EncodeBuf::new()).is_err());
3325 assert_eq!(request, original);
3326
3327 request.set_standard_pdu_type(StandardPduType::Response);
3328 assert!(request.encode(&mut EncodeBuf::new()).is_ok());
3329 assert!(
3330 request
3331 .encode_for(&mut EncodeBuf::new(), Version::V1, PduDirection::Response,)
3332 .is_err()
3333 );
3334 }
3335
3336 #[test]
3337 fn outbound_validation_rejects_receive_only_values_without_mutation() {
3338 for value in [
3339 Value::UInteger32(1),
3340 Value::Nsap(bytes::Bytes::from_static(b"nsap")),
3341 Value::Unknown {
3342 tag: 0x48,
3343 data: bytes::Bytes::from_static(b"raw"),
3344 },
3345 ] {
3346 let pdu = Pdu::set_request(1, vec![VarBind::new(oid!(1, 3, 6, 1), value)]);
3347 let original = pdu.clone();
3348 assert!(pdu.encode(&mut EncodeBuf::new()).is_err());
3349 assert_eq!(pdu, original);
3350 }
3351 }
3352
3353 #[test]
3354 fn pdu_type_hash() {
3355 use std::collections::HashSet;
3356 let mut set = HashSet::new();
3357 set.insert(PduType::GetRequest);
3358 set.insert(PduType::GetNextRequest);
3359 assert_eq!(set.len(), 2);
3360 assert!(set.contains(&PduType::GetRequest));
3361 }
3362
3363 #[test]
3368 fn test_v1_to_v2_generic_trap() {
3369 use crate::value::Value;
3370 use crate::varbind::VarBind;
3371
3372 let trap = TrapV1Pdu::new(
3373 oid!(1, 3, 6, 1, 4, 1, 9999),
3374 [192, 168, 1, 1],
3375 GenericTrap::LinkDown,
3376 0,
3377 12345,
3378 vec![VarBind::new(
3379 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
3380 Value::Integer(1),
3381 )],
3382 );
3383
3384 let pdu = trap.to_v2_pdu().unwrap();
3385
3386 assert_eq!(pdu.pdu_type(), PduType::TrapV2);
3387 assert_eq!(pdu.request_id(), 0);
3388 assert_eq!(pdu.varbinds().len(), 3);
3390
3391 assert_eq!(pdu.varbinds()[0].oid, oid!(1, 3, 6, 1, 2, 1, 1, 3, 0));
3393 assert_eq!(pdu.varbinds()[0].value, Value::TimeTicks(12345));
3394
3395 assert_eq!(pdu.varbinds()[1].oid, oid!(1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0));
3397 assert_eq!(
3398 pdu.varbinds()[1].value,
3399 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3))
3400 );
3401
3402 assert_eq!(pdu.varbinds()[2].oid, oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1));
3404 }
3405
3406 #[test]
3407 fn unknown_v1_generic_trap_seven_uses_net_snmp_arithmetic_mapping() {
3408 let trap = TrapV1Pdu::from_raw_parts(
3409 oid!(1, 3, 6, 1, 4, 1, 9999),
3410 [192, 0, 2, 1],
3411 GenericTrap::Unknown(7),
3412 0,
3413 1,
3414 vec![],
3415 );
3416
3417 assert_eq!(
3418 trap.v2_trap_oid().unwrap(),
3419 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 8)
3420 );
3421 assert!(
3422 TrapV1Notification::new(
3423 oid!(1, 3, 6, 1, 4, 1, 9999),
3424 [192, 0, 2, 1],
3425 GenericTrap::Unknown(7),
3426 0,
3427 1,
3428 vec![],
3429 )
3430 .is_err()
3431 );
3432 }
3433
3434 #[test]
3435 fn test_v1_to_v2_no_proxy_varbinds() {
3436 let trap = TrapV1Pdu::new(
3437 oid!(1, 3, 6, 1, 4, 1, 9999),
3438 [192, 168, 1, 1],
3439 GenericTrap::ColdStart,
3440 0,
3441 100,
3442 vec![],
3443 );
3444
3445 let pdu = trap.to_v2_pdu().unwrap();
3446 assert_eq!(pdu.varbinds().len(), 2);
3449 }
3450
3451 #[test]
3452 fn test_v1_to_v2_enterprise_specific() {
3453 use crate::value::Value;
3454
3455 let trap = TrapV1Pdu::new(
3456 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
3457 [10, 0, 0, 1],
3458 GenericTrap::EnterpriseSpecific,
3459 42,
3460 5000,
3461 vec![],
3462 );
3463
3464 let pdu = trap.to_v2_pdu().unwrap();
3465
3466 assert_eq!(
3468 pdu.varbinds()[1].value,
3469 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2, 0, 42))
3470 );
3471 }
3472
3473 #[test]
3474 fn v1_to_v2_rejects_invalid_synthesized_trap_oid() {
3475 let mut enterprise_arcs = vec![1, 3];
3476 enterprise_arcs.resize(crate::oid::MAX_OID_LEN - 1, 1);
3477 let trap = TrapV1Pdu::from_raw_parts(
3478 Oid::new(enterprise_arcs),
3479 [0, 0, 0, 0],
3480 GenericTrap::EnterpriseSpecific,
3481 1,
3482 0,
3483 vec![],
3484 );
3485
3486 assert!(trap.enterprise().validate_for_wire().is_ok());
3487 assert!(trap.v2_trap_oid().is_err());
3488 assert!(trap.to_v2_pdu().is_err());
3489 }
3490
3491 #[test]
3492 fn v1_to_v2_validates_every_copied_varbind() {
3493 let valid = VarBind::new(oid!(1, 3, 6, 1, 4, 1, 9999, 1), Value::Integer(1));
3494 let invalid_varbinds = [
3495 VarBind::new(Oid::empty(), Value::Integer(2)),
3496 VarBind::new(
3497 oid!(1, 3, 6, 1, 4, 1, 9999, 2),
3498 Value::ObjectIdentifier(Oid::empty()),
3499 ),
3500 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 9999, 3), Value::Null),
3501 VarBind::new(
3502 oid!(1, 3, 6, 1, 4, 1, 9999, 4),
3503 Value::Unknown {
3504 tag: 0x48,
3505 data: bytes::Bytes::from_static(b"raw"),
3506 },
3507 ),
3508 ];
3509
3510 for invalid in invalid_varbinds {
3511 let trap = TrapV1Pdu::from_raw_parts(
3512 oid!(1, 3, 6, 1, 4, 1, 9999),
3513 [0, 0, 0, 0],
3514 GenericTrap::ColdStart,
3515 0,
3516 0,
3517 vec![valid.clone(), invalid],
3518 );
3519 assert!(trap.to_v2_pdu().is_err());
3520 }
3521 }
3522
3523 #[test]
3524 fn test_v2_to_v1_standard_trap() {
3525 use crate::value::Value;
3526 use crate::varbind::VarBind;
3527
3528 let pdu = Pdu::trap_v2(
3529 1,
3530 5000,
3531 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3), vec![VarBind::new(
3533 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
3534 Value::Integer(1),
3535 )],
3536 );
3537
3538 let trap = to_v1_trap(&pdu, [10, 0, 0, 1]).unwrap();
3539
3540 assert_eq!(trap.generic_trap, GenericTrap::LinkDown);
3541 assert_eq!(trap.specific_trap, 0);
3542 assert_eq!(trap.time_stamp, 5000);
3543 assert_eq!(trap.agent_addr, [10, 0, 0, 1]);
3544 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5));
3546 assert_eq!(trap.varbinds.len(), 1);
3547 }
3548
3549 #[test]
3550 fn test_v2_to_v1_enterprise_specific_trap() {
3551 let pdu = Pdu::trap_v2(1, 100, &oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2, 0, 42), vec![]);
3552
3553 let trap = to_v1_trap(&pdu, [0, 0, 0, 0]).unwrap();
3554
3555 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
3556 assert_eq!(trap.specific_trap, 42);
3557 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2));
3558 assert_eq!(trap.time_stamp, 100);
3559 }
3560
3561 #[test]
3562 fn test_v2_to_v1_enterprise_specific_nonzero_penultimate() {
3563 let pdu = Pdu::trap_v2(1, 200, &oid!(1, 3, 6, 1, 4, 1, 9999, 1, 42), vec![]);
3566
3567 let trap = to_v1_trap(&pdu, [0, 0, 0, 0]).unwrap();
3568
3569 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
3570 assert_eq!(trap.specific_trap, 42);
3571 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999, 1));
3573 assert_eq!(trap.time_stamp, 200);
3574 }
3575
3576 #[test]
3577 fn v2_to_v1_rejects_short_derived_enterprise_oids() {
3578 for trap_oid in [oid!(0, 1), oid!(0, 0, 1)] {
3579 let notification =
3580 NotificationPdu::trap_v2(Version::V2c, 1, 100, &trap_oid, vec![]).unwrap();
3581 assert!(notification.to_v1_trap([0, 0, 0, 0]).is_err());
3582 }
3583
3584 let boundary = NotificationPdu::trap_v2(Version::V2c, 1, 100, &oid!(0, 0, 0, 1), vec![])
3585 .unwrap()
3586 .to_v1_trap([0, 0, 0, 0])
3587 .unwrap();
3588 assert_eq!(boundary.enterprise(), &oid!(0, 0));
3589 }
3590
3591 #[test]
3592 fn arbitrary_standard_pdu_cannot_enter_notification_conversion() {
3593 let get = Pdu::get_request(1, &[oid!(1, 3, 6, 1)]);
3594 assert!(NotificationPdu::try_from_raw(Version::V2c, get).is_err());
3595 }
3596
3597 #[test]
3598 fn test_v2_to_v1_snmp_traps_arc_out_of_range() {
3599 for arc in [0u32, 7, 9] {
3603 let pdu = Pdu::trap_v2(1, 100, &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, arc), vec![]);
3604
3605 let trap = to_v1_trap(&pdu, [0, 0, 0, 0]).unwrap();
3606
3607 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
3608 assert_eq!(trap.specific_trap, i32::try_from(arc).unwrap());
3609 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5));
3610 }
3611 }
3612
3613 #[test]
3614 fn test_v2_to_v1_extracts_trap_address() {
3615 use crate::notification::oids;
3616 use crate::value::Value;
3617 use crate::varbind::VarBind;
3618
3619 let pdu = Pdu::trap_v2(
3620 1,
3621 0,
3622 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1), vec![VarBind::new(
3624 oids::snmp_trap_address(),
3625 Value::IpAddress([192, 168, 1, 1]),
3626 )],
3627 );
3628
3629 let trap = to_v1_trap(&pdu, [0, 0, 0, 0]).unwrap();
3630 assert_eq!(trap.agent_addr, [192, 168, 1, 1]);
3631 assert_eq!(trap.varbinds.len(), 1);
3634 assert_eq!(trap.varbinds[0].oid, oids::snmp_trap_address());
3635 }
3636
3637 #[test]
3638 fn test_v2_to_v1_extracts_trap_enterprise() {
3639 use crate::notification::oids;
3640 use crate::value::Value;
3641 use crate::varbind::VarBind;
3642
3643 let pdu = Pdu::trap_v2(
3644 1,
3645 0,
3646 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1), vec![VarBind::new(
3648 oids::snmp_trap_enterprise(),
3649 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4, 1, 9999)),
3650 )],
3651 );
3652
3653 let trap = to_v1_trap(&pdu, [0, 0, 0, 0]).unwrap();
3654 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999));
3656 assert_eq!(trap.varbinds.len(), 1);
3659 assert_eq!(trap.varbinds[0].oid, oids::snmp_trap_enterprise());
3660 }
3661
3662 #[test]
3663 fn test_v2_to_v1_counter64_dropped() {
3664 use crate::value::Value;
3665 use crate::varbind::VarBind;
3666
3667 let pdu = Pdu::trap_v2(
3668 1,
3669 0,
3670 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1),
3671 vec![VarBind::new(
3672 oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
3673 Value::Counter64(12345),
3674 )],
3675 );
3676
3677 assert!(to_v1_trap(&pdu, [0, 0, 0, 0]).is_err());
3679 }
3680
3681 #[test]
3682 fn test_v2_to_v1_too_few_varbinds() {
3683 let pdu = Pdu::standard(crate::pdu::StandardPduType::TrapV2, 1, 0, 0, vec![]);
3684
3685 assert!(to_v1_trap(&pdu, [0, 0, 0, 0]).is_err());
3686 }
3687
3688 #[test]
3689 fn test_v1_v2_roundtrip_enterprise_specific() {
3690 use crate::value::Value;
3691 use crate::varbind::VarBind;
3692
3693 let original = TrapV1Pdu::new(
3698 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
3699 [192, 168, 1, 1],
3700 GenericTrap::EnterpriseSpecific,
3701 42,
3702 12345,
3703 vec![VarBind::new(
3704 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
3705 Value::Integer(1),
3706 )],
3707 );
3708
3709 let v2 = original.to_v2_pdu().unwrap();
3710 let restored = to_v1_trap(v2.as_raw(), [0, 0, 0, 0]).unwrap();
3711
3712 assert_eq!(restored.enterprise, original.enterprise);
3713 assert_eq!(restored.generic_trap, original.generic_trap);
3714 assert_eq!(restored.specific_trap, original.specific_trap);
3715 assert_eq!(restored.time_stamp, original.time_stamp);
3716 assert_eq!(restored.varbinds.len(), original.varbinds.len());
3717 assert_eq!(restored.varbinds[0].oid, original.varbinds[0].oid);
3718 assert_eq!(restored.agent_addr, [0, 0, 0, 0]);
3720 }
3721
3722 #[test]
3723 fn test_v1_v2_roundtrip_standard_trap() {
3724 let original = TrapV1Pdu::new(
3728 oid!(1, 3, 6, 1, 4, 1, 9999),
3729 [10, 0, 0, 1],
3730 GenericTrap::WarmStart,
3731 0,
3732 500,
3733 vec![],
3734 );
3735
3736 let v2 = original.to_v2_pdu().unwrap();
3737 let restored = to_v1_trap(v2.as_raw(), [10, 0, 0, 1]).unwrap();
3738
3739 assert_eq!(restored.generic_trap, GenericTrap::WarmStart);
3740 assert_eq!(restored.specific_trap, 0);
3741 assert_eq!(restored.time_stamp, 500);
3742 assert_eq!(restored.agent_addr, [10, 0, 0, 1]); assert_eq!(restored.enterprise, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5));
3745 }
3746
3747 #[test]
3748 fn test_v2_to_v1_all_generic_traps() {
3749 let traps = [
3751 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1), GenericTrap::ColdStart),
3752 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 2), GenericTrap::WarmStart),
3753 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3), GenericTrap::LinkDown),
3754 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 4), GenericTrap::LinkUp),
3755 (
3756 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 5),
3757 GenericTrap::AuthenticationFailure,
3758 ),
3759 (
3760 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 6),
3761 GenericTrap::EgpNeighborLoss,
3762 ),
3763 ];
3764
3765 for (trap_oid, expected_generic) in traps {
3766 let pdu = Pdu::trap_v2(1, 100, &trap_oid, vec![]);
3767 let v1 = to_v1_trap(&pdu, [0, 0, 0, 0]).unwrap();
3768 assert_eq!(v1.generic_trap, expected_generic, "Failed for {trap_oid:?}");
3769 assert_eq!(v1.specific_trap, 0);
3770 }
3771 }
3772}