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
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[repr(u8)]
14pub enum PduType {
15 GetRequest = 0xA0,
17 GetNextRequest = 0xA1,
19 Response = 0xA2,
21 SetRequest = 0xA3,
23 TrapV1 = 0xA4,
25 GetBulkRequest = 0xA5,
27 InformRequest = 0xA6,
29 TrapV2 = 0xA7,
31 Report = 0xA8,
33}
34
35impl PduType {
36 #[must_use]
38 pub fn from_tag(tag: u8) -> Option<Self> {
39 match tag {
40 0xA0 => Some(Self::GetRequest),
41 0xA1 => Some(Self::GetNextRequest),
42 0xA2 => Some(Self::Response),
43 0xA3 => Some(Self::SetRequest),
44 0xA4 => Some(Self::TrapV1),
45 0xA5 => Some(Self::GetBulkRequest),
46 0xA6 => Some(Self::InformRequest),
47 0xA7 => Some(Self::TrapV2),
48 0xA8 => Some(Self::Report),
49 _ => None,
50 }
51 }
52
53 #[must_use]
55 pub fn tag(self) -> u8 {
56 self as u8
57 }
58}
59
60impl std::fmt::Display for PduType {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 match self {
63 Self::GetRequest => write!(f, "GetRequest"),
64 Self::GetNextRequest => write!(f, "GetNextRequest"),
65 Self::Response => write!(f, "Response"),
66 Self::SetRequest => write!(f, "SetRequest"),
67 Self::TrapV1 => write!(f, "TrapV1"),
68 Self::GetBulkRequest => write!(f, "GetBulkRequest"),
69 Self::InformRequest => write!(f, "InformRequest"),
70 Self::TrapV2 => write!(f, "TrapV2"),
71 Self::Report => write!(f, "Report"),
72 }
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct Pdu {
79 pub pdu_type: PduType,
81 pub request_id: i32,
83 pub error_status: i32,
85 pub error_index: i32,
87 pub varbinds: Vec<VarBind>,
89}
90
91impl Pdu {
92 #[must_use]
94 pub fn get_request(request_id: i32, oids: &[Oid]) -> Self {
95 Self {
96 pdu_type: PduType::GetRequest,
97 request_id,
98 error_status: 0,
99 error_index: 0,
100 varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
101 }
102 }
103
104 #[must_use]
106 pub fn get_next_request(request_id: i32, oids: &[Oid]) -> Self {
107 Self {
108 pdu_type: PduType::GetNextRequest,
109 request_id,
110 error_status: 0,
111 error_index: 0,
112 varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
113 }
114 }
115
116 #[must_use]
118 pub fn set_request(request_id: i32, varbinds: Vec<VarBind>) -> Self {
119 Self {
120 pdu_type: PduType::SetRequest,
121 request_id,
122 error_status: 0,
123 error_index: 0,
124 varbinds,
125 }
126 }
127
128 #[must_use]
136 pub fn trap_v2(request_id: i32, uptime: u32, trap_oid: &Oid, varbinds: Vec<VarBind>) -> Self {
137 let mut all_varbinds = Vec::with_capacity(2 + varbinds.len());
138 all_varbinds.push(VarBind::new(
139 crate::notification::oids::sys_uptime(),
140 crate::value::Value::TimeTicks(uptime),
141 ));
142 all_varbinds.push(VarBind::new(
143 crate::notification::oids::snmp_trap_oid(),
144 crate::value::Value::ObjectIdentifier(trap_oid.clone()),
145 ));
146 all_varbinds.extend(varbinds);
147 Self {
148 pdu_type: PduType::TrapV2,
149 request_id,
150 error_status: 0,
151 error_index: 0,
152 varbinds: all_varbinds,
153 }
154 }
155
156 #[must_use]
161 pub fn inform_request(
162 request_id: i32,
163 uptime: u32,
164 trap_oid: &Oid,
165 varbinds: Vec<VarBind>,
166 ) -> Self {
167 let mut all_varbinds = Vec::with_capacity(2 + varbinds.len());
168 all_varbinds.push(VarBind::new(
169 crate::notification::oids::sys_uptime(),
170 crate::value::Value::TimeTicks(uptime),
171 ));
172 all_varbinds.push(VarBind::new(
173 crate::notification::oids::snmp_trap_oid(),
174 crate::value::Value::ObjectIdentifier(trap_oid.clone()),
175 ));
176 all_varbinds.extend(varbinds);
177 Self {
178 pdu_type: PduType::InformRequest,
179 request_id,
180 error_status: 0,
181 error_index: 0,
182 varbinds: all_varbinds,
183 }
184 }
185
186 #[must_use]
190 pub fn get_bulk(
191 request_id: i32,
192 non_repeaters: i32,
193 max_repetitions: i32,
194 varbinds: Vec<VarBind>,
195 ) -> Self {
196 Self {
197 pdu_type: PduType::GetBulkRequest,
198 request_id,
199 error_status: non_repeaters,
200 error_index: max_repetitions,
201 varbinds,
202 }
203 }
204
205 pub fn encode(&self, buf: &mut EncodeBuf) {
207 buf.push_constructed(self.pdu_type.tag(), |buf| {
208 encode_varbind_list(buf, &self.varbinds);
209 buf.push_integer(self.error_index);
210 buf.push_integer(self.error_status);
211 buf.push_integer(self.request_id);
212 });
213 }
214
215 pub fn decode(decoder: &mut Decoder) -> Result<Self> {
217 let tag = decoder.read_tag()?;
218 let pdu_type = PduType::from_tag(tag).ok_or_else(|| {
219 tracing::debug!(target: "async_snmp::pdu", { offset = decoder.offset(), tag = tag, kind = %DecodeErrorKind::UnknownPduType(tag) }, "decode error");
220 Error::MalformedResponse {
221 target: UNKNOWN_TARGET,
222 }
223 .boxed()
224 })?;
225
226 let len = decoder.read_length()?;
227 let mut pdu_decoder = decoder.sub_decoder(len)?;
228
229 let request_id = pdu_decoder.read_integer()?;
230 let error_status = pdu_decoder.read_integer()?;
231 let error_index = pdu_decoder.read_integer()?;
232 let varbinds = decode_varbind_list(&mut pdu_decoder)?;
233
234 Ok(Pdu {
242 pdu_type,
243 request_id,
244 error_status,
245 error_index,
246 varbinds,
247 })
248 }
249
250 #[must_use]
252 pub fn is_error(&self) -> bool {
253 self.pdu_type == PduType::Response && self.error_status != 0
254 }
255
256 #[must_use]
258 pub fn error_status_enum(&self) -> ErrorStatus {
259 ErrorStatus::from_i32(self.error_status)
260 }
261
262 #[must_use]
267 pub fn to_response(&self) -> Self {
268 Self {
269 pdu_type: PduType::Response,
270 request_id: self.request_id,
271 error_status: 0,
272 error_index: 0,
273 varbinds: self.varbinds.clone(),
274 }
275 }
276
277 #[must_use]
279 pub fn to_error_response(&self, error_status: ErrorStatus, error_index: i32) -> Self {
280 Self {
281 pdu_type: PduType::Response,
282 request_id: self.request_id,
283 error_status: error_status.as_i32(),
284 error_index,
285 varbinds: self.varbinds.clone(),
286 }
287 }
288
289 #[must_use]
310 pub fn to_v1_trap(&self, default_addr: [u8; 4]) -> Option<TrapV1Pdu> {
311 use crate::notification::oids;
312 use crate::value::Value;
313
314 if self.varbinds.len() < 2 {
315 return None;
316 }
317
318 if self.varbinds[0].oid != oids::sys_uptime() {
321 return None;
322 }
323 if self.varbinds[1].oid != oids::snmp_trap_oid() {
324 return None;
325 }
326
327 let time_stamp = match &self.varbinds[0].value {
328 Value::TimeTicks(t) => *t,
329 _ => return None,
330 };
331
332 let Value::ObjectIdentifier(trap_oid) = &self.varbinds[1].value else {
333 return None;
334 };
335
336 for vb in &self.varbinds {
338 if matches!(vb.value, Value::Counter64(_)) {
339 return None;
340 }
341 }
342
343 let snmp_traps_prefix = oids::snmp_traps();
345 let (generic_trap, specific_trap, enterprise) = if trap_oid.starts_with(&snmp_traps_prefix)
346 && trap_oid.len() == snmp_traps_prefix.len() + 1
347 {
348 let last_arc = trap_oid.arcs()[trap_oid.len() - 1];
350 if last_arc == 0 || last_arc > 6 {
351 (GenericTrap::EnterpriseSpecific, 0, trap_oid.clone())
353 } else {
354 let generic = GenericTrap::from_i32((last_arc - 1) as i32);
355 let enterprise_oid = oids::snmp_trap_enterprise();
358 let ent = self.varbinds[2..]
359 .iter()
360 .find(|vb| vb.oid == enterprise_oid)
361 .and_then(|vb| match &vb.value {
362 Value::ObjectIdentifier(oid) => Some(oid.clone()),
363 _ => None,
364 })
365 .unwrap_or_else(|| snmp_traps_prefix.clone());
366 (generic, 0, ent)
367 }
368 } else if trap_oid.len() >= 2 {
369 let arcs = trap_oid.arcs();
373 let specific = i32::try_from(arcs[arcs.len() - 1]).ok()?;
374 let next_to_last = arcs[arcs.len() - 2];
375 let enterprise = if next_to_last == 0 {
376 Oid::from_slice(&arcs[..arcs.len() - 2])
377 } else {
378 Oid::from_slice(&arcs[..arcs.len() - 1])
379 };
380 (GenericTrap::EnterpriseSpecific, specific, enterprise)
381 } else {
382 return None;
383 };
384
385 let trap_address_oid = oids::snmp_trap_address();
387 let agent_addr = self.varbinds[2..]
388 .iter()
389 .find(|vb| vb.oid == trap_address_oid)
390 .and_then(|vb| match &vb.value {
391 Value::IpAddress(addr) => Some(*addr),
392 _ => None,
393 })
394 .unwrap_or(default_addr);
395
396 let enterprise_oid = oids::snmp_trap_enterprise();
399 let varbinds: Vec<VarBind> = self.varbinds[2..]
400 .iter()
401 .filter(|vb| vb.oid != trap_address_oid && vb.oid != enterprise_oid)
402 .cloned()
403 .collect();
404
405 Some(TrapV1Pdu {
406 enterprise,
407 agent_addr,
408 generic_trap,
409 specific_trap,
410 time_stamp,
411 varbinds,
412 })
413 }
414
415 #[must_use]
417 pub fn is_notification(&self) -> bool {
418 matches!(
419 self.pdu_type,
420 PduType::TrapV1 | PduType::TrapV2 | PduType::InformRequest
421 )
422 }
423
424 #[must_use]
426 pub fn is_confirmed(&self) -> bool {
427 matches!(
428 self.pdu_type,
429 PduType::GetRequest
430 | PduType::GetNextRequest
431 | PduType::GetBulkRequest
432 | PduType::SetRequest
433 | PduType::InformRequest
434 )
435 }
436}
437
438#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
440pub enum GenericTrap {
441 ColdStart,
443 WarmStart,
445 LinkDown,
447 LinkUp,
449 AuthenticationFailure,
451 EgpNeighborLoss,
453 EnterpriseSpecific,
455 Unknown(i32),
457}
458
459impl std::fmt::Display for GenericTrap {
460 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
461 match self {
462 Self::ColdStart => write!(f, "coldStart"),
463 Self::WarmStart => write!(f, "warmStart"),
464 Self::LinkDown => write!(f, "linkDown"),
465 Self::LinkUp => write!(f, "linkUp"),
466 Self::AuthenticationFailure => write!(f, "authenticationFailure"),
467 Self::EgpNeighborLoss => write!(f, "egpNeighborLoss"),
468 Self::EnterpriseSpecific => write!(f, "enterpriseSpecific"),
469 Self::Unknown(v) => write!(f, "unknown({v})"),
470 }
471 }
472}
473
474impl GenericTrap {
475 #[must_use]
477 pub fn from_i32(v: i32) -> Self {
478 match v {
479 0 => Self::ColdStart,
480 1 => Self::WarmStart,
481 2 => Self::LinkDown,
482 3 => Self::LinkUp,
483 4 => Self::AuthenticationFailure,
484 5 => Self::EgpNeighborLoss,
485 6 => Self::EnterpriseSpecific,
486 _ => Self::Unknown(v),
487 }
488 }
489
490 #[must_use]
492 pub fn as_i32(self) -> i32 {
493 match self {
494 Self::ColdStart => 0,
495 Self::WarmStart => 1,
496 Self::LinkDown => 2,
497 Self::LinkUp => 3,
498 Self::AuthenticationFailure => 4,
499 Self::EgpNeighborLoss => 5,
500 Self::EnterpriseSpecific => 6,
501 Self::Unknown(v) => v,
502 }
503 }
504}
505
506#[derive(Debug, Clone, PartialEq, Eq)]
511pub struct TrapV1Pdu {
512 pub enterprise: Oid,
514 pub agent_addr: [u8; 4],
516 pub generic_trap: GenericTrap,
518 pub specific_trap: i32,
520 pub time_stamp: u32,
522 pub varbinds: Vec<VarBind>,
524}
525
526impl TrapV1Pdu {
527 #[must_use]
529 pub fn new(
530 enterprise: Oid,
531 agent_addr: [u8; 4],
532 generic_trap: GenericTrap,
533 specific_trap: i32,
534 time_stamp: u32,
535 varbinds: Vec<VarBind>,
536 ) -> Self {
537 Self {
538 enterprise,
539 agent_addr,
540 generic_trap,
541 specific_trap,
542 time_stamp,
543 varbinds,
544 }
545 }
546
547 #[must_use]
549 pub fn is_enterprise_specific(&self) -> bool {
550 self.generic_trap == GenericTrap::EnterpriseSpecific
551 }
552
553 pub fn v2_trap_oid(&self) -> crate::Result<Oid> {
600 if self.is_enterprise_specific() {
601 if self.specific_trap < 0 {
602 return Err(Error::InvalidOid("specific_trap cannot be negative".into()).boxed());
603 }
604 let mut arcs: Vec<u32> = self.enterprise.arcs().to_vec();
605 arcs.push(0);
606 arcs.push(self.specific_trap as u32);
607 Ok(Oid::new(arcs))
608 } else {
609 let raw = self.generic_trap.as_i32();
610 if raw < 0 {
611 return Err(Error::InvalidOid("generic_trap cannot be negative".into()).boxed());
612 }
613 if raw == i32::MAX {
614 return Err(Error::InvalidOid("generic_trap overflow".into()).boxed());
615 }
616 let trap_num = raw + 1;
617 Ok(crate::oid!(1, 3, 6, 1, 6, 3, 1, 1, 5).child(trap_num as u32))
618 }
619 }
620
621 pub fn to_v2_pdu(&self) -> crate::Result<Pdu> {
635 use crate::notification::oids;
636 use crate::value::Value;
637
638 let trap_oid = self.v2_trap_oid()?;
639
640 let mut varbinds = Vec::with_capacity(2 + self.varbinds.len());
641 varbinds.push(VarBind::new(
642 oids::sys_uptime(),
643 Value::TimeTicks(self.time_stamp),
644 ));
645 varbinds.push(VarBind::new(
646 oids::snmp_trap_oid(),
647 Value::ObjectIdentifier(trap_oid),
648 ));
649 varbinds.extend_from_slice(&self.varbinds);
650
651 Ok(Pdu {
652 pdu_type: PduType::TrapV2,
653 request_id: 0,
654 error_status: 0,
655 error_index: 0,
656 varbinds,
657 })
658 }
659
660 pub fn encode(&self, buf: &mut EncodeBuf) {
662 buf.push_constructed(tag::pdu::TRAP_V1, |buf| {
663 encode_varbind_list(buf, &self.varbinds);
664 buf.push_unsigned32(tag::application::TIMETICKS, self.time_stamp);
665 buf.push_integer(self.specific_trap);
666 buf.push_integer(self.generic_trap.as_i32());
667 buf.push_bytes(&self.agent_addr);
670 buf.push_length(4);
671 buf.push_tag(tag::application::IP_ADDRESS);
672 buf.push_oid(&self.enterprise);
673 });
674 }
675
676 pub fn decode(decoder: &mut Decoder) -> Result<Self> {
678 let mut pdu = decoder.read_constructed(tag::pdu::TRAP_V1)?;
679
680 let enterprise = pdu.read_oid()?;
682
683 let agent_tag = pdu.read_tag()?;
685 if agent_tag != tag::application::IP_ADDRESS {
686 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), expected = 0x40_u8, actual = agent_tag, kind = %DecodeErrorKind::UnexpectedTag {
687 expected: 0x40,
688 actual: agent_tag,
689 } }, "decode error");
690 return Err(Error::MalformedResponse {
691 target: UNKNOWN_TARGET,
692 }
693 .boxed());
694 }
695 let agent_len = pdu.read_length()?;
696 if agent_len != 4 {
697 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), length = agent_len, kind = %DecodeErrorKind::InvalidIpAddressLength { length: agent_len } }, "decode error");
698 return Err(Error::MalformedResponse {
699 target: UNKNOWN_TARGET,
700 }
701 .boxed());
702 }
703 let agent_bytes = pdu.read_bytes(4)?;
704 let agent_addr = [
705 agent_bytes[0],
706 agent_bytes[1],
707 agent_bytes[2],
708 agent_bytes[3],
709 ];
710
711 let generic_trap = GenericTrap::from_i32(pdu.read_integer()?);
713
714 let specific_trap = pdu.read_integer()?;
716
717 let ts_tag = pdu.read_tag()?;
719 if ts_tag != tag::application::TIMETICKS {
720 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), expected = 0x43_u8, actual = ts_tag, kind = %DecodeErrorKind::UnexpectedTag {
721 expected: 0x43,
722 actual: ts_tag,
723 } }, "decode error");
724 return Err(Error::MalformedResponse {
725 target: UNKNOWN_TARGET,
726 }
727 .boxed());
728 }
729 let ts_len = pdu.read_length()?;
730 let time_stamp = pdu.read_unsigned32_value(ts_len)?;
731
732 let varbinds = decode_varbind_list(&mut pdu)?;
734
735 Ok(TrapV1Pdu {
736 enterprise,
737 agent_addr,
738 generic_trap,
739 specific_trap,
740 time_stamp,
741 varbinds,
742 })
743 }
744}
745
746#[derive(Debug, Clone)]
748pub struct GetBulkPdu {
749 pub request_id: i32,
751 pub non_repeaters: i32,
753 pub max_repetitions: i32,
755 pub varbinds: Vec<VarBind>,
757}
758
759impl GetBulkPdu {
760 #[must_use]
762 pub fn new(request_id: i32, non_repeaters: i32, max_repetitions: i32, oids: &[Oid]) -> Self {
763 Self {
764 request_id,
765 non_repeaters,
766 max_repetitions,
767 varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
768 }
769 }
770
771 pub fn encode(&self, buf: &mut EncodeBuf) {
773 buf.push_constructed(tag::pdu::GET_BULK_REQUEST, |buf| {
774 encode_varbind_list(buf, &self.varbinds);
775 buf.push_integer(self.max_repetitions);
776 buf.push_integer(self.non_repeaters);
777 buf.push_integer(self.request_id);
778 });
779 }
780
781 pub fn decode(decoder: &mut Decoder) -> Result<Self> {
783 let mut pdu = decoder.read_constructed(tag::pdu::GET_BULK_REQUEST)?;
784
785 let request_id = pdu.read_integer()?;
786 let non_repeaters = pdu.read_integer()?;
787 let max_repetitions = pdu.read_integer()?;
788 let varbinds = decode_varbind_list(&mut pdu)?;
789
790 if non_repeaters < 0 {
792 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), non_repeaters = non_repeaters, kind = %DecodeErrorKind::NegativeNonRepeaters {
793 value: non_repeaters,
794 } }, "decode error");
795 return Err(Error::MalformedResponse {
796 target: UNKNOWN_TARGET,
797 }
798 .boxed());
799 }
800 if max_repetitions < 0 {
801 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), max_repetitions = max_repetitions, kind = %DecodeErrorKind::NegativeMaxRepetitions {
802 value: max_repetitions,
803 } }, "decode error");
804 return Err(Error::MalformedResponse {
805 target: UNKNOWN_TARGET,
806 }
807 .boxed());
808 }
809
810 Ok(GetBulkPdu {
811 request_id,
812 non_repeaters,
813 max_repetitions,
814 varbinds,
815 })
816 }
817}
818
819#[cfg(test)]
820mod tests {
821 use super::*;
822 use crate::oid;
823
824 struct RawPdu {
829 pdu_type: u8,
830 request_id: i32,
831 error_status: i32,
832 error_index: i32,
833 varbinds: Vec<VarBind>,
834 }
835
836 impl RawPdu {
837 fn response(
838 request_id: i32,
839 error_status: i32,
840 error_index: i32,
841 varbinds: Vec<VarBind>,
842 ) -> Self {
843 Self {
844 pdu_type: PduType::Response.tag(),
845 request_id,
846 error_status,
847 error_index,
848 varbinds,
849 }
850 }
851
852 fn encode(&self) -> bytes::Bytes {
853 let mut buf = EncodeBuf::new();
854 buf.push_constructed(self.pdu_type, |buf| {
855 encode_varbind_list(buf, &self.varbinds);
856 buf.push_integer(self.error_index);
857 buf.push_integer(self.error_status);
858 buf.push_integer(self.request_id);
859 });
860 buf.finish()
861 }
862 }
863
864 struct RawGetBulkPdu {
866 request_id: i32,
867 non_repeaters: i32,
868 max_repetitions: i32,
869 varbinds: Vec<VarBind>,
870 }
871
872 impl RawGetBulkPdu {
873 fn new(
874 request_id: i32,
875 non_repeaters: i32,
876 max_repetitions: i32,
877 varbinds: Vec<VarBind>,
878 ) -> Self {
879 Self {
880 request_id,
881 non_repeaters,
882 max_repetitions,
883 varbinds,
884 }
885 }
886
887 fn encode(&self) -> bytes::Bytes {
888 let mut buf = EncodeBuf::new();
889 buf.push_constructed(tag::pdu::GET_BULK_REQUEST, |buf| {
890 encode_varbind_list(buf, &self.varbinds);
891 buf.push_integer(self.max_repetitions);
892 buf.push_integer(self.non_repeaters);
893 buf.push_integer(self.request_id);
894 });
895 buf.finish()
896 }
897 }
898
899 #[test]
900 fn test_get_request_roundtrip() {
901 let pdu = Pdu::get_request(12345, &[oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)]);
902
903 let mut buf = EncodeBuf::new();
904 pdu.encode(&mut buf);
905 let bytes = buf.finish();
906
907 let mut decoder = Decoder::new(bytes);
908 let decoded = Pdu::decode(&mut decoder).unwrap();
909
910 assert_eq!(decoded.pdu_type, PduType::GetRequest);
911 assert_eq!(decoded.request_id, 12345);
912 assert_eq!(decoded.varbinds.len(), 1);
913 }
914
915 #[test]
916 fn test_getbulk_roundtrip() {
917 let pdu = GetBulkPdu::new(12345, 0, 10, &[oid!(1, 3, 6, 1, 2, 1, 1)]);
918
919 let mut buf = EncodeBuf::new();
920 pdu.encode(&mut buf);
921 let bytes = buf.finish();
922
923 let mut decoder = Decoder::new(bytes);
924 let decoded = GetBulkPdu::decode(&mut decoder).unwrap();
925
926 assert_eq!(decoded.request_id, 12345);
927 assert_eq!(decoded.non_repeaters, 0);
928 assert_eq!(decoded.max_repetitions, 10);
929 }
930
931 #[test]
932 fn test_trap_v1_roundtrip() {
933 use crate::value::Value;
934 use crate::varbind::VarBind;
935
936 let trap = TrapV1Pdu::new(
937 oid!(1, 3, 6, 1, 4, 1, 9999), [192, 168, 1, 1], GenericTrap::LinkDown,
940 0,
941 1234_5678, vec![VarBind::new(
943 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
944 Value::Integer(1),
945 )],
946 );
947
948 let mut buf = EncodeBuf::new();
949 trap.encode(&mut buf);
950 let bytes = buf.finish();
951
952 let mut decoder = Decoder::new(bytes);
953 let decoded = TrapV1Pdu::decode(&mut decoder).unwrap();
954
955 assert_eq!(decoded.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999));
956 assert_eq!(decoded.agent_addr, [192, 168, 1, 1]);
957 assert_eq!(decoded.generic_trap, GenericTrap::LinkDown);
958 assert_eq!(decoded.specific_trap, 0);
959 assert_eq!(decoded.time_stamp, 1234_5678);
960 assert_eq!(decoded.varbinds.len(), 1);
961 }
962
963 #[test]
964 fn test_trap_v1_enterprise_specific() {
965 let trap = TrapV1Pdu::new(
966 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
967 [10, 0, 0, 1],
968 GenericTrap::EnterpriseSpecific,
969 42, 100,
971 vec![],
972 );
973
974 assert!(trap.is_enterprise_specific());
975 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
976
977 let mut buf = EncodeBuf::new();
978 trap.encode(&mut buf);
979 let bytes = buf.finish();
980
981 let mut decoder = Decoder::new(bytes);
982 let decoded = TrapV1Pdu::decode(&mut decoder).unwrap();
983
984 assert_eq!(decoded.specific_trap, 42);
985 }
986
987 #[test]
988 fn test_trap_v1_v2_trap_oid_generic_traps() {
989 let test_cases = [
993 (GenericTrap::ColdStart, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1)),
994 (GenericTrap::WarmStart, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 2)),
995 (GenericTrap::LinkDown, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3)),
996 (GenericTrap::LinkUp, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 4)),
997 (
998 GenericTrap::AuthenticationFailure,
999 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 5),
1000 ),
1001 (
1002 GenericTrap::EgpNeighborLoss,
1003 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 6),
1004 ),
1005 ];
1006
1007 for (generic_trap, expected_oid) in test_cases {
1008 let trap = TrapV1Pdu::new(
1009 oid!(1, 3, 6, 1, 4, 1, 9999),
1010 [192, 168, 1, 1],
1011 generic_trap,
1012 0,
1013 12345,
1014 vec![],
1015 );
1016 assert_eq!(
1017 trap.v2_trap_oid().unwrap(),
1018 expected_oid,
1019 "Failed for {generic_trap:?}"
1020 );
1021 }
1022 }
1023
1024 #[test]
1025 fn test_trap_v1_v2_trap_oid_enterprise_specific() {
1026 let trap = TrapV1Pdu::new(
1028 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
1029 [192, 168, 1, 1],
1030 GenericTrap::EnterpriseSpecific,
1031 42,
1032 12345,
1033 vec![],
1034 );
1035
1036 assert_eq!(
1038 trap.v2_trap_oid().unwrap(),
1039 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2, 0, 42)
1040 );
1041 }
1042
1043 #[test]
1044 fn test_trap_v1_v2_trap_oid_enterprise_specific_zero() {
1045 let trap = TrapV1Pdu::new(
1047 oid!(1, 3, 6, 1, 4, 1, 1234),
1048 [10, 0, 0, 1],
1049 GenericTrap::EnterpriseSpecific,
1050 0,
1051 100,
1052 vec![],
1053 );
1054
1055 assert_eq!(
1057 trap.v2_trap_oid().unwrap(),
1058 oid!(1, 3, 6, 1, 4, 1, 1234, 0, 0)
1059 );
1060 }
1061
1062 #[test]
1063 fn test_pdu_to_response() {
1064 use crate::value::Value;
1065 use crate::varbind::VarBind;
1066
1067 let inform = Pdu {
1068 pdu_type: PduType::InformRequest,
1069 request_id: 99999,
1070 error_status: 0,
1071 error_index: 0,
1072 varbinds: vec![
1073 VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::TimeTicks(12345)),
1074 VarBind::new(
1075 oid!(1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0),
1076 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1)),
1077 ),
1078 ],
1079 };
1080
1081 let response = inform.to_response();
1082
1083 assert_eq!(response.pdu_type, PduType::Response);
1084 assert_eq!(response.request_id, 99999);
1085 assert_eq!(response.error_status, 0);
1086 assert_eq!(response.error_index, 0);
1087 assert_eq!(response.varbinds.len(), 2);
1088 }
1089
1090 #[test]
1091 fn test_pdu_is_confirmed() {
1092 let get = Pdu::get_request(1, &[oid!(1, 3, 6, 1)]);
1093 assert!(get.is_confirmed());
1094
1095 let inform = Pdu {
1096 pdu_type: PduType::InformRequest,
1097 request_id: 1,
1098 error_status: 0,
1099 error_index: 0,
1100 varbinds: vec![],
1101 };
1102 assert!(inform.is_confirmed());
1103
1104 let trap = Pdu {
1105 pdu_type: PduType::TrapV2,
1106 request_id: 1,
1107 error_status: 0,
1108 error_index: 0,
1109 varbinds: vec![],
1110 };
1111 assert!(!trap.is_confirmed());
1112 assert!(trap.is_notification());
1113 }
1114
1115 #[test]
1116 fn test_decode_accepts_negative_error_index() {
1117 let raw = RawPdu::response(1, 0, -1, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1123 let encoded = raw.encode();
1124
1125 let mut decoder = Decoder::new(encoded);
1126 let result = Pdu::decode(&mut decoder);
1127
1128 assert!(
1129 result.is_ok(),
1130 "negative error_index must be accepted to match net-snmp behavior, got {:?}",
1131 result.err()
1132 );
1133 assert_eq!(result.unwrap().error_index, -1);
1134 }
1135
1136 #[test]
1137 fn test_decode_accepts_error_index_beyond_varbinds() {
1138 let raw = RawPdu::response(1, 5, 5, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1144 let encoded = raw.encode();
1145
1146 let mut decoder = Decoder::new(encoded);
1147 let result = Pdu::decode(&mut decoder);
1148
1149 assert!(
1150 result.is_ok(),
1151 "error_index beyond varbind count must be accepted to match net-snmp behavior, got {:?}",
1152 result.err()
1153 );
1154 assert_eq!(result.unwrap().error_index, 5);
1155 }
1156
1157 #[test]
1158 fn test_decode_accepts_valid_error_index_zero() {
1159 let raw = RawPdu::response(1, 0, 0, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1161 let encoded = raw.encode();
1162
1163 let mut decoder = Decoder::new(encoded);
1164 let decoded = Pdu::decode(&mut decoder);
1165 assert!(decoded.is_ok(), "error_index=0 should be valid");
1166 }
1167
1168 #[test]
1169 fn test_decode_accepts_error_index_within_bounds() {
1170 let raw = RawPdu::response(1, 5, 1, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1172 let encoded = raw.encode();
1173
1174 let mut decoder = Decoder::new(encoded);
1175 let result = Pdu::decode(&mut decoder);
1176 assert!(
1177 result.is_ok(),
1178 "error_index=1 with 1 varbind should be valid"
1179 );
1180 }
1181
1182 #[test]
1183 fn test_decode_rejects_negative_non_repeaters() {
1184 let raw = RawGetBulkPdu::new(1, -1, 10, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1185 let encoded = raw.encode();
1186
1187 let mut decoder = Decoder::new(encoded);
1188 let result = GetBulkPdu::decode(&mut decoder);
1189
1190 assert!(result.is_err(), "should reject negative non_repeaters");
1191 let err = result.unwrap_err();
1192 assert!(
1193 matches!(&*err, crate::error::Error::MalformedResponse { .. }),
1194 "expected MalformedResponse, got {err:?}"
1195 );
1196 }
1197
1198 #[test]
1199 fn test_decode_rejects_negative_max_repetitions() {
1200 let raw = RawGetBulkPdu::new(1, 0, -5, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1201 let encoded = raw.encode();
1202
1203 let mut decoder = Decoder::new(encoded);
1204 let result = GetBulkPdu::decode(&mut decoder);
1205
1206 assert!(result.is_err(), "should reject negative max_repetitions");
1207 let err = result.unwrap_err();
1208 assert!(
1209 matches!(&*err, crate::error::Error::MalformedResponse { .. }),
1210 "expected MalformedResponse, got {err:?}"
1211 );
1212 }
1213
1214 #[test]
1215 fn test_decode_accepts_valid_getbulk_params() {
1216 let raw = RawGetBulkPdu::new(1, 0, 10, vec![VarBind::null(oid!(1, 3, 6, 1))]);
1217 let encoded = raw.encode();
1218
1219 let mut decoder = Decoder::new(encoded);
1220 let result = GetBulkPdu::decode(&mut decoder);
1221 assert!(result.is_ok(), "valid GETBULK params should be accepted");
1222
1223 let pdu = result.unwrap();
1224 assert_eq!(pdu.non_repeaters, 0);
1225 assert_eq!(pdu.max_repetitions, 10);
1226 }
1227
1228 #[test]
1229 fn test_pdu_decode_getbulk_with_large_max_repetitions() {
1230 let raw = RawGetBulkPdu::new(12345, 0, 25, vec![VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1))]);
1234 let encoded = raw.encode();
1235
1236 let mut decoder = Decoder::new(encoded);
1237 let result = Pdu::decode(&mut decoder);
1238 assert!(
1239 result.is_ok(),
1240 "Pdu::decode should accept GETBULK with max_repetitions > varbinds.len(), got {:?}",
1241 result.err()
1242 );
1243
1244 let pdu = result.unwrap();
1245 assert_eq!(pdu.pdu_type, PduType::GetBulkRequest);
1246 assert_eq!(pdu.request_id, 12345);
1247 assert_eq!(pdu.error_status, 0);
1249 assert_eq!(pdu.error_index, 25);
1250 assert_eq!(pdu.varbinds.len(), 1);
1251 }
1252
1253 #[test]
1254 fn test_getbulk_request_is_not_treated_as_error() {
1255 let pdu = Pdu::get_bulk(
1256 12345,
1257 2,
1258 10,
1259 vec![
1260 VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1)),
1261 VarBind::null(oid!(1, 3, 6, 1, 2, 1, 2)),
1262 ],
1263 );
1264
1265 assert!(!pdu.is_error());
1266 }
1267
1268 #[test]
1269 fn test_response_with_error_status_is_treated_as_error() {
1270 let pdu = Pdu {
1271 pdu_type: PduType::Response,
1272 request_id: 12345,
1273 error_status: ErrorStatus::TooBig.as_i32(),
1274 error_index: 1,
1275 varbinds: vec![VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1))],
1276 };
1277
1278 assert!(pdu.is_error());
1279 }
1280
1281 #[test]
1282 fn pdu_type_hash() {
1283 use std::collections::HashSet;
1284 let mut set = HashSet::new();
1285 set.insert(PduType::GetRequest);
1286 set.insert(PduType::GetNextRequest);
1287 assert_eq!(set.len(), 2);
1288 assert!(set.contains(&PduType::GetRequest));
1289 }
1290
1291 #[test]
1296 fn test_v1_to_v2_generic_trap() {
1297 use crate::value::Value;
1298 use crate::varbind::VarBind;
1299
1300 let trap = TrapV1Pdu::new(
1301 oid!(1, 3, 6, 1, 4, 1, 9999),
1302 [192, 168, 1, 1],
1303 GenericTrap::LinkDown,
1304 0,
1305 12345,
1306 vec![VarBind::new(
1307 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
1308 Value::Integer(1),
1309 )],
1310 );
1311
1312 let pdu = trap.to_v2_pdu().unwrap();
1313
1314 assert_eq!(pdu.pdu_type, PduType::TrapV2);
1315 assert_eq!(pdu.request_id, 0);
1316 assert_eq!(pdu.varbinds.len(), 3);
1318
1319 assert_eq!(pdu.varbinds[0].oid, oid!(1, 3, 6, 1, 2, 1, 1, 3, 0));
1321 assert_eq!(pdu.varbinds[0].value, Value::TimeTicks(12345));
1322
1323 assert_eq!(pdu.varbinds[1].oid, oid!(1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0));
1325 assert_eq!(
1326 pdu.varbinds[1].value,
1327 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3))
1328 );
1329
1330 assert_eq!(pdu.varbinds[2].oid, oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1));
1332 }
1333
1334 #[test]
1335 fn test_v1_to_v2_no_proxy_varbinds() {
1336 let trap = TrapV1Pdu::new(
1337 oid!(1, 3, 6, 1, 4, 1, 9999),
1338 [192, 168, 1, 1],
1339 GenericTrap::ColdStart,
1340 0,
1341 100,
1342 vec![],
1343 );
1344
1345 let pdu = trap.to_v2_pdu().unwrap();
1346 assert_eq!(pdu.varbinds.len(), 2);
1349 }
1350
1351 #[test]
1352 fn test_v1_to_v2_enterprise_specific() {
1353 use crate::value::Value;
1354
1355 let trap = TrapV1Pdu::new(
1356 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
1357 [10, 0, 0, 1],
1358 GenericTrap::EnterpriseSpecific,
1359 42,
1360 5000,
1361 vec![],
1362 );
1363
1364 let pdu = trap.to_v2_pdu().unwrap();
1365
1366 assert_eq!(
1368 pdu.varbinds[1].value,
1369 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2, 0, 42))
1370 );
1371 }
1372
1373 #[test]
1374 fn test_v2_to_v1_standard_trap() {
1375 use crate::value::Value;
1376 use crate::varbind::VarBind;
1377
1378 let pdu = Pdu::trap_v2(
1379 1,
1380 5000,
1381 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3), vec![VarBind::new(
1383 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
1384 Value::Integer(1),
1385 )],
1386 );
1387
1388 let trap = pdu.to_v1_trap([10, 0, 0, 1]).unwrap();
1389
1390 assert_eq!(trap.generic_trap, GenericTrap::LinkDown);
1391 assert_eq!(trap.specific_trap, 0);
1392 assert_eq!(trap.time_stamp, 5000);
1393 assert_eq!(trap.agent_addr, [10, 0, 0, 1]);
1394 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5));
1396 assert_eq!(trap.varbinds.len(), 1);
1397 }
1398
1399 #[test]
1400 fn test_v2_to_v1_enterprise_specific_trap() {
1401 let pdu = Pdu::trap_v2(1, 100, &oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2, 0, 42), vec![]);
1402
1403 let trap = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1404
1405 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
1406 assert_eq!(trap.specific_trap, 42);
1407 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2));
1408 assert_eq!(trap.time_stamp, 100);
1409 }
1410
1411 #[test]
1412 fn test_v2_to_v1_enterprise_specific_nonzero_penultimate() {
1413 let pdu = Pdu::trap_v2(1, 200, &oid!(1, 3, 6, 1, 4, 1, 9999, 1, 42), vec![]);
1416
1417 let trap = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1418
1419 assert_eq!(trap.generic_trap, GenericTrap::EnterpriseSpecific);
1420 assert_eq!(trap.specific_trap, 42);
1421 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999, 1));
1423 assert_eq!(trap.time_stamp, 200);
1424 }
1425
1426 #[test]
1427 fn test_v2_to_v1_extracts_trap_address() {
1428 use crate::notification::oids;
1429 use crate::value::Value;
1430 use crate::varbind::VarBind;
1431
1432 let pdu = Pdu::trap_v2(
1433 1,
1434 0,
1435 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1), vec![VarBind::new(
1437 oids::snmp_trap_address(),
1438 Value::IpAddress([192, 168, 1, 1]),
1439 )],
1440 );
1441
1442 let trap = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1443 assert_eq!(trap.agent_addr, [192, 168, 1, 1]);
1444 assert!(trap.varbinds.is_empty());
1446 }
1447
1448 #[test]
1449 fn test_v2_to_v1_extracts_trap_enterprise() {
1450 use crate::notification::oids;
1451 use crate::value::Value;
1452 use crate::varbind::VarBind;
1453
1454 let pdu = Pdu::trap_v2(
1455 1,
1456 0,
1457 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1), vec![VarBind::new(
1459 oids::snmp_trap_enterprise(),
1460 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4, 1, 9999)),
1461 )],
1462 );
1463
1464 let trap = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1465 assert_eq!(trap.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999));
1467 assert!(trap.varbinds.is_empty());
1469 }
1470
1471 #[test]
1472 fn test_v2_to_v1_counter64_dropped() {
1473 use crate::value::Value;
1474 use crate::varbind::VarBind;
1475
1476 let pdu = Pdu::trap_v2(
1477 1,
1478 0,
1479 &oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1),
1480 vec![VarBind::new(
1481 oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
1482 Value::Counter64(12345),
1483 )],
1484 );
1485
1486 assert!(pdu.to_v1_trap([0, 0, 0, 0]).is_none());
1488 }
1489
1490 #[test]
1491 fn test_v2_to_v1_too_few_varbinds() {
1492 let pdu = Pdu {
1493 pdu_type: PduType::TrapV2,
1494 request_id: 1,
1495 error_status: 0,
1496 error_index: 0,
1497 varbinds: vec![],
1498 };
1499
1500 assert!(pdu.to_v1_trap([0, 0, 0, 0]).is_none());
1501 }
1502
1503 #[test]
1504 fn test_v1_v2_roundtrip_enterprise_specific() {
1505 use crate::value::Value;
1506 use crate::varbind::VarBind;
1507
1508 let original = TrapV1Pdu::new(
1513 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
1514 [192, 168, 1, 1],
1515 GenericTrap::EnterpriseSpecific,
1516 42,
1517 12345,
1518 vec![VarBind::new(
1519 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
1520 Value::Integer(1),
1521 )],
1522 );
1523
1524 let v2 = original.to_v2_pdu().unwrap();
1525 let restored = v2.to_v1_trap([0, 0, 0, 0]).unwrap();
1526
1527 assert_eq!(restored.enterprise, original.enterprise);
1528 assert_eq!(restored.generic_trap, original.generic_trap);
1529 assert_eq!(restored.specific_trap, original.specific_trap);
1530 assert_eq!(restored.time_stamp, original.time_stamp);
1531 assert_eq!(restored.varbinds.len(), original.varbinds.len());
1532 assert_eq!(restored.varbinds[0].oid, original.varbinds[0].oid);
1533 assert_eq!(restored.agent_addr, [0, 0, 0, 0]);
1535 }
1536
1537 #[test]
1538 fn test_v1_v2_roundtrip_standard_trap() {
1539 let original = TrapV1Pdu::new(
1543 oid!(1, 3, 6, 1, 4, 1, 9999),
1544 [10, 0, 0, 1],
1545 GenericTrap::WarmStart,
1546 0,
1547 500,
1548 vec![],
1549 );
1550
1551 let v2 = original.to_v2_pdu().unwrap();
1552 let restored = v2.to_v1_trap([10, 0, 0, 1]).unwrap();
1553
1554 assert_eq!(restored.generic_trap, GenericTrap::WarmStart);
1555 assert_eq!(restored.specific_trap, 0);
1556 assert_eq!(restored.time_stamp, 500);
1557 assert_eq!(restored.agent_addr, [10, 0, 0, 1]); assert_eq!(restored.enterprise, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5));
1560 }
1561
1562 #[test]
1563 fn test_v2_to_v1_all_generic_traps() {
1564 let traps = [
1566 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1), GenericTrap::ColdStart),
1567 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 2), GenericTrap::WarmStart),
1568 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3), GenericTrap::LinkDown),
1569 (oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 4), GenericTrap::LinkUp),
1570 (
1571 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 5),
1572 GenericTrap::AuthenticationFailure,
1573 ),
1574 (
1575 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 6),
1576 GenericTrap::EgpNeighborLoss,
1577 ),
1578 ];
1579
1580 for (trap_oid, expected_generic) in traps {
1581 let pdu = Pdu::trap_v2(1, 100, &trap_oid, vec![]);
1582 let v1 = pdu.to_v1_trap([0, 0, 0, 0]).unwrap();
1583 assert_eq!(v1.generic_trap, expected_generic, "Failed for {trap_oid:?}");
1584 assert_eq!(v1.specific_trap, 0);
1585 }
1586 }
1587}