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 {
26 if value < 0 { 0 } else { value }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31#[repr(u8)]
32pub enum PduType {
33 GetRequest = 0xA0,
35 GetNextRequest = 0xA1,
37 Response = 0xA2,
39 SetRequest = 0xA3,
41 TrapV1 = 0xA4,
43 GetBulkRequest = 0xA5,
45 InformRequest = 0xA6,
47 TrapV2 = 0xA7,
49 Report = 0xA8,
51}
52
53impl PduType {
54 #[must_use]
56 pub fn from_tag(tag: u8) -> Option<Self> {
57 match tag {
58 0xA0 => Some(Self::GetRequest),
59 0xA1 => Some(Self::GetNextRequest),
60 0xA2 => Some(Self::Response),
61 0xA3 => Some(Self::SetRequest),
62 0xA4 => Some(Self::TrapV1),
63 0xA5 => Some(Self::GetBulkRequest),
64 0xA6 => Some(Self::InformRequest),
65 0xA7 => Some(Self::TrapV2),
66 0xA8 => Some(Self::Report),
67 _ => None,
68 }
69 }
70
71 #[must_use]
73 pub fn tag(self) -> u8 {
74 self as u8
75 }
76}
77
78impl std::fmt::Display for PduType {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 match self {
81 Self::GetRequest => write!(f, "GetRequest"),
82 Self::GetNextRequest => write!(f, "GetNextRequest"),
83 Self::Response => write!(f, "Response"),
84 Self::SetRequest => write!(f, "SetRequest"),
85 Self::TrapV1 => write!(f, "TrapV1"),
86 Self::GetBulkRequest => write!(f, "GetBulkRequest"),
87 Self::InformRequest => write!(f, "InformRequest"),
88 Self::TrapV2 => write!(f, "TrapV2"),
89 Self::Report => write!(f, "Report"),
90 }
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct Pdu {
97 pub pdu_type: PduType,
99 pub request_id: i32,
101 pub error_status: i32,
103 pub error_index: i32,
105 pub varbinds: Vec<VarBind>,
107}
108
109impl Pdu {
110 #[must_use]
112 pub fn get_request(request_id: i32, oids: &[Oid]) -> Self {
113 Self {
114 pdu_type: PduType::GetRequest,
115 request_id,
116 error_status: 0,
117 error_index: 0,
118 varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
119 }
120 }
121
122 #[must_use]
124 pub fn get_next_request(request_id: i32, oids: &[Oid]) -> Self {
125 Self {
126 pdu_type: PduType::GetNextRequest,
127 request_id,
128 error_status: 0,
129 error_index: 0,
130 varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
131 }
132 }
133
134 #[must_use]
136 pub fn set_request(request_id: i32, varbinds: Vec<VarBind>) -> Self {
137 Self {
138 pdu_type: PduType::SetRequest,
139 request_id,
140 error_status: 0,
141 error_index: 0,
142 varbinds,
143 }
144 }
145
146 #[must_use]
154 pub fn trap_v2(request_id: i32, uptime: u32, trap_oid: &Oid, varbinds: Vec<VarBind>) -> Self {
155 let mut all_varbinds = Vec::with_capacity(2 + varbinds.len());
156 all_varbinds.push(VarBind::new(
157 crate::notification::oids::sys_uptime(),
158 crate::value::Value::TimeTicks(uptime),
159 ));
160 all_varbinds.push(VarBind::new(
161 crate::notification::oids::snmp_trap_oid(),
162 crate::value::Value::ObjectIdentifier(trap_oid.clone()),
163 ));
164 all_varbinds.extend(varbinds);
165 Self {
166 pdu_type: PduType::TrapV2,
167 request_id,
168 error_status: 0,
169 error_index: 0,
170 varbinds: all_varbinds,
171 }
172 }
173
174 #[must_use]
179 pub fn inform_request(
180 request_id: i32,
181 uptime: u32,
182 trap_oid: &Oid,
183 varbinds: Vec<VarBind>,
184 ) -> Self {
185 let mut all_varbinds = Vec::with_capacity(2 + varbinds.len());
186 all_varbinds.push(VarBind::new(
187 crate::notification::oids::sys_uptime(),
188 crate::value::Value::TimeTicks(uptime),
189 ));
190 all_varbinds.push(VarBind::new(
191 crate::notification::oids::snmp_trap_oid(),
192 crate::value::Value::ObjectIdentifier(trap_oid.clone()),
193 ));
194 all_varbinds.extend(varbinds);
195 Self {
196 pdu_type: PduType::InformRequest,
197 request_id,
198 error_status: 0,
199 error_index: 0,
200 varbinds: all_varbinds,
201 }
202 }
203
204 #[must_use]
210 pub fn get_bulk(
211 request_id: i32,
212 non_repeaters: i32,
213 max_repetitions: i32,
214 varbinds: Vec<VarBind>,
215 ) -> Self {
216 Self {
217 pdu_type: PduType::GetBulkRequest,
218 request_id,
219 error_status: clamp_bulk_field(non_repeaters),
220 error_index: clamp_bulk_field(max_repetitions),
221 varbinds,
222 }
223 }
224
225 pub fn encode(&self, buf: &mut EncodeBuf) {
227 debug_assert!(
230 self.pdu_type != PduType::TrapV1,
231 "TrapV1 must be encoded via TrapV1Pdu, not the generic Pdu encoder"
232 );
233 let (error_status, error_index) = if self.pdu_type == PduType::GetBulkRequest {
239 (
240 clamp_bulk_field(self.error_status),
241 clamp_bulk_field(self.error_index),
242 )
243 } else {
244 (self.error_status, self.error_index)
245 };
246
247 buf.push_constructed(self.pdu_type.tag(), |buf| {
248 encode_varbind_list(buf, &self.varbinds);
249 buf.push_integer(error_index);
250 buf.push_integer(error_status);
251 buf.push_integer(self.request_id);
252 });
253 }
254
255 pub fn decode(decoder: &mut Decoder) -> Result<Self> {
257 let tag = decoder.read_tag()?;
258 let pdu_type = PduType::from_tag(tag).ok_or_else(|| {
259 tracing::debug!(target: "async_snmp::pdu", { offset = decoder.offset(), tag = tag, kind = %DecodeErrorKind::UnknownPduType(tag) }, "decode error");
260 Error::MalformedResponse {
261 target: UNKNOWN_TARGET,
262 }
263 .boxed()
264 })?;
265
266 if pdu_type == PduType::TrapV1 {
272 tracing::debug!(target: "async_snmp::pdu", { offset = decoder.offset(), tag = tag }, "TrapV1 PDU tag not valid in generic PDU context");
273 return Err(Error::MalformedResponse {
274 target: UNKNOWN_TARGET,
275 }
276 .boxed());
277 }
278
279 let len = decoder.read_length()?;
280 let mut pdu_decoder = decoder.sub_decoder(len)?;
281
282 let request_id = pdu_decoder.read_integer()?;
283 let mut error_status = pdu_decoder.read_integer()?;
284 let mut error_index = pdu_decoder.read_integer()?;
285 let varbinds = decode_varbind_list(&mut pdu_decoder)?;
286
287 if pdu_type == PduType::GetBulkRequest {
294 error_status = clamp_bulk_field(error_status);
295 error_index = clamp_bulk_field(error_index);
296 }
297
298 Ok(Pdu {
306 pdu_type,
307 request_id,
308 error_status,
309 error_index,
310 varbinds,
311 })
312 }
313
314 #[must_use]
316 pub fn is_error(&self) -> bool {
317 self.pdu_type == PduType::Response && self.error_status != 0
318 }
319
320 #[must_use]
322 pub fn error_status_enum(&self) -> ErrorStatus {
323 ErrorStatus::from_i32(self.error_status)
324 }
325
326 #[must_use]
331 pub fn to_response(&self) -> Self {
332 Self {
333 pdu_type: PduType::Response,
334 request_id: self.request_id,
335 error_status: 0,
336 error_index: 0,
337 varbinds: self.varbinds.clone(),
338 }
339 }
340
341 #[must_use]
343 pub fn to_error_response(&self, error_status: ErrorStatus, error_index: i32) -> Self {
344 Self {
345 pdu_type: PduType::Response,
346 request_id: self.request_id,
347 error_status: error_status.as_i32(),
348 error_index,
349 varbinds: self.varbinds.clone(),
350 }
351 }
352
353 #[must_use]
374 pub fn to_v1_trap(&self, default_addr: [u8; 4]) -> Option<TrapV1Pdu> {
375 use crate::notification::oids;
376 use crate::value::Value;
377
378 if self.varbinds.len() < 2 {
379 return None;
380 }
381
382 if self.varbinds[0].oid != oids::sys_uptime() {
385 return None;
386 }
387 if self.varbinds[1].oid != oids::snmp_trap_oid() {
388 return None;
389 }
390
391 let time_stamp = match &self.varbinds[0].value {
392 Value::TimeTicks(t) => *t,
393 _ => return None,
394 };
395
396 let Value::ObjectIdentifier(trap_oid) = &self.varbinds[1].value else {
397 return None;
398 };
399
400 for vb in &self.varbinds {
402 if matches!(vb.value, Value::Counter64(_)) {
403 return None;
404 }
405 }
406
407 let snmp_traps_prefix = oids::snmp_traps();
409 let (generic_trap, specific_trap, enterprise) = if trap_oid.starts_with(&snmp_traps_prefix)
410 && trap_oid.len() == snmp_traps_prefix.len() + 1
411 && (1..=6).contains(&trap_oid.arcs()[trap_oid.len() - 1])
412 {
413 let last_arc = trap_oid.arcs()[trap_oid.len() - 1];
415 let generic = GenericTrap::from_i32((last_arc - 1) as i32);
416 let enterprise_oid = oids::snmp_trap_enterprise();
419 let ent = self.varbinds[2..]
420 .iter()
421 .find(|vb| vb.oid == enterprise_oid)
422 .and_then(|vb| match &vb.value {
423 Value::ObjectIdentifier(oid) => Some(oid.clone()),
424 _ => None,
425 })
426 .unwrap_or_else(|| snmp_traps_prefix.clone());
427 (generic, 0, ent)
428 } else if trap_oid.len() >= 2 {
429 let arcs = trap_oid.arcs();
433 let specific = i32::try_from(arcs[arcs.len() - 1]).ok()?;
434 let next_to_last = arcs[arcs.len() - 2];
435 let enterprise = if next_to_last == 0 {
436 Oid::from_slice(&arcs[..arcs.len() - 2])
437 } else {
438 Oid::from_slice(&arcs[..arcs.len() - 1])
439 };
440 (GenericTrap::EnterpriseSpecific, specific, enterprise)
441 } else {
442 return None;
443 };
444
445 let trap_address_oid = oids::snmp_trap_address();
447 let agent_addr = self.varbinds[2..]
448 .iter()
449 .find(|vb| vb.oid == trap_address_oid)
450 .and_then(|vb| match &vb.value {
451 Value::IpAddress(addr) => Some(*addr),
452 _ => None,
453 })
454 .unwrap_or(default_addr);
455
456 let varbinds: Vec<VarBind> = self.varbinds[2..].to_vec();
460
461 Some(TrapV1Pdu {
462 enterprise,
463 agent_addr,
464 generic_trap,
465 specific_trap,
466 time_stamp,
467 varbinds,
468 })
469 }
470
471 #[must_use]
473 pub fn is_notification(&self) -> bool {
474 matches!(
475 self.pdu_type,
476 PduType::TrapV1 | PduType::TrapV2 | PduType::InformRequest
477 )
478 }
479
480 #[must_use]
482 pub fn is_confirmed(&self) -> bool {
483 matches!(
484 self.pdu_type,
485 PduType::GetRequest
486 | PduType::GetNextRequest
487 | PduType::GetBulkRequest
488 | PduType::SetRequest
489 | PduType::InformRequest
490 )
491 }
492}
493
494#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
496pub enum GenericTrap {
497 ColdStart,
499 WarmStart,
501 LinkDown,
503 LinkUp,
505 AuthenticationFailure,
507 EgpNeighborLoss,
509 EnterpriseSpecific,
511 Unknown(i32),
513}
514
515impl std::fmt::Display for GenericTrap {
516 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
517 match self {
518 Self::ColdStart => write!(f, "coldStart"),
519 Self::WarmStart => write!(f, "warmStart"),
520 Self::LinkDown => write!(f, "linkDown"),
521 Self::LinkUp => write!(f, "linkUp"),
522 Self::AuthenticationFailure => write!(f, "authenticationFailure"),
523 Self::EgpNeighborLoss => write!(f, "egpNeighborLoss"),
524 Self::EnterpriseSpecific => write!(f, "enterpriseSpecific"),
525 Self::Unknown(v) => write!(f, "unknown({v})"),
526 }
527 }
528}
529
530impl GenericTrap {
531 #[must_use]
533 pub fn from_i32(v: i32) -> Self {
534 match v {
535 0 => Self::ColdStart,
536 1 => Self::WarmStart,
537 2 => Self::LinkDown,
538 3 => Self::LinkUp,
539 4 => Self::AuthenticationFailure,
540 5 => Self::EgpNeighborLoss,
541 6 => Self::EnterpriseSpecific,
542 _ => Self::Unknown(v),
543 }
544 }
545
546 #[must_use]
548 pub fn as_i32(self) -> i32 {
549 match self {
550 Self::ColdStart => 0,
551 Self::WarmStart => 1,
552 Self::LinkDown => 2,
553 Self::LinkUp => 3,
554 Self::AuthenticationFailure => 4,
555 Self::EgpNeighborLoss => 5,
556 Self::EnterpriseSpecific => 6,
557 Self::Unknown(v) => v,
558 }
559 }
560}
561
562#[derive(Debug, Clone, PartialEq, Eq)]
567pub struct TrapV1Pdu {
568 pub enterprise: Oid,
570 pub agent_addr: [u8; 4],
572 pub generic_trap: GenericTrap,
574 pub specific_trap: i32,
576 pub time_stamp: u32,
578 pub varbinds: Vec<VarBind>,
580}
581
582impl TrapV1Pdu {
583 #[must_use]
585 pub fn new(
586 enterprise: Oid,
587 agent_addr: [u8; 4],
588 generic_trap: GenericTrap,
589 specific_trap: i32,
590 time_stamp: u32,
591 varbinds: Vec<VarBind>,
592 ) -> Self {
593 Self {
594 enterprise,
595 agent_addr,
596 generic_trap,
597 specific_trap,
598 time_stamp,
599 varbinds,
600 }
601 }
602
603 #[must_use]
605 pub fn is_enterprise_specific(&self) -> bool {
606 self.generic_trap == GenericTrap::EnterpriseSpecific
607 }
608
609 pub fn v2_trap_oid(&self) -> crate::Result<Oid> {
656 if self.is_enterprise_specific() {
657 if self.specific_trap < 0 {
658 return Err(Error::InvalidOid("specific_trap cannot be negative".into()).boxed());
659 }
660 let mut arcs: Vec<u32> = self.enterprise.arcs().to_vec();
661 arcs.push(0);
662 arcs.push(self.specific_trap as u32);
663 Ok(Oid::new(arcs))
664 } else {
665 let raw = self.generic_trap.as_i32();
666 if raw < 0 {
667 return Err(Error::InvalidOid("generic_trap cannot be negative".into()).boxed());
668 }
669 if raw == i32::MAX {
670 return Err(Error::InvalidOid("generic_trap overflow".into()).boxed());
671 }
672 let trap_num = raw + 1;
673 Ok(crate::oid!(1, 3, 6, 1, 6, 3, 1, 1, 5).child(trap_num as u32))
674 }
675 }
676
677 pub fn to_v2_pdu(&self) -> crate::Result<Pdu> {
691 use crate::notification::oids;
692 use crate::value::Value;
693
694 let trap_oid = self.v2_trap_oid()?;
695
696 let mut varbinds = Vec::with_capacity(2 + self.varbinds.len());
697 varbinds.push(VarBind::new(
698 oids::sys_uptime(),
699 Value::TimeTicks(self.time_stamp),
700 ));
701 varbinds.push(VarBind::new(
702 oids::snmp_trap_oid(),
703 Value::ObjectIdentifier(trap_oid),
704 ));
705 varbinds.extend_from_slice(&self.varbinds);
706
707 Ok(Pdu {
708 pdu_type: PduType::TrapV2,
709 request_id: 0,
710 error_status: 0,
711 error_index: 0,
712 varbinds,
713 })
714 }
715
716 pub fn encode(&self, buf: &mut EncodeBuf) {
718 buf.push_constructed(tag::pdu::TRAP_V1, |buf| {
719 encode_varbind_list(buf, &self.varbinds);
720 buf.push_unsigned32(tag::application::TIMETICKS, self.time_stamp);
721 buf.push_integer(self.specific_trap);
722 buf.push_integer(self.generic_trap.as_i32());
723 buf.push_bytes(&self.agent_addr);
726 buf.push_length(4);
727 buf.push_tag(tag::application::IP_ADDRESS);
728 buf.push_oid(&self.enterprise);
729 });
730 }
731
732 pub fn decode(decoder: &mut Decoder) -> Result<Self> {
734 let mut pdu = decoder.read_constructed(tag::pdu::TRAP_V1)?;
735
736 let enterprise = pdu.read_oid()?;
738
739 let agent_tag = pdu.read_tag()?;
741 if agent_tag != tag::application::IP_ADDRESS {
742 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), expected = 0x40_u8, actual = agent_tag, kind = %DecodeErrorKind::UnexpectedTag {
743 expected: 0x40,
744 actual: agent_tag,
745 } }, "decode error");
746 return Err(Error::MalformedResponse {
747 target: UNKNOWN_TARGET,
748 }
749 .boxed());
750 }
751 let agent_len = pdu.read_length()?;
752 if agent_len != 4 {
753 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), length = agent_len, kind = %DecodeErrorKind::InvalidIpAddressLength { length: agent_len } }, "decode error");
754 return Err(Error::MalformedResponse {
755 target: UNKNOWN_TARGET,
756 }
757 .boxed());
758 }
759 let agent_bytes = pdu.read_bytes(4)?;
760 let agent_addr = [
761 agent_bytes[0],
762 agent_bytes[1],
763 agent_bytes[2],
764 agent_bytes[3],
765 ];
766
767 let generic_trap = GenericTrap::from_i32(pdu.read_integer()?);
769
770 let specific_trap = pdu.read_integer()?;
772
773 let ts_tag = pdu.read_tag()?;
775 if ts_tag != tag::application::TIMETICKS {
776 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), expected = 0x43_u8, actual = ts_tag, kind = %DecodeErrorKind::UnexpectedTag {
777 expected: 0x43,
778 actual: ts_tag,
779 } }, "decode error");
780 return Err(Error::MalformedResponse {
781 target: UNKNOWN_TARGET,
782 }
783 .boxed());
784 }
785 let ts_len = pdu.read_length()?;
786 let time_stamp = pdu.read_unsigned32_value(ts_len)?;
787
788 let varbinds = decode_varbind_list(&mut pdu)?;
790
791 Ok(TrapV1Pdu {
792 enterprise,
793 agent_addr,
794 generic_trap,
795 specific_trap,
796 time_stamp,
797 varbinds,
798 })
799 }
800}
801
802#[derive(Debug, Clone)]
804pub struct GetBulkPdu {
805 pub request_id: i32,
807 pub non_repeaters: i32,
809 pub max_repetitions: i32,
811 pub varbinds: Vec<VarBind>,
813}
814
815impl GetBulkPdu {
816 #[must_use]
818 pub fn new(request_id: i32, non_repeaters: i32, max_repetitions: i32, oids: &[Oid]) -> Self {
819 Self {
820 request_id,
821 non_repeaters,
822 max_repetitions,
823 varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
824 }
825 }
826
827 pub fn encode(&self, buf: &mut EncodeBuf) {
829 buf.push_constructed(tag::pdu::GET_BULK_REQUEST, |buf| {
830 encode_varbind_list(buf, &self.varbinds);
831 buf.push_integer(clamp_bulk_field(self.max_repetitions));
834 buf.push_integer(clamp_bulk_field(self.non_repeaters));
835 buf.push_integer(self.request_id);
836 });
837 }
838
839 pub fn decode(decoder: &mut Decoder) -> Result<Self> {
847 let pdu = Pdu::decode(decoder)?;
848
849 if pdu.pdu_type != PduType::GetBulkRequest {
850 tracing::debug!(target: "async_snmp::pdu", { pdu_type = ?pdu.pdu_type }, "expected GETBULK PDU");
851 return Err(Error::MalformedResponse {
852 target: UNKNOWN_TARGET,
853 }
854 .boxed());
855 }
856
857 Ok(GetBulkPdu {
858 request_id: pdu.request_id,
859 non_repeaters: pdu.error_status,
860 max_repetitions: pdu.error_index,
861 varbinds: pdu.varbinds,
862 })
863 }
864}
865
866#[cfg(test)]
867mod tests {
868 use super::*;
869 use crate::oid;
870
871 struct RawPdu {
876 pdu_type: u8,
877 request_id: i32,
878 error_status: i32,
879 error_index: i32,
880 varbinds: Vec<VarBind>,
881 }
882
883 impl RawPdu {
884 fn response(
885 request_id: i32,
886 error_status: i32,
887 error_index: i32,
888 varbinds: Vec<VarBind>,
889 ) -> Self {
890 Self {
891 pdu_type: PduType::Response.tag(),
892 request_id,
893 error_status,
894 error_index,
895 varbinds,
896 }
897 }
898
899 fn encode(&self) -> bytes::Bytes {
900 let mut buf = EncodeBuf::new();
901 buf.push_constructed(self.pdu_type, |buf| {
902 encode_varbind_list(buf, &self.varbinds);
903 buf.push_integer(self.error_index);
904 buf.push_integer(self.error_status);
905 buf.push_integer(self.request_id);
906 });
907 buf.finish()
908 }
909 }
910
911 struct RawGetBulkPdu {
913 request_id: i32,
914 non_repeaters: i32,
915 max_repetitions: i32,
916 varbinds: Vec<VarBind>,
917 }
918
919 impl RawGetBulkPdu {
920 fn new(
921 request_id: i32,
922 non_repeaters: i32,
923 max_repetitions: i32,
924 varbinds: Vec<VarBind>,
925 ) -> Self {
926 Self {
927 request_id,
928 non_repeaters,
929 max_repetitions,
930 varbinds,
931 }
932 }
933
934 fn encode(&self) -> bytes::Bytes {
935 let mut buf = EncodeBuf::new();
936 buf.push_constructed(tag::pdu::GET_BULK_REQUEST, |buf| {
937 encode_varbind_list(buf, &self.varbinds);
938 buf.push_integer(self.max_repetitions);
939 buf.push_integer(self.non_repeaters);
940 buf.push_integer(self.request_id);
941 });
942 buf.finish()
943 }
944 }
945
946 #[test]
947 fn test_get_request_roundtrip() {
948 let pdu = Pdu::get_request(12345, &[oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)]);
949
950 let mut buf = EncodeBuf::new();
951 pdu.encode(&mut buf);
952 let bytes = buf.finish();
953
954 let mut decoder = Decoder::new(bytes);
955 let decoded = Pdu::decode(&mut decoder).unwrap();
956
957 assert_eq!(decoded.pdu_type, PduType::GetRequest);
958 assert_eq!(decoded.request_id, 12345);
959 assert_eq!(decoded.varbinds.len(), 1);
960 }
961
962 #[test]
963 fn test_getbulk_roundtrip() {
964 let pdu = GetBulkPdu::new(12345, 0, 10, &[oid!(1, 3, 6, 1, 2, 1, 1)]);
965
966 let mut buf = EncodeBuf::new();
967 pdu.encode(&mut buf);
968 let bytes = buf.finish();
969
970 let mut decoder = Decoder::new(bytes);
971 let decoded = GetBulkPdu::decode(&mut decoder).unwrap();
972
973 assert_eq!(decoded.request_id, 12345);
974 assert_eq!(decoded.non_repeaters, 0);
975 assert_eq!(decoded.max_repetitions, 10);
976 }
977
978 #[test]
979 fn test_trap_v1_roundtrip() {
980 use crate::value::Value;
981 use crate::varbind::VarBind;
982
983 let trap = TrapV1Pdu::new(
984 oid!(1, 3, 6, 1, 4, 1, 9999), [192, 168, 1, 1], GenericTrap::LinkDown,
987 0,
988 1234_5678, vec![VarBind::new(
990 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
991 Value::Integer(1),
992 )],
993 );
994
995 let mut buf = EncodeBuf::new();
996 trap.encode(&mut buf);
997 let bytes = buf.finish();
998
999 let mut decoder = Decoder::new(bytes);
1000 let decoded = TrapV1Pdu::decode(&mut decoder).unwrap();
1001
1002 assert_eq!(decoded.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999));
1003 assert_eq!(decoded.agent_addr, [192, 168, 1, 1]);
1004 assert_eq!(decoded.generic_trap, GenericTrap::LinkDown);
1005 assert_eq!(decoded.specific_trap, 0);
1006 assert_eq!(decoded.time_stamp, 1234_5678);
1007 assert_eq!(decoded.varbinds.len(), 1);
1008 }
1009
1010 #[test]
1011 fn test_generic_pdu_decode_rejects_trap_v1_tag() {
1012 let trap = TrapV1Pdu::new(
1016 oid!(1, 3, 6, 1, 4, 1, 9999),
1017 [192, 168, 1, 1],
1018 GenericTrap::LinkDown,
1019 0,
1020 12345,
1021 vec![],
1022 );
1023 let mut buf = EncodeBuf::new();
1024 trap.encode(&mut buf);
1025 let bytes = buf.finish();
1026
1027 let mut decoder = Decoder::new(bytes);
1028 let result = Pdu::decode(&mut decoder);
1029 assert!(
1030 result.is_err(),
1031 "generic Pdu::decode must reject TrapV1 tag, got {result:?}"
1032 );
1033 }
1034
1035 #[test]
1036 fn test_trap_v1_enterprise_specific() {
1037 let trap = TrapV1Pdu::new(
1038 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
1039 [10, 0, 0, 1],
1040 GenericTrap::EnterpriseSpecific,
1041 42, 100,
1043 vec![],
1044 );
1045
1046 assert!(trap.is_enterprise_specific());
1047 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
1048
1049 let mut buf = EncodeBuf::new();
1050 trap.encode(&mut buf);
1051 let bytes = buf.finish();
1052
1053 let mut decoder = Decoder::new(bytes);
1054 let decoded = TrapV1Pdu::decode(&mut decoder).unwrap();
1055
1056 assert_eq!(decoded.specific_trap, 42);
1057 }
1058
1059 #[test]
1060 fn test_trap_v1_v2_trap_oid_generic_traps() {
1061 let test_cases = [
1065 (GenericTrap::ColdStart, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1)),
1066 (GenericTrap::WarmStart, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 2)),
1067 (GenericTrap::LinkDown, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3)),
1068 (GenericTrap::LinkUp, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 4)),
1069 (
1070 GenericTrap::AuthenticationFailure,
1071 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 5),
1072 ),
1073 (
1074 GenericTrap::EgpNeighborLoss,
1075 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 6),
1076 ),
1077 ];
1078
1079 for (generic_trap, expected_oid) in test_cases {
1080 let trap = TrapV1Pdu::new(
1081 oid!(1, 3, 6, 1, 4, 1, 9999),
1082 [192, 168, 1, 1],
1083 generic_trap,
1084 0,
1085 12345,
1086 vec![],
1087 );
1088 assert_eq!(
1089 trap.v2_trap_oid().unwrap(),
1090 expected_oid,
1091 "Failed for {generic_trap:?}"
1092 );
1093 }
1094 }
1095
1096 #[test]
1097 fn test_trap_v1_v2_trap_oid_enterprise_specific() {
1098 let trap = TrapV1Pdu::new(
1100 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
1101 [192, 168, 1, 1],
1102 GenericTrap::EnterpriseSpecific,
1103 42,
1104 12345,
1105 vec![],
1106 );
1107
1108 assert_eq!(
1110 trap.v2_trap_oid().unwrap(),
1111 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2, 0, 42)
1112 );
1113 }
1114
1115 #[test]
1116 fn test_trap_v1_v2_trap_oid_enterprise_specific_zero() {
1117 let trap = TrapV1Pdu::new(
1119 oid!(1, 3, 6, 1, 4, 1, 1234),
1120 [10, 0, 0, 1],
1121 GenericTrap::EnterpriseSpecific,
1122 0,
1123 100,
1124 vec![],
1125 );
1126
1127 assert_eq!(
1129 trap.v2_trap_oid().unwrap(),
1130 oid!(1, 3, 6, 1, 4, 1, 1234, 0, 0)
1131 );
1132 }
1133
1134 #[test]
1135 fn test_pdu_to_response() {
1136 use crate::value::Value;
1137 use crate::varbind::VarBind;
1138
1139 let inform = Pdu {
1140 pdu_type: PduType::InformRequest,
1141 request_id: 99999,
1142 error_status: 0,
1143 error_index: 0,
1144 varbinds: vec![
1145 VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::TimeTicks(12345)),
1146 VarBind::new(
1147 oid!(1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0),
1148 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1)),
1149 ),
1150 ],
1151 };
1152
1153 let response = inform.to_response();
1154
1155 assert_eq!(response.pdu_type, PduType::Response);
1156 assert_eq!(response.request_id, 99999);
1157 assert_eq!(response.error_status, 0);
1158 assert_eq!(response.error_index, 0);
1159 assert_eq!(response.varbinds.len(), 2);
1160 }
1161
1162 #[test]
1163 fn test_pdu_is_confirmed() {
1164 let get = Pdu::get_request(1, &[oid!(1, 3, 6, 1)]);
1165 assert!(get.is_confirmed());
1166
1167 let inform = Pdu {
1168 pdu_type: PduType::InformRequest,
1169 request_id: 1,
1170 error_status: 0,
1171 error_index: 0,
1172 varbinds: vec![],
1173 };
1174 assert!(inform.is_confirmed());
1175
1176 let trap = Pdu {
1177 pdu_type: PduType::TrapV2,
1178 request_id: 1,
1179 error_status: 0,
1180 error_index: 0,
1181 varbinds: vec![],
1182 };
1183 assert!(!trap.is_confirmed());
1184 assert!(trap.is_notification());
1185 }
1186
1187 #[test]
1188 fn test_decode_accepts_negative_error_index() {
1189 let raw = RawPdu::response(1, 0, -1, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1195 let encoded = raw.encode();
1196
1197 let mut decoder = Decoder::new(encoded);
1198 let result = Pdu::decode(&mut decoder);
1199
1200 assert!(
1201 result.is_ok(),
1202 "negative error_index must be accepted to match net-snmp behavior, got {:?}",
1203 result.err()
1204 );
1205 assert_eq!(result.unwrap().error_index, -1);
1206 }
1207
1208 #[test]
1209 fn test_decode_accepts_error_index_beyond_varbinds() {
1210 let raw = RawPdu::response(1, 5, 5, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1216 let encoded = raw.encode();
1217
1218 let mut decoder = Decoder::new(encoded);
1219 let result = Pdu::decode(&mut decoder);
1220
1221 assert!(
1222 result.is_ok(),
1223 "error_index beyond varbind count must be accepted to match net-snmp behavior, got {:?}",
1224 result.err()
1225 );
1226 assert_eq!(result.unwrap().error_index, 5);
1227 }
1228
1229 #[test]
1230 fn test_decode_accepts_valid_error_index_zero() {
1231 let raw = RawPdu::response(1, 0, 0, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1233 let encoded = raw.encode();
1234
1235 let mut decoder = Decoder::new(encoded);
1236 let decoded = Pdu::decode(&mut decoder);
1237 assert!(decoded.is_ok(), "error_index=0 should be valid");
1238 }
1239
1240 #[test]
1241 fn test_decode_accepts_error_index_within_bounds() {
1242 let raw = RawPdu::response(1, 5, 1, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1244 let encoded = raw.encode();
1245
1246 let mut decoder = Decoder::new(encoded);
1247 let result = Pdu::decode(&mut decoder);
1248 assert!(
1249 result.is_ok(),
1250 "error_index=1 with 1 varbind should be valid"
1251 );
1252 }
1253
1254 #[test]
1255 fn test_decode_clamps_negative_non_repeaters() {
1256 let raw = RawGetBulkPdu::new(1, -1, 10, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1260 let encoded = raw.encode();
1261
1262 let mut decoder = Decoder::new(encoded);
1263 let decoded = GetBulkPdu::decode(&mut decoder).expect("negative non_repeaters clamps to 0");
1264 assert_eq!(decoded.non_repeaters, 0);
1265 assert_eq!(decoded.max_repetitions, 10);
1266 }
1267
1268 #[test]
1269 fn test_decode_clamps_negative_max_repetitions() {
1270 let raw = RawGetBulkPdu::new(1, 0, -5, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1271 let encoded = raw.encode();
1272
1273 let mut decoder = Decoder::new(encoded);
1274 let decoded =
1275 GetBulkPdu::decode(&mut decoder).expect("negative max_repetitions clamps to 0");
1276 assert_eq!(decoded.non_repeaters, 0);
1277 assert_eq!(decoded.max_repetitions, 0);
1278 }
1279
1280 #[test]
1281 fn test_pdu_decode_getbulk_clamps_negative_non_repeaters_repro() {
1282 let packet = [
1288 0xa5, 0x0b, 0x02, 0x01, 0x01, 0x02, 0x01, 0xff, 0x02, 0x01, 0x01, 0x30, 0x00,
1289 ];
1290 let mut decoder = Decoder::new(bytes::Bytes::copy_from_slice(&packet));
1291 let pdu = Pdu::decode(&mut decoder).expect("repro GETBULK packet must decode");
1292 assert_eq!(pdu.pdu_type, PduType::GetBulkRequest);
1293 assert_eq!(pdu.request_id, 1);
1294 assert_eq!(pdu.error_status, 0);
1296 assert_eq!(pdu.error_index, 1);
1298 }
1299
1300 #[test]
1301 fn test_decode_accepts_valid_getbulk_params() {
1302 let raw = RawGetBulkPdu::new(1, 0, 10, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1303 let encoded = raw.encode();
1304
1305 let mut decoder = Decoder::new(encoded);
1306 let result = GetBulkPdu::decode(&mut decoder);
1307 assert!(result.is_ok(), "valid GETBULK params should be accepted");
1308
1309 let pdu = result.unwrap();
1310 assert_eq!(pdu.non_repeaters, 0);
1311 assert_eq!(pdu.max_repetitions, 10);
1312 }
1313
1314 #[test]
1315 fn test_encode_clamps_negative_non_repeaters_and_max_repetitions() {
1316 let pdu = GetBulkPdu::new(1, -1, -5, &[oid!(1, 3, 6, 1)]);
1322
1323 let mut buf = EncodeBuf::new();
1324 pdu.encode(&mut buf);
1325 let bytes = buf.finish();
1326
1327 let mut decoder = Decoder::new(bytes);
1328 let result = GetBulkPdu::decode(&mut decoder);
1329 assert!(
1330 result.is_ok(),
1331 "encoded negative non_repeaters/max_repetitions should decode after clamping, got {result:?}"
1332 );
1333 let decoded = result.unwrap();
1334 assert_eq!(decoded.non_repeaters, 0);
1335 assert_eq!(decoded.max_repetitions, 0);
1336 }
1337
1338 #[test]
1339 fn test_encode_leaves_non_negative_non_repeaters_and_max_repetitions_unchanged() {
1340 let pdu = GetBulkPdu::new(1, 0, 10, &[oid!(1, 3, 6, 1)]);
1341
1342 let mut buf = EncodeBuf::new();
1343 pdu.encode(&mut buf);
1344 let bytes = buf.finish();
1345
1346 let mut decoder = Decoder::new(bytes);
1347 let decoded = GetBulkPdu::decode(&mut decoder).unwrap();
1348 assert_eq!(decoded.non_repeaters, 0);
1349 assert_eq!(decoded.max_repetitions, 10);
1350 }
1351
1352 #[test]
1353 fn test_generic_pdu_get_bulk_clamps_negative_fields() {
1354 let pdu = Pdu::get_bulk(1, -1, -5, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1360 assert_eq!(pdu.error_status, 0);
1361 assert_eq!(pdu.error_index, 0);
1362
1363 let mut buf = EncodeBuf::new();
1364 pdu.encode(&mut buf);
1365 let bytes = buf.finish();
1366
1367 let mut decoder = Decoder::new(bytes);
1368 let decoded = GetBulkPdu::decode(&mut decoder)
1369 .expect("clamped generic GETBULK encode should decode as valid");
1370 assert_eq!(decoded.non_repeaters, 0);
1371 assert_eq!(decoded.max_repetitions, 0);
1372 }
1373
1374 #[test]
1375 fn test_directly_constructed_getbulk_pdu_encode_clamps_negative_fields() {
1376 let pdu = Pdu {
1381 pdu_type: PduType::GetBulkRequest,
1382 request_id: 1,
1383 error_status: -1,
1384 error_index: -5,
1385 varbinds: vec![VarBind::null(oid!(1, 3, 6, 1))],
1386 };
1387
1388 let mut buf = EncodeBuf::new();
1389 pdu.encode(&mut buf);
1390 let bytes = buf.finish();
1391
1392 let mut decoder = Decoder::new(bytes);
1395 let tag = decoder.read_tag().expect("read pdu tag");
1396 assert_eq!(tag, PduType::GetBulkRequest.tag());
1397 let len = decoder.read_length().expect("read pdu length");
1398 let mut inner = decoder.sub_decoder(len).expect("pdu sub-decoder");
1399 let _request_id = inner.read_integer().expect("read request_id");
1400 let non_repeaters = inner.read_integer().expect("read non_repeaters");
1401 let max_repetitions = inner.read_integer().expect("read max_repetitions");
1402 assert_eq!(non_repeaters, 0);
1403 assert_eq!(max_repetitions, 0);
1404 }
1405
1406 #[test]
1407 fn test_pdu_decode_getbulk_with_large_max_repetitions() {
1408 let raw = RawGetBulkPdu::new(12345, 0, 25, vec![VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1))]);
1412 let encoded = raw.encode();
1413
1414 let mut decoder = Decoder::new(encoded);
1415 let result = Pdu::decode(&mut decoder);
1416 assert!(
1417 result.is_ok(),
1418 "Pdu::decode should accept GETBULK with max_repetitions > varbinds.len(), got {:?}",
1419 result.err()
1420 );
1421
1422 let pdu = result.unwrap();
1423 assert_eq!(pdu.pdu_type, PduType::GetBulkRequest);
1424 assert_eq!(pdu.request_id, 12345);
1425 assert_eq!(pdu.error_status, 0);
1427 assert_eq!(pdu.error_index, 25);
1428 assert_eq!(pdu.varbinds.len(), 1);
1429 }
1430
1431 #[test]
1432 fn test_getbulk_request_is_not_treated_as_error() {
1433 let pdu = Pdu::get_bulk(
1434 12345,
1435 2,
1436 10,
1437 vec![
1438 VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1)),
1439 VarBind::null(oid!(1, 3, 6, 1, 2, 1, 2)),
1440 ],
1441 );
1442
1443 assert!(!pdu.is_error());
1444 }
1445
1446 #[test]
1447 fn test_response_with_error_status_is_treated_as_error() {
1448 let pdu = Pdu {
1449 pdu_type: PduType::Response,
1450 request_id: 12345,
1451 error_status: ErrorStatus::TooBig.as_i32(),
1452 error_index: 1,
1453 varbinds: vec![VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1))],
1454 };
1455
1456 assert!(pdu.is_error());
1457 }
1458
1459 #[test]
1460 fn pdu_type_hash() {
1461 use std::collections::HashSet;
1462 let mut set = HashSet::new();
1463 set.insert(PduType::GetRequest);
1464 set.insert(PduType::GetNextRequest);
1465 assert_eq!(set.len(), 2);
1466 assert!(set.contains(&PduType::GetRequest));
1467 }
1468
1469 #[test]
1474 fn test_v1_to_v2_generic_trap() {
1475 use crate::value::Value;
1476 use crate::varbind::VarBind;
1477
1478 let trap = TrapV1Pdu::new(
1479 oid!(1, 3, 6, 1, 4, 1, 9999),
1480 [192, 168, 1, 1],
1481 GenericTrap::LinkDown,
1482 0,
1483 12345,
1484 vec![VarBind::new(
1485 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
1486 Value::Integer(1),
1487 )],
1488 );
1489
1490 let pdu = trap.to_v2_pdu().unwrap();
1491
1492 assert_eq!(pdu.pdu_type, PduType::TrapV2);
1493 assert_eq!(pdu.request_id, 0);
1494 assert_eq!(pdu.varbinds.len(), 3);
1496
1497 assert_eq!(pdu.varbinds[0].oid, oid!(1, 3, 6, 1, 2, 1, 1, 3, 0));
1499 assert_eq!(pdu.varbinds[0].value, Value::TimeTicks(12345));
1500
1501 assert_eq!(pdu.varbinds[1].oid, oid!(1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0));
1503 assert_eq!(
1504 pdu.varbinds[1].value,
1505 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3))
1506 );
1507
1508 assert_eq!(pdu.varbinds[2].oid, oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1));
1510 }
1511
1512 #[test]
1513 fn test_v1_to_v2_no_proxy_varbinds() {
1514 let trap = TrapV1Pdu::new(
1515 oid!(1, 3, 6, 1, 4, 1, 9999),
1516 [192, 168, 1, 1],
1517 GenericTrap::ColdStart,
1518 0,
1519 100,
1520 vec![],
1521 );
1522
1523 let pdu = trap.to_v2_pdu().unwrap();
1524 assert_eq!(pdu.varbinds.len(), 2);
1527 }
1528
1529 #[test]
1530 fn test_v1_to_v2_enterprise_specific() {
1531 use crate::value::Value;
1532
1533 let trap = TrapV1Pdu::new(
1534 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
1535 [10, 0, 0, 1],
1536 GenericTrap::EnterpriseSpecific,
1537 42,
1538 5000,
1539 vec![],
1540 );
1541
1542 let pdu = trap.to_v2_pdu().unwrap();
1543
1544 assert_eq!(
1546 pdu.varbinds[1].value,
1547 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2, 0, 42))
1548 );
1549 }
1550
1551 #[test]
1552 fn test_v2_to_v1_standard_trap() {
1553 use crate::value::Value;
1554 use crate::varbind::VarBind;
1555
1556 let pdu = Pdu::trap_v2(
1557 1,
1558 5000,
1559 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3), vec![VarBind::new(
1561 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
1562 Value::Integer(1),
1563 )],
1564 );
1565
1566 let trap = pdu.to_v1_trap([10, 0, 0, 1]).unwrap();
1567
1568 assert_eq!(trap.generic_trap, GenericTrap::LinkDown);
1569 assert_eq!(trap.specific_trap, 0);
1570 assert_eq!(trap.time_stamp, 5000);
1571 assert_eq!(trap.agent_addr, [10, 0, 0, 1]);
1572 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5));
1574 assert_eq!(trap.varbinds.len(), 1);
1575 }
1576
1577 #[test]
1578 fn test_v2_to_v1_enterprise_specific_trap() {
1579 let pdu = Pdu::trap_v2(1, 100, &oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2, 0, 42), vec![]);
1580
1581 let trap = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1582
1583 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
1584 assert_eq!(trap.specific_trap, 42);
1585 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2));
1586 assert_eq!(trap.time_stamp, 100);
1587 }
1588
1589 #[test]
1590 fn test_v2_to_v1_enterprise_specific_nonzero_penultimate() {
1591 let pdu = Pdu::trap_v2(1, 200, &oid!(1, 3, 6, 1, 4, 1, 9999, 1, 42), vec![]);
1594
1595 let trap = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1596
1597 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
1598 assert_eq!(trap.specific_trap, 42);
1599 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999, 1));
1601 assert_eq!(trap.time_stamp, 200);
1602 }
1603
1604 #[test]
1605 fn test_v2_to_v1_snmp_traps_arc_out_of_range() {
1606 for arc in [0u32, 7, 9] {
1610 let pdu = Pdu::trap_v2(1, 100, &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, arc), vec![]);
1611
1612 let trap = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1613
1614 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
1615 assert_eq!(trap.specific_trap, i32::try_from(arc).unwrap());
1616 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5));
1617 }
1618 }
1619
1620 #[test]
1621 fn test_v2_to_v1_extracts_trap_address() {
1622 use crate::notification::oids;
1623 use crate::value::Value;
1624 use crate::varbind::VarBind;
1625
1626 let pdu = Pdu::trap_v2(
1627 1,
1628 0,
1629 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1), vec![VarBind::new(
1631 oids::snmp_trap_address(),
1632 Value::IpAddress([192, 168, 1, 1]),
1633 )],
1634 );
1635
1636 let trap = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1637 assert_eq!(trap.agent_addr, [192, 168, 1, 1]);
1638 assert_eq!(trap.varbinds.len(), 1);
1641 assert_eq!(trap.varbinds[0].oid, oids::snmp_trap_address());
1642 }
1643
1644 #[test]
1645 fn test_v2_to_v1_extracts_trap_enterprise() {
1646 use crate::notification::oids;
1647 use crate::value::Value;
1648 use crate::varbind::VarBind;
1649
1650 let pdu = Pdu::trap_v2(
1651 1,
1652 0,
1653 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1), vec![VarBind::new(
1655 oids::snmp_trap_enterprise(),
1656 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4, 1, 9999)),
1657 )],
1658 );
1659
1660 let trap = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1661 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999));
1663 assert_eq!(trap.varbinds.len(), 1);
1666 assert_eq!(trap.varbinds[0].oid, oids::snmp_trap_enterprise());
1667 }
1668
1669 #[test]
1670 fn test_v2_to_v1_counter64_dropped() {
1671 use crate::value::Value;
1672 use crate::varbind::VarBind;
1673
1674 let pdu = Pdu::trap_v2(
1675 1,
1676 0,
1677 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1),
1678 vec![VarBind::new(
1679 oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
1680 Value::Counter64(12345),
1681 )],
1682 );
1683
1684 assert!(pdu.to_v1_trap([0, 0, 0, 0]).is_none());
1686 }
1687
1688 #[test]
1689 fn test_v2_to_v1_too_few_varbinds() {
1690 let pdu = Pdu {
1691 pdu_type: PduType::TrapV2,
1692 request_id: 1,
1693 error_status: 0,
1694 error_index: 0,
1695 varbinds: vec![],
1696 };
1697
1698 assert!(pdu.to_v1_trap([0, 0, 0, 0]).is_none());
1699 }
1700
1701 #[test]
1702 fn test_v1_v2_roundtrip_enterprise_specific() {
1703 use crate::value::Value;
1704 use crate::varbind::VarBind;
1705
1706 let original = TrapV1Pdu::new(
1711 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
1712 [192, 168, 1, 1],
1713 GenericTrap::EnterpriseSpecific,
1714 42,
1715 12345,
1716 vec![VarBind::new(
1717 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
1718 Value::Integer(1),
1719 )],
1720 );
1721
1722 let v2 = original.to_v2_pdu().unwrap();
1723 let restored = v2.to_v1_trap([0, 0, 0, 0]).unwrap();
1724
1725 assert_eq!(restored.enterprise, original.enterprise);
1726 assert_eq!(restored.generic_trap, original.generic_trap);
1727 assert_eq!(restored.specific_trap, original.specific_trap);
1728 assert_eq!(restored.time_stamp, original.time_stamp);
1729 assert_eq!(restored.varbinds.len(), original.varbinds.len());
1730 assert_eq!(restored.varbinds[0].oid, original.varbinds[0].oid);
1731 assert_eq!(restored.agent_addr, [0, 0, 0, 0]);
1733 }
1734
1735 #[test]
1736 fn test_v1_v2_roundtrip_standard_trap() {
1737 let original = TrapV1Pdu::new(
1741 oid!(1, 3, 6, 1, 4, 1, 9999),
1742 [10, 0, 0, 1],
1743 GenericTrap::WarmStart,
1744 0,
1745 500,
1746 vec![],
1747 );
1748
1749 let v2 = original.to_v2_pdu().unwrap();
1750 let restored = v2.to_v1_trap([10, 0, 0, 1]).unwrap();
1751
1752 assert_eq!(restored.generic_trap, GenericTrap::WarmStart);
1753 assert_eq!(restored.specific_trap, 0);
1754 assert_eq!(restored.time_stamp, 500);
1755 assert_eq!(restored.agent_addr, [10, 0, 0, 1]); assert_eq!(restored.enterprise, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5));
1758 }
1759
1760 #[test]
1761 fn test_v2_to_v1_all_generic_traps() {
1762 let traps = [
1764 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1), GenericTrap::ColdStart),
1765 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 2), GenericTrap::WarmStart),
1766 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3), GenericTrap::LinkDown),
1767 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 4), GenericTrap::LinkUp),
1768 (
1769 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 5),
1770 GenericTrap::AuthenticationFailure,
1771 ),
1772 (
1773 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 6),
1774 GenericTrap::EgpNeighborLoss,
1775 ),
1776 ];
1777
1778 for (trap_oid, expected_generic) in traps {
1779 let pdu = Pdu::trap_v2(1, 100, &trap_oid, vec![]);
1780 let v1 = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1781 assert_eq!(v1.generic_trap, expected_generic, "Failed for {trap_oid:?}");
1782 assert_eq!(v1.specific_trap, 0);
1783 }
1784 }
1785}