1use crate::ber::{Decoder, EncodeBuf, tag};
6use crate::error::internal::DecodeErrorKind;
7use crate::error::{Error, ErrorStatus, Result, UNKNOWN_TARGET};
8use crate::oid::Oid;
9use crate::varbind::{VarBind, decode_varbind_list, encode_varbind_list};
10
11const fn clamp_bulk_field(value: i32) -> i32 {
23 if value < 0 { 0 } else { value }
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28#[repr(u8)]
29pub enum PduType {
30 GetRequest = 0xA0,
32 GetNextRequest = 0xA1,
34 Response = 0xA2,
36 SetRequest = 0xA3,
38 TrapV1 = 0xA4,
40 GetBulkRequest = 0xA5,
42 InformRequest = 0xA6,
44 TrapV2 = 0xA7,
46 Report = 0xA8,
48}
49
50impl PduType {
51 #[must_use]
53 pub fn from_tag(tag: u8) -> Option<Self> {
54 match tag {
55 0xA0 => Some(Self::GetRequest),
56 0xA1 => Some(Self::GetNextRequest),
57 0xA2 => Some(Self::Response),
58 0xA3 => Some(Self::SetRequest),
59 0xA4 => Some(Self::TrapV1),
60 0xA5 => Some(Self::GetBulkRequest),
61 0xA6 => Some(Self::InformRequest),
62 0xA7 => Some(Self::TrapV2),
63 0xA8 => Some(Self::Report),
64 _ => None,
65 }
66 }
67
68 #[must_use]
70 pub fn tag(self) -> u8 {
71 self as u8
72 }
73}
74
75impl std::fmt::Display for PduType {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 match self {
78 Self::GetRequest => write!(f, "GetRequest"),
79 Self::GetNextRequest => write!(f, "GetNextRequest"),
80 Self::Response => write!(f, "Response"),
81 Self::SetRequest => write!(f, "SetRequest"),
82 Self::TrapV1 => write!(f, "TrapV1"),
83 Self::GetBulkRequest => write!(f, "GetBulkRequest"),
84 Self::InformRequest => write!(f, "InformRequest"),
85 Self::TrapV2 => write!(f, "TrapV2"),
86 Self::Report => write!(f, "Report"),
87 }
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct Pdu {
94 pub pdu_type: PduType,
96 pub request_id: i32,
98 pub error_status: i32,
100 pub error_index: i32,
102 pub varbinds: Vec<VarBind>,
104}
105
106impl Pdu {
107 #[must_use]
109 pub fn get_request(request_id: i32, oids: &[Oid]) -> Self {
110 Self {
111 pdu_type: PduType::GetRequest,
112 request_id,
113 error_status: 0,
114 error_index: 0,
115 varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
116 }
117 }
118
119 #[must_use]
121 pub fn get_next_request(request_id: i32, oids: &[Oid]) -> Self {
122 Self {
123 pdu_type: PduType::GetNextRequest,
124 request_id,
125 error_status: 0,
126 error_index: 0,
127 varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
128 }
129 }
130
131 #[must_use]
133 pub fn set_request(request_id: i32, varbinds: Vec<VarBind>) -> Self {
134 Self {
135 pdu_type: PduType::SetRequest,
136 request_id,
137 error_status: 0,
138 error_index: 0,
139 varbinds,
140 }
141 }
142
143 #[must_use]
151 pub fn trap_v2(request_id: i32, uptime: u32, trap_oid: &Oid, varbinds: Vec<VarBind>) -> Self {
152 let mut all_varbinds = Vec::with_capacity(2 + varbinds.len());
153 all_varbinds.push(VarBind::new(
154 crate::notification::oids::sys_uptime(),
155 crate::value::Value::TimeTicks(uptime),
156 ));
157 all_varbinds.push(VarBind::new(
158 crate::notification::oids::snmp_trap_oid(),
159 crate::value::Value::ObjectIdentifier(trap_oid.clone()),
160 ));
161 all_varbinds.extend(varbinds);
162 Self {
163 pdu_type: PduType::TrapV2,
164 request_id,
165 error_status: 0,
166 error_index: 0,
167 varbinds: all_varbinds,
168 }
169 }
170
171 #[must_use]
176 pub fn inform_request(
177 request_id: i32,
178 uptime: u32,
179 trap_oid: &Oid,
180 varbinds: Vec<VarBind>,
181 ) -> Self {
182 let mut all_varbinds = Vec::with_capacity(2 + varbinds.len());
183 all_varbinds.push(VarBind::new(
184 crate::notification::oids::sys_uptime(),
185 crate::value::Value::TimeTicks(uptime),
186 ));
187 all_varbinds.push(VarBind::new(
188 crate::notification::oids::snmp_trap_oid(),
189 crate::value::Value::ObjectIdentifier(trap_oid.clone()),
190 ));
191 all_varbinds.extend(varbinds);
192 Self {
193 pdu_type: PduType::InformRequest,
194 request_id,
195 error_status: 0,
196 error_index: 0,
197 varbinds: all_varbinds,
198 }
199 }
200
201 #[must_use]
207 pub fn get_bulk(
208 request_id: i32,
209 non_repeaters: i32,
210 max_repetitions: i32,
211 varbinds: Vec<VarBind>,
212 ) -> Self {
213 Self {
214 pdu_type: PduType::GetBulkRequest,
215 request_id,
216 error_status: clamp_bulk_field(non_repeaters),
217 error_index: clamp_bulk_field(max_repetitions),
218 varbinds,
219 }
220 }
221
222 pub fn encode(&self, buf: &mut EncodeBuf) {
224 buf.push_constructed(self.pdu_type.tag(), |buf| {
225 encode_varbind_list(buf, &self.varbinds);
226 buf.push_integer(self.error_index);
227 buf.push_integer(self.error_status);
228 buf.push_integer(self.request_id);
229 });
230 }
231
232 pub fn decode(decoder: &mut Decoder) -> Result<Self> {
234 let tag = decoder.read_tag()?;
235 let pdu_type = PduType::from_tag(tag).ok_or_else(|| {
236 tracing::debug!(target: "async_snmp::pdu", { offset = decoder.offset(), tag = tag, kind = %DecodeErrorKind::UnknownPduType(tag) }, "decode error");
237 Error::MalformedResponse {
238 target: UNKNOWN_TARGET,
239 }
240 .boxed()
241 })?;
242
243 let len = decoder.read_length()?;
244 let mut pdu_decoder = decoder.sub_decoder(len)?;
245
246 let request_id = pdu_decoder.read_integer()?;
247 let error_status = pdu_decoder.read_integer()?;
248 let error_index = pdu_decoder.read_integer()?;
249 let varbinds = decode_varbind_list(&mut pdu_decoder)?;
250
251 Ok(Pdu {
259 pdu_type,
260 request_id,
261 error_status,
262 error_index,
263 varbinds,
264 })
265 }
266
267 #[must_use]
269 pub fn is_error(&self) -> bool {
270 self.pdu_type == PduType::Response && self.error_status != 0
271 }
272
273 #[must_use]
275 pub fn error_status_enum(&self) -> ErrorStatus {
276 ErrorStatus::from_i32(self.error_status)
277 }
278
279 #[must_use]
284 pub fn to_response(&self) -> Self {
285 Self {
286 pdu_type: PduType::Response,
287 request_id: self.request_id,
288 error_status: 0,
289 error_index: 0,
290 varbinds: self.varbinds.clone(),
291 }
292 }
293
294 #[must_use]
296 pub fn to_error_response(&self, error_status: ErrorStatus, error_index: i32) -> Self {
297 Self {
298 pdu_type: PduType::Response,
299 request_id: self.request_id,
300 error_status: error_status.as_i32(),
301 error_index,
302 varbinds: self.varbinds.clone(),
303 }
304 }
305
306 #[must_use]
327 pub fn to_v1_trap(&self, default_addr: [u8; 4]) -> Option<TrapV1Pdu> {
328 use crate::notification::oids;
329 use crate::value::Value;
330
331 if self.varbinds.len() < 2 {
332 return None;
333 }
334
335 if self.varbinds[0].oid != oids::sys_uptime() {
338 return None;
339 }
340 if self.varbinds[1].oid != oids::snmp_trap_oid() {
341 return None;
342 }
343
344 let time_stamp = match &self.varbinds[0].value {
345 Value::TimeTicks(t) => *t,
346 _ => return None,
347 };
348
349 let Value::ObjectIdentifier(trap_oid) = &self.varbinds[1].value else {
350 return None;
351 };
352
353 for vb in &self.varbinds {
355 if matches!(vb.value, Value::Counter64(_)) {
356 return None;
357 }
358 }
359
360 let snmp_traps_prefix = oids::snmp_traps();
362 let (generic_trap, specific_trap, enterprise) = if trap_oid.starts_with(&snmp_traps_prefix)
363 && trap_oid.len() == snmp_traps_prefix.len() + 1
364 && (1..=6).contains(&trap_oid.arcs()[trap_oid.len() - 1])
365 {
366 let last_arc = trap_oid.arcs()[trap_oid.len() - 1];
368 let generic = GenericTrap::from_i32((last_arc - 1) as i32);
369 let enterprise_oid = oids::snmp_trap_enterprise();
372 let ent = self.varbinds[2..]
373 .iter()
374 .find(|vb| vb.oid == enterprise_oid)
375 .and_then(|vb| match &vb.value {
376 Value::ObjectIdentifier(oid) => Some(oid.clone()),
377 _ => None,
378 })
379 .unwrap_or_else(|| snmp_traps_prefix.clone());
380 (generic, 0, ent)
381 } else if trap_oid.len() >= 2 {
382 let arcs = trap_oid.arcs();
386 let specific = i32::try_from(arcs[arcs.len() - 1]).ok()?;
387 let next_to_last = arcs[arcs.len() - 2];
388 let enterprise = if next_to_last == 0 {
389 Oid::from_slice(&arcs[..arcs.len() - 2])
390 } else {
391 Oid::from_slice(&arcs[..arcs.len() - 1])
392 };
393 (GenericTrap::EnterpriseSpecific, specific, enterprise)
394 } else {
395 return None;
396 };
397
398 let trap_address_oid = oids::snmp_trap_address();
400 let agent_addr = self.varbinds[2..]
401 .iter()
402 .find(|vb| vb.oid == trap_address_oid)
403 .and_then(|vb| match &vb.value {
404 Value::IpAddress(addr) => Some(*addr),
405 _ => None,
406 })
407 .unwrap_or(default_addr);
408
409 let varbinds: Vec<VarBind> = self.varbinds[2..].to_vec();
413
414 Some(TrapV1Pdu {
415 enterprise,
416 agent_addr,
417 generic_trap,
418 specific_trap,
419 time_stamp,
420 varbinds,
421 })
422 }
423
424 #[must_use]
426 pub fn is_notification(&self) -> bool {
427 matches!(
428 self.pdu_type,
429 PduType::TrapV1 | PduType::TrapV2 | PduType::InformRequest
430 )
431 }
432
433 #[must_use]
435 pub fn is_confirmed(&self) -> bool {
436 matches!(
437 self.pdu_type,
438 PduType::GetRequest
439 | PduType::GetNextRequest
440 | PduType::GetBulkRequest
441 | PduType::SetRequest
442 | PduType::InformRequest
443 )
444 }
445}
446
447#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
449pub enum GenericTrap {
450 ColdStart,
452 WarmStart,
454 LinkDown,
456 LinkUp,
458 AuthenticationFailure,
460 EgpNeighborLoss,
462 EnterpriseSpecific,
464 Unknown(i32),
466}
467
468impl std::fmt::Display for GenericTrap {
469 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
470 match self {
471 Self::ColdStart => write!(f, "coldStart"),
472 Self::WarmStart => write!(f, "warmStart"),
473 Self::LinkDown => write!(f, "linkDown"),
474 Self::LinkUp => write!(f, "linkUp"),
475 Self::AuthenticationFailure => write!(f, "authenticationFailure"),
476 Self::EgpNeighborLoss => write!(f, "egpNeighborLoss"),
477 Self::EnterpriseSpecific => write!(f, "enterpriseSpecific"),
478 Self::Unknown(v) => write!(f, "unknown({v})"),
479 }
480 }
481}
482
483impl GenericTrap {
484 #[must_use]
486 pub fn from_i32(v: i32) -> Self {
487 match v {
488 0 => Self::ColdStart,
489 1 => Self::WarmStart,
490 2 => Self::LinkDown,
491 3 => Self::LinkUp,
492 4 => Self::AuthenticationFailure,
493 5 => Self::EgpNeighborLoss,
494 6 => Self::EnterpriseSpecific,
495 _ => Self::Unknown(v),
496 }
497 }
498
499 #[must_use]
501 pub fn as_i32(self) -> i32 {
502 match self {
503 Self::ColdStart => 0,
504 Self::WarmStart => 1,
505 Self::LinkDown => 2,
506 Self::LinkUp => 3,
507 Self::AuthenticationFailure => 4,
508 Self::EgpNeighborLoss => 5,
509 Self::EnterpriseSpecific => 6,
510 Self::Unknown(v) => v,
511 }
512 }
513}
514
515#[derive(Debug, Clone, PartialEq, Eq)]
520pub struct TrapV1Pdu {
521 pub enterprise: Oid,
523 pub agent_addr: [u8; 4],
525 pub generic_trap: GenericTrap,
527 pub specific_trap: i32,
529 pub time_stamp: u32,
531 pub varbinds: Vec<VarBind>,
533}
534
535impl TrapV1Pdu {
536 #[must_use]
538 pub fn new(
539 enterprise: Oid,
540 agent_addr: [u8; 4],
541 generic_trap: GenericTrap,
542 specific_trap: i32,
543 time_stamp: u32,
544 varbinds: Vec<VarBind>,
545 ) -> Self {
546 Self {
547 enterprise,
548 agent_addr,
549 generic_trap,
550 specific_trap,
551 time_stamp,
552 varbinds,
553 }
554 }
555
556 #[must_use]
558 pub fn is_enterprise_specific(&self) -> bool {
559 self.generic_trap == GenericTrap::EnterpriseSpecific
560 }
561
562 pub fn v2_trap_oid(&self) -> crate::Result<Oid> {
609 if self.is_enterprise_specific() {
610 if self.specific_trap < 0 {
611 return Err(Error::InvalidOid("specific_trap cannot be negative".into()).boxed());
612 }
613 let mut arcs: Vec<u32> = self.enterprise.arcs().to_vec();
614 arcs.push(0);
615 arcs.push(self.specific_trap as u32);
616 Ok(Oid::new(arcs))
617 } else {
618 let raw = self.generic_trap.as_i32();
619 if raw < 0 {
620 return Err(Error::InvalidOid("generic_trap cannot be negative".into()).boxed());
621 }
622 if raw == i32::MAX {
623 return Err(Error::InvalidOid("generic_trap overflow".into()).boxed());
624 }
625 let trap_num = raw + 1;
626 Ok(crate::oid!(1, 3, 6, 1, 6, 3, 1, 1, 5).child(trap_num as u32))
627 }
628 }
629
630 pub fn to_v2_pdu(&self) -> crate::Result<Pdu> {
644 use crate::notification::oids;
645 use crate::value::Value;
646
647 let trap_oid = self.v2_trap_oid()?;
648
649 let mut varbinds = Vec::with_capacity(2 + self.varbinds.len());
650 varbinds.push(VarBind::new(
651 oids::sys_uptime(),
652 Value::TimeTicks(self.time_stamp),
653 ));
654 varbinds.push(VarBind::new(
655 oids::snmp_trap_oid(),
656 Value::ObjectIdentifier(trap_oid),
657 ));
658 varbinds.extend_from_slice(&self.varbinds);
659
660 Ok(Pdu {
661 pdu_type: PduType::TrapV2,
662 request_id: 0,
663 error_status: 0,
664 error_index: 0,
665 varbinds,
666 })
667 }
668
669 pub fn encode(&self, buf: &mut EncodeBuf) {
671 buf.push_constructed(tag::pdu::TRAP_V1, |buf| {
672 encode_varbind_list(buf, &self.varbinds);
673 buf.push_unsigned32(tag::application::TIMETICKS, self.time_stamp);
674 buf.push_integer(self.specific_trap);
675 buf.push_integer(self.generic_trap.as_i32());
676 buf.push_bytes(&self.agent_addr);
679 buf.push_length(4);
680 buf.push_tag(tag::application::IP_ADDRESS);
681 buf.push_oid(&self.enterprise);
682 });
683 }
684
685 pub fn decode(decoder: &mut Decoder) -> Result<Self> {
687 let mut pdu = decoder.read_constructed(tag::pdu::TRAP_V1)?;
688
689 let enterprise = pdu.read_oid()?;
691
692 let agent_tag = pdu.read_tag()?;
694 if agent_tag != tag::application::IP_ADDRESS {
695 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), expected = 0x40_u8, actual = agent_tag, kind = %DecodeErrorKind::UnexpectedTag {
696 expected: 0x40,
697 actual: agent_tag,
698 } }, "decode error");
699 return Err(Error::MalformedResponse {
700 target: UNKNOWN_TARGET,
701 }
702 .boxed());
703 }
704 let agent_len = pdu.read_length()?;
705 if agent_len != 4 {
706 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), length = agent_len, kind = %DecodeErrorKind::InvalidIpAddressLength { length: agent_len } }, "decode error");
707 return Err(Error::MalformedResponse {
708 target: UNKNOWN_TARGET,
709 }
710 .boxed());
711 }
712 let agent_bytes = pdu.read_bytes(4)?;
713 let agent_addr = [
714 agent_bytes[0],
715 agent_bytes[1],
716 agent_bytes[2],
717 agent_bytes[3],
718 ];
719
720 let generic_trap = GenericTrap::from_i32(pdu.read_integer()?);
722
723 let specific_trap = pdu.read_integer()?;
725
726 let ts_tag = pdu.read_tag()?;
728 if ts_tag != tag::application::TIMETICKS {
729 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), expected = 0x43_u8, actual = ts_tag, kind = %DecodeErrorKind::UnexpectedTag {
730 expected: 0x43,
731 actual: ts_tag,
732 } }, "decode error");
733 return Err(Error::MalformedResponse {
734 target: UNKNOWN_TARGET,
735 }
736 .boxed());
737 }
738 let ts_len = pdu.read_length()?;
739 let time_stamp = pdu.read_unsigned32_value(ts_len)?;
740
741 let varbinds = decode_varbind_list(&mut pdu)?;
743
744 Ok(TrapV1Pdu {
745 enterprise,
746 agent_addr,
747 generic_trap,
748 specific_trap,
749 time_stamp,
750 varbinds,
751 })
752 }
753}
754
755#[derive(Debug, Clone)]
757pub struct GetBulkPdu {
758 pub request_id: i32,
760 pub non_repeaters: i32,
762 pub max_repetitions: i32,
764 pub varbinds: Vec<VarBind>,
766}
767
768impl GetBulkPdu {
769 #[must_use]
771 pub fn new(request_id: i32, non_repeaters: i32, max_repetitions: i32, oids: &[Oid]) -> Self {
772 Self {
773 request_id,
774 non_repeaters,
775 max_repetitions,
776 varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
777 }
778 }
779
780 pub fn encode(&self, buf: &mut EncodeBuf) {
782 buf.push_constructed(tag::pdu::GET_BULK_REQUEST, |buf| {
783 encode_varbind_list(buf, &self.varbinds);
784 buf.push_integer(clamp_bulk_field(self.max_repetitions));
787 buf.push_integer(clamp_bulk_field(self.non_repeaters));
788 buf.push_integer(self.request_id);
789 });
790 }
791
792 pub fn decode(decoder: &mut Decoder) -> Result<Self> {
794 let mut pdu = decoder.read_constructed(tag::pdu::GET_BULK_REQUEST)?;
795
796 let request_id = pdu.read_integer()?;
797 let non_repeaters = pdu.read_integer()?;
798 let max_repetitions = pdu.read_integer()?;
799 let varbinds = decode_varbind_list(&mut pdu)?;
800
801 if non_repeaters < 0 {
803 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), non_repeaters = non_repeaters, kind = %DecodeErrorKind::NegativeNonRepeaters {
804 value: non_repeaters,
805 } }, "decode error");
806 return Err(Error::MalformedResponse {
807 target: UNKNOWN_TARGET,
808 }
809 .boxed());
810 }
811 if max_repetitions < 0 {
812 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), max_repetitions = max_repetitions, kind = %DecodeErrorKind::NegativeMaxRepetitions {
813 value: max_repetitions,
814 } }, "decode error");
815 return Err(Error::MalformedResponse {
816 target: UNKNOWN_TARGET,
817 }
818 .boxed());
819 }
820
821 Ok(GetBulkPdu {
822 request_id,
823 non_repeaters,
824 max_repetitions,
825 varbinds,
826 })
827 }
828}
829
830#[cfg(test)]
831mod tests {
832 use super::*;
833 use crate::oid;
834
835 struct RawPdu {
840 pdu_type: u8,
841 request_id: i32,
842 error_status: i32,
843 error_index: i32,
844 varbinds: Vec<VarBind>,
845 }
846
847 impl RawPdu {
848 fn response(
849 request_id: i32,
850 error_status: i32,
851 error_index: i32,
852 varbinds: Vec<VarBind>,
853 ) -> Self {
854 Self {
855 pdu_type: PduType::Response.tag(),
856 request_id,
857 error_status,
858 error_index,
859 varbinds,
860 }
861 }
862
863 fn encode(&self) -> bytes::Bytes {
864 let mut buf = EncodeBuf::new();
865 buf.push_constructed(self.pdu_type, |buf| {
866 encode_varbind_list(buf, &self.varbinds);
867 buf.push_integer(self.error_index);
868 buf.push_integer(self.error_status);
869 buf.push_integer(self.request_id);
870 });
871 buf.finish()
872 }
873 }
874
875 struct RawGetBulkPdu {
877 request_id: i32,
878 non_repeaters: i32,
879 max_repetitions: i32,
880 varbinds: Vec<VarBind>,
881 }
882
883 impl RawGetBulkPdu {
884 fn new(
885 request_id: i32,
886 non_repeaters: i32,
887 max_repetitions: i32,
888 varbinds: Vec<VarBind>,
889 ) -> Self {
890 Self {
891 request_id,
892 non_repeaters,
893 max_repetitions,
894 varbinds,
895 }
896 }
897
898 fn encode(&self) -> bytes::Bytes {
899 let mut buf = EncodeBuf::new();
900 buf.push_constructed(tag::pdu::GET_BULK_REQUEST, |buf| {
901 encode_varbind_list(buf, &self.varbinds);
902 buf.push_integer(self.max_repetitions);
903 buf.push_integer(self.non_repeaters);
904 buf.push_integer(self.request_id);
905 });
906 buf.finish()
907 }
908 }
909
910 #[test]
911 fn test_get_request_roundtrip() {
912 let pdu = Pdu::get_request(12345, &[oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)]);
913
914 let mut buf = EncodeBuf::new();
915 pdu.encode(&mut buf);
916 let bytes = buf.finish();
917
918 let mut decoder = Decoder::new(bytes);
919 let decoded = Pdu::decode(&mut decoder).unwrap();
920
921 assert_eq!(decoded.pdu_type, PduType::GetRequest);
922 assert_eq!(decoded.request_id, 12345);
923 assert_eq!(decoded.varbinds.len(), 1);
924 }
925
926 #[test]
927 fn test_getbulk_roundtrip() {
928 let pdu = GetBulkPdu::new(12345, 0, 10, &[oid!(1, 3, 6, 1, 2, 1, 1)]);
929
930 let mut buf = EncodeBuf::new();
931 pdu.encode(&mut buf);
932 let bytes = buf.finish();
933
934 let mut decoder = Decoder::new(bytes);
935 let decoded = GetBulkPdu::decode(&mut decoder).unwrap();
936
937 assert_eq!(decoded.request_id, 12345);
938 assert_eq!(decoded.non_repeaters, 0);
939 assert_eq!(decoded.max_repetitions, 10);
940 }
941
942 #[test]
943 fn test_trap_v1_roundtrip() {
944 use crate::value::Value;
945 use crate::varbind::VarBind;
946
947 let trap = TrapV1Pdu::new(
948 oid!(1, 3, 6, 1, 4, 1, 9999), [192, 168, 1, 1], GenericTrap::LinkDown,
951 0,
952 1234_5678, vec![VarBind::new(
954 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
955 Value::Integer(1),
956 )],
957 );
958
959 let mut buf = EncodeBuf::new();
960 trap.encode(&mut buf);
961 let bytes = buf.finish();
962
963 let mut decoder = Decoder::new(bytes);
964 let decoded = TrapV1Pdu::decode(&mut decoder).unwrap();
965
966 assert_eq!(decoded.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999));
967 assert_eq!(decoded.agent_addr, [192, 168, 1, 1]);
968 assert_eq!(decoded.generic_trap, GenericTrap::LinkDown);
969 assert_eq!(decoded.specific_trap, 0);
970 assert_eq!(decoded.time_stamp, 1234_5678);
971 assert_eq!(decoded.varbinds.len(), 1);
972 }
973
974 #[test]
975 fn test_trap_v1_enterprise_specific() {
976 let trap = TrapV1Pdu::new(
977 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
978 [10, 0, 0, 1],
979 GenericTrap::EnterpriseSpecific,
980 42, 100,
982 vec![],
983 );
984
985 assert!(trap.is_enterprise_specific());
986 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
987
988 let mut buf = EncodeBuf::new();
989 trap.encode(&mut buf);
990 let bytes = buf.finish();
991
992 let mut decoder = Decoder::new(bytes);
993 let decoded = TrapV1Pdu::decode(&mut decoder).unwrap();
994
995 assert_eq!(decoded.specific_trap, 42);
996 }
997
998 #[test]
999 fn test_trap_v1_v2_trap_oid_generic_traps() {
1000 let test_cases = [
1004 (GenericTrap::ColdStart, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1)),
1005 (GenericTrap::WarmStart, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 2)),
1006 (GenericTrap::LinkDown, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3)),
1007 (GenericTrap::LinkUp, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 4)),
1008 (
1009 GenericTrap::AuthenticationFailure,
1010 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 5),
1011 ),
1012 (
1013 GenericTrap::EgpNeighborLoss,
1014 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 6),
1015 ),
1016 ];
1017
1018 for (generic_trap, expected_oid) in test_cases {
1019 let trap = TrapV1Pdu::new(
1020 oid!(1, 3, 6, 1, 4, 1, 9999),
1021 [192, 168, 1, 1],
1022 generic_trap,
1023 0,
1024 12345,
1025 vec![],
1026 );
1027 assert_eq!(
1028 trap.v2_trap_oid().unwrap(),
1029 expected_oid,
1030 "Failed for {generic_trap:?}"
1031 );
1032 }
1033 }
1034
1035 #[test]
1036 fn test_trap_v1_v2_trap_oid_enterprise_specific() {
1037 let trap = TrapV1Pdu::new(
1039 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
1040 [192, 168, 1, 1],
1041 GenericTrap::EnterpriseSpecific,
1042 42,
1043 12345,
1044 vec![],
1045 );
1046
1047 assert_eq!(
1049 trap.v2_trap_oid().unwrap(),
1050 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2, 0, 42)
1051 );
1052 }
1053
1054 #[test]
1055 fn test_trap_v1_v2_trap_oid_enterprise_specific_zero() {
1056 let trap = TrapV1Pdu::new(
1058 oid!(1, 3, 6, 1, 4, 1, 1234),
1059 [10, 0, 0, 1],
1060 GenericTrap::EnterpriseSpecific,
1061 0,
1062 100,
1063 vec![],
1064 );
1065
1066 assert_eq!(
1068 trap.v2_trap_oid().unwrap(),
1069 oid!(1, 3, 6, 1, 4, 1, 1234, 0, 0)
1070 );
1071 }
1072
1073 #[test]
1074 fn test_pdu_to_response() {
1075 use crate::value::Value;
1076 use crate::varbind::VarBind;
1077
1078 let inform = Pdu {
1079 pdu_type: PduType::InformRequest,
1080 request_id: 99999,
1081 error_status: 0,
1082 error_index: 0,
1083 varbinds: vec![
1084 VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::TimeTicks(12345)),
1085 VarBind::new(
1086 oid!(1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0),
1087 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1)),
1088 ),
1089 ],
1090 };
1091
1092 let response = inform.to_response();
1093
1094 assert_eq!(response.pdu_type, PduType::Response);
1095 assert_eq!(response.request_id, 99999);
1096 assert_eq!(response.error_status, 0);
1097 assert_eq!(response.error_index, 0);
1098 assert_eq!(response.varbinds.len(), 2);
1099 }
1100
1101 #[test]
1102 fn test_pdu_is_confirmed() {
1103 let get = Pdu::get_request(1, &[oid!(1, 3, 6, 1)]);
1104 assert!(get.is_confirmed());
1105
1106 let inform = Pdu {
1107 pdu_type: PduType::InformRequest,
1108 request_id: 1,
1109 error_status: 0,
1110 error_index: 0,
1111 varbinds: vec![],
1112 };
1113 assert!(inform.is_confirmed());
1114
1115 let trap = Pdu {
1116 pdu_type: PduType::TrapV2,
1117 request_id: 1,
1118 error_status: 0,
1119 error_index: 0,
1120 varbinds: vec![],
1121 };
1122 assert!(!trap.is_confirmed());
1123 assert!(trap.is_notification());
1124 }
1125
1126 #[test]
1127 fn test_decode_accepts_negative_error_index() {
1128 let raw = RawPdu::response(1, 0, -1, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1134 let encoded = raw.encode();
1135
1136 let mut decoder = Decoder::new(encoded);
1137 let result = Pdu::decode(&mut decoder);
1138
1139 assert!(
1140 result.is_ok(),
1141 "negative error_index must be accepted to match net-snmp behavior, got {:?}",
1142 result.err()
1143 );
1144 assert_eq!(result.unwrap().error_index, -1);
1145 }
1146
1147 #[test]
1148 fn test_decode_accepts_error_index_beyond_varbinds() {
1149 let raw = RawPdu::response(1, 5, 5, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1155 let encoded = raw.encode();
1156
1157 let mut decoder = Decoder::new(encoded);
1158 let result = Pdu::decode(&mut decoder);
1159
1160 assert!(
1161 result.is_ok(),
1162 "error_index beyond varbind count must be accepted to match net-snmp behavior, got {:?}",
1163 result.err()
1164 );
1165 assert_eq!(result.unwrap().error_index, 5);
1166 }
1167
1168 #[test]
1169 fn test_decode_accepts_valid_error_index_zero() {
1170 let raw = RawPdu::response(1, 0, 0, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1172 let encoded = raw.encode();
1173
1174 let mut decoder = Decoder::new(encoded);
1175 let decoded = Pdu::decode(&mut decoder);
1176 assert!(decoded.is_ok(), "error_index=0 should be valid");
1177 }
1178
1179 #[test]
1180 fn test_decode_accepts_error_index_within_bounds() {
1181 let raw = RawPdu::response(1, 5, 1, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1183 let encoded = raw.encode();
1184
1185 let mut decoder = Decoder::new(encoded);
1186 let result = Pdu::decode(&mut decoder);
1187 assert!(
1188 result.is_ok(),
1189 "error_index=1 with 1 varbind should be valid"
1190 );
1191 }
1192
1193 #[test]
1194 fn test_decode_rejects_negative_non_repeaters() {
1195 let raw = RawGetBulkPdu::new(1, -1, 10, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1196 let encoded = raw.encode();
1197
1198 let mut decoder = Decoder::new(encoded);
1199 let result = GetBulkPdu::decode(&mut decoder);
1200
1201 assert!(result.is_err(), "should reject negative non_repeaters");
1202 let err = result.unwrap_err();
1203 assert!(
1204 matches!(&*err, crate::error::Error::MalformedResponse { .. }),
1205 "expected MalformedResponse, got {err:?}"
1206 );
1207 }
1208
1209 #[test]
1210 fn test_decode_rejects_negative_max_repetitions() {
1211 let raw = RawGetBulkPdu::new(1, 0, -5, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1212 let encoded = raw.encode();
1213
1214 let mut decoder = Decoder::new(encoded);
1215 let result = GetBulkPdu::decode(&mut decoder);
1216
1217 assert!(result.is_err(), "should reject negative max_repetitions");
1218 let err = result.unwrap_err();
1219 assert!(
1220 matches!(&*err, crate::error::Error::MalformedResponse { .. }),
1221 "expected MalformedResponse, got {err:?}"
1222 );
1223 }
1224
1225 #[test]
1226 fn test_decode_accepts_valid_getbulk_params() {
1227 let raw = RawGetBulkPdu::new(1, 0, 10, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1228 let encoded = raw.encode();
1229
1230 let mut decoder = Decoder::new(encoded);
1231 let result = GetBulkPdu::decode(&mut decoder);
1232 assert!(result.is_ok(), "valid GETBULK params should be accepted");
1233
1234 let pdu = result.unwrap();
1235 assert_eq!(pdu.non_repeaters, 0);
1236 assert_eq!(pdu.max_repetitions, 10);
1237 }
1238
1239 #[test]
1240 fn test_encode_clamps_negative_non_repeaters_and_max_repetitions() {
1241 let pdu = GetBulkPdu::new(1, -1, -5, &[oid!(1, 3, 6, 1)]);
1247
1248 let mut buf = EncodeBuf::new();
1249 pdu.encode(&mut buf);
1250 let bytes = buf.finish();
1251
1252 let mut decoder = Decoder::new(bytes);
1253 let result = GetBulkPdu::decode(&mut decoder);
1254 assert!(
1255 result.is_ok(),
1256 "encoded negative non_repeaters/max_repetitions should decode after clamping, got {result:?}"
1257 );
1258 let decoded = result.unwrap();
1259 assert_eq!(decoded.non_repeaters, 0);
1260 assert_eq!(decoded.max_repetitions, 0);
1261 }
1262
1263 #[test]
1264 fn test_encode_leaves_non_negative_non_repeaters_and_max_repetitions_unchanged() {
1265 let pdu = GetBulkPdu::new(1, 0, 10, &[oid!(1, 3, 6, 1)]);
1266
1267 let mut buf = EncodeBuf::new();
1268 pdu.encode(&mut buf);
1269 let bytes = buf.finish();
1270
1271 let mut decoder = Decoder::new(bytes);
1272 let decoded = GetBulkPdu::decode(&mut decoder).unwrap();
1273 assert_eq!(decoded.non_repeaters, 0);
1274 assert_eq!(decoded.max_repetitions, 10);
1275 }
1276
1277 #[test]
1278 fn test_generic_pdu_get_bulk_clamps_negative_fields() {
1279 let pdu = Pdu::get_bulk(1, -1, -5, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1285 assert_eq!(pdu.error_status, 0);
1286 assert_eq!(pdu.error_index, 0);
1287
1288 let mut buf = EncodeBuf::new();
1289 pdu.encode(&mut buf);
1290 let bytes = buf.finish();
1291
1292 let mut decoder = Decoder::new(bytes);
1293 let decoded = GetBulkPdu::decode(&mut decoder)
1294 .expect("clamped generic GETBULK encode should decode as valid");
1295 assert_eq!(decoded.non_repeaters, 0);
1296 assert_eq!(decoded.max_repetitions, 0);
1297 }
1298
1299 #[test]
1300 fn test_pdu_decode_getbulk_with_large_max_repetitions() {
1301 let raw = RawGetBulkPdu::new(12345, 0, 25, vec![VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1))]);
1305 let encoded = raw.encode();
1306
1307 let mut decoder = Decoder::new(encoded);
1308 let result = Pdu::decode(&mut decoder);
1309 assert!(
1310 result.is_ok(),
1311 "Pdu::decode should accept GETBULK with max_repetitions > varbinds.len(), got {:?}",
1312 result.err()
1313 );
1314
1315 let pdu = result.unwrap();
1316 assert_eq!(pdu.pdu_type, PduType::GetBulkRequest);
1317 assert_eq!(pdu.request_id, 12345);
1318 assert_eq!(pdu.error_status, 0);
1320 assert_eq!(pdu.error_index, 25);
1321 assert_eq!(pdu.varbinds.len(), 1);
1322 }
1323
1324 #[test]
1325 fn test_getbulk_request_is_not_treated_as_error() {
1326 let pdu = Pdu::get_bulk(
1327 12345,
1328 2,
1329 10,
1330 vec![
1331 VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1)),
1332 VarBind::null(oid!(1, 3, 6, 1, 2, 1, 2)),
1333 ],
1334 );
1335
1336 assert!(!pdu.is_error());
1337 }
1338
1339 #[test]
1340 fn test_response_with_error_status_is_treated_as_error() {
1341 let pdu = Pdu {
1342 pdu_type: PduType::Response,
1343 request_id: 12345,
1344 error_status: ErrorStatus::TooBig.as_i32(),
1345 error_index: 1,
1346 varbinds: vec![VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1))],
1347 };
1348
1349 assert!(pdu.is_error());
1350 }
1351
1352 #[test]
1353 fn pdu_type_hash() {
1354 use std::collections::HashSet;
1355 let mut set = HashSet::new();
1356 set.insert(PduType::GetRequest);
1357 set.insert(PduType::GetNextRequest);
1358 assert_eq!(set.len(), 2);
1359 assert!(set.contains(&PduType::GetRequest));
1360 }
1361
1362 #[test]
1367 fn test_v1_to_v2_generic_trap() {
1368 use crate::value::Value;
1369 use crate::varbind::VarBind;
1370
1371 let trap = TrapV1Pdu::new(
1372 oid!(1, 3, 6, 1, 4, 1, 9999),
1373 [192, 168, 1, 1],
1374 GenericTrap::LinkDown,
1375 0,
1376 12345,
1377 vec![VarBind::new(
1378 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
1379 Value::Integer(1),
1380 )],
1381 );
1382
1383 let pdu = trap.to_v2_pdu().unwrap();
1384
1385 assert_eq!(pdu.pdu_type, PduType::TrapV2);
1386 assert_eq!(pdu.request_id, 0);
1387 assert_eq!(pdu.varbinds.len(), 3);
1389
1390 assert_eq!(pdu.varbinds[0].oid, oid!(1, 3, 6, 1, 2, 1, 1, 3, 0));
1392 assert_eq!(pdu.varbinds[0].value, Value::TimeTicks(12345));
1393
1394 assert_eq!(pdu.varbinds[1].oid, oid!(1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0));
1396 assert_eq!(
1397 pdu.varbinds[1].value,
1398 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3))
1399 );
1400
1401 assert_eq!(pdu.varbinds[2].oid, oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1));
1403 }
1404
1405 #[test]
1406 fn test_v1_to_v2_no_proxy_varbinds() {
1407 let trap = TrapV1Pdu::new(
1408 oid!(1, 3, 6, 1, 4, 1, 9999),
1409 [192, 168, 1, 1],
1410 GenericTrap::ColdStart,
1411 0,
1412 100,
1413 vec![],
1414 );
1415
1416 let pdu = trap.to_v2_pdu().unwrap();
1417 assert_eq!(pdu.varbinds.len(), 2);
1420 }
1421
1422 #[test]
1423 fn test_v1_to_v2_enterprise_specific() {
1424 use crate::value::Value;
1425
1426 let trap = TrapV1Pdu::new(
1427 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
1428 [10, 0, 0, 1],
1429 GenericTrap::EnterpriseSpecific,
1430 42,
1431 5000,
1432 vec![],
1433 );
1434
1435 let pdu = trap.to_v2_pdu().unwrap();
1436
1437 assert_eq!(
1439 pdu.varbinds[1].value,
1440 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2, 0, 42))
1441 );
1442 }
1443
1444 #[test]
1445 fn test_v2_to_v1_standard_trap() {
1446 use crate::value::Value;
1447 use crate::varbind::VarBind;
1448
1449 let pdu = Pdu::trap_v2(
1450 1,
1451 5000,
1452 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3), vec![VarBind::new(
1454 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
1455 Value::Integer(1),
1456 )],
1457 );
1458
1459 let trap = pdu.to_v1_trap([10, 0, 0, 1]).unwrap();
1460
1461 assert_eq!(trap.generic_trap, GenericTrap::LinkDown);
1462 assert_eq!(trap.specific_trap, 0);
1463 assert_eq!(trap.time_stamp, 5000);
1464 assert_eq!(trap.agent_addr, [10, 0, 0, 1]);
1465 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5));
1467 assert_eq!(trap.varbinds.len(), 1);
1468 }
1469
1470 #[test]
1471 fn test_v2_to_v1_enterprise_specific_trap() {
1472 let pdu = Pdu::trap_v2(1, 100, &oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2, 0, 42), vec![]);
1473
1474 let trap = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1475
1476 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
1477 assert_eq!(trap.specific_trap, 42);
1478 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2));
1479 assert_eq!(trap.time_stamp, 100);
1480 }
1481
1482 #[test]
1483 fn test_v2_to_v1_enterprise_specific_nonzero_penultimate() {
1484 let pdu = Pdu::trap_v2(1, 200, &oid!(1, 3, 6, 1, 4, 1, 9999, 1, 42), vec![]);
1487
1488 let trap = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1489
1490 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
1491 assert_eq!(trap.specific_trap, 42);
1492 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999, 1));
1494 assert_eq!(trap.time_stamp, 200);
1495 }
1496
1497 #[test]
1498 fn test_v2_to_v1_snmp_traps_arc_out_of_range() {
1499 for arc in [0u32, 7, 9] {
1503 let pdu = Pdu::trap_v2(1, 100, &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, arc), vec![]);
1504
1505 let trap = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1506
1507 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
1508 assert_eq!(trap.specific_trap, i32::try_from(arc).unwrap());
1509 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5));
1510 }
1511 }
1512
1513 #[test]
1514 fn test_v2_to_v1_extracts_trap_address() {
1515 use crate::notification::oids;
1516 use crate::value::Value;
1517 use crate::varbind::VarBind;
1518
1519 let pdu = Pdu::trap_v2(
1520 1,
1521 0,
1522 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1), vec![VarBind::new(
1524 oids::snmp_trap_address(),
1525 Value::IpAddress([192, 168, 1, 1]),
1526 )],
1527 );
1528
1529 let trap = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1530 assert_eq!(trap.agent_addr, [192, 168, 1, 1]);
1531 assert_eq!(trap.varbinds.len(), 1);
1534 assert_eq!(trap.varbinds[0].oid, oids::snmp_trap_address());
1535 }
1536
1537 #[test]
1538 fn test_v2_to_v1_extracts_trap_enterprise() {
1539 use crate::notification::oids;
1540 use crate::value::Value;
1541 use crate::varbind::VarBind;
1542
1543 let pdu = Pdu::trap_v2(
1544 1,
1545 0,
1546 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1), vec![VarBind::new(
1548 oids::snmp_trap_enterprise(),
1549 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4, 1, 9999)),
1550 )],
1551 );
1552
1553 let trap = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1554 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999));
1556 assert_eq!(trap.varbinds.len(), 1);
1559 assert_eq!(trap.varbinds[0].oid, oids::snmp_trap_enterprise());
1560 }
1561
1562 #[test]
1563 fn test_v2_to_v1_counter64_dropped() {
1564 use crate::value::Value;
1565 use crate::varbind::VarBind;
1566
1567 let pdu = Pdu::trap_v2(
1568 1,
1569 0,
1570 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1),
1571 vec![VarBind::new(
1572 oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
1573 Value::Counter64(12345),
1574 )],
1575 );
1576
1577 assert!(pdu.to_v1_trap([0, 0, 0, 0]).is_none());
1579 }
1580
1581 #[test]
1582 fn test_v2_to_v1_too_few_varbinds() {
1583 let pdu = Pdu {
1584 pdu_type: PduType::TrapV2,
1585 request_id: 1,
1586 error_status: 0,
1587 error_index: 0,
1588 varbinds: vec![],
1589 };
1590
1591 assert!(pdu.to_v1_trap([0, 0, 0, 0]).is_none());
1592 }
1593
1594 #[test]
1595 fn test_v1_v2_roundtrip_enterprise_specific() {
1596 use crate::value::Value;
1597 use crate::varbind::VarBind;
1598
1599 let original = TrapV1Pdu::new(
1604 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
1605 [192, 168, 1, 1],
1606 GenericTrap::EnterpriseSpecific,
1607 42,
1608 12345,
1609 vec![VarBind::new(
1610 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
1611 Value::Integer(1),
1612 )],
1613 );
1614
1615 let v2 = original.to_v2_pdu().unwrap();
1616 let restored = v2.to_v1_trap([0, 0, 0, 0]).unwrap();
1617
1618 assert_eq!(restored.enterprise, original.enterprise);
1619 assert_eq!(restored.generic_trap, original.generic_trap);
1620 assert_eq!(restored.specific_trap, original.specific_trap);
1621 assert_eq!(restored.time_stamp, original.time_stamp);
1622 assert_eq!(restored.varbinds.len(), original.varbinds.len());
1623 assert_eq!(restored.varbinds[0].oid, original.varbinds[0].oid);
1624 assert_eq!(restored.agent_addr, [0, 0, 0, 0]);
1626 }
1627
1628 #[test]
1629 fn test_v1_v2_roundtrip_standard_trap() {
1630 let original = TrapV1Pdu::new(
1634 oid!(1, 3, 6, 1, 4, 1, 9999),
1635 [10, 0, 0, 1],
1636 GenericTrap::WarmStart,
1637 0,
1638 500,
1639 vec![],
1640 );
1641
1642 let v2 = original.to_v2_pdu().unwrap();
1643 let restored = v2.to_v1_trap([10, 0, 0, 1]).unwrap();
1644
1645 assert_eq!(restored.generic_trap, GenericTrap::WarmStart);
1646 assert_eq!(restored.specific_trap, 0);
1647 assert_eq!(restored.time_stamp, 500);
1648 assert_eq!(restored.agent_addr, [10, 0, 0, 1]); assert_eq!(restored.enterprise, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5));
1651 }
1652
1653 #[test]
1654 fn test_v2_to_v1_all_generic_traps() {
1655 let traps = [
1657 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1), GenericTrap::ColdStart),
1658 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 2), GenericTrap::WarmStart),
1659 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3), GenericTrap::LinkDown),
1660 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 4), GenericTrap::LinkUp),
1661 (
1662 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 5),
1663 GenericTrap::AuthenticationFailure,
1664 ),
1665 (
1666 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 6),
1667 GenericTrap::EgpNeighborLoss,
1668 ),
1669 ];
1670
1671 for (trap_oid, expected_generic) in traps {
1672 let pdu = Pdu::trap_v2(1, 100, &trap_oid, vec![]);
1673 let v1 = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1674 assert_eq!(v1.generic_trap, expected_generic, "Failed for {trap_oid:?}");
1675 assert_eq!(v1.specific_trap, 0);
1676 }
1677 }
1678}