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)]
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 pub fn from_tag(tag: u8) -> Option<Self> {
38 match tag {
39 0xA0 => Some(Self::GetRequest),
40 0xA1 => Some(Self::GetNextRequest),
41 0xA2 => Some(Self::Response),
42 0xA3 => Some(Self::SetRequest),
43 0xA4 => Some(Self::TrapV1),
44 0xA5 => Some(Self::GetBulkRequest),
45 0xA6 => Some(Self::InformRequest),
46 0xA7 => Some(Self::TrapV2),
47 0xA8 => Some(Self::Report),
48 _ => None,
49 }
50 }
51
52 pub fn tag(self) -> u8 {
54 self as u8
55 }
56}
57
58impl std::fmt::Display for PduType {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 match self {
61 Self::GetRequest => write!(f, "GetRequest"),
62 Self::GetNextRequest => write!(f, "GetNextRequest"),
63 Self::Response => write!(f, "Response"),
64 Self::SetRequest => write!(f, "SetRequest"),
65 Self::TrapV1 => write!(f, "TrapV1"),
66 Self::GetBulkRequest => write!(f, "GetBulkRequest"),
67 Self::InformRequest => write!(f, "InformRequest"),
68 Self::TrapV2 => write!(f, "TrapV2"),
69 Self::Report => write!(f, "Report"),
70 }
71 }
72}
73
74#[derive(Debug, Clone)]
76pub struct Pdu {
77 pub pdu_type: PduType,
79 pub request_id: i32,
81 pub error_status: i32,
83 pub error_index: i32,
85 pub varbinds: Vec<VarBind>,
87}
88
89impl Pdu {
90 pub fn get_request(request_id: i32, oids: &[Oid]) -> Self {
92 Self {
93 pdu_type: PduType::GetRequest,
94 request_id,
95 error_status: 0,
96 error_index: 0,
97 varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
98 }
99 }
100
101 pub fn get_next_request(request_id: i32, oids: &[Oid]) -> Self {
103 Self {
104 pdu_type: PduType::GetNextRequest,
105 request_id,
106 error_status: 0,
107 error_index: 0,
108 varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
109 }
110 }
111
112 pub fn set_request(request_id: i32, varbinds: Vec<VarBind>) -> Self {
114 Self {
115 pdu_type: PduType::SetRequest,
116 request_id,
117 error_status: 0,
118 error_index: 0,
119 varbinds,
120 }
121 }
122
123 pub fn get_bulk(
127 request_id: i32,
128 non_repeaters: i32,
129 max_repetitions: i32,
130 varbinds: Vec<VarBind>,
131 ) -> Self {
132 Self {
133 pdu_type: PduType::GetBulkRequest,
134 request_id,
135 error_status: non_repeaters,
136 error_index: max_repetitions,
137 varbinds,
138 }
139 }
140
141 pub fn encode(&self, buf: &mut EncodeBuf) {
143 buf.push_constructed(self.pdu_type.tag(), |buf| {
144 encode_varbind_list(buf, &self.varbinds);
145 buf.push_integer(self.error_index);
146 buf.push_integer(self.error_status);
147 buf.push_integer(self.request_id);
148 });
149 }
150
151 pub fn decode(decoder: &mut Decoder) -> Result<Self> {
153 let tag = decoder.read_tag()?;
154 let pdu_type = PduType::from_tag(tag).ok_or_else(|| {
155 tracing::debug!(target: "async_snmp::pdu", { offset = decoder.offset(), tag = tag, kind = %DecodeErrorKind::UnknownPduType(tag) }, "decode error");
156 Error::MalformedResponse {
157 target: UNKNOWN_TARGET,
158 }
159 .boxed()
160 })?;
161
162 let len = decoder.read_length()?;
163 let mut pdu_decoder = decoder.sub_decoder(len)?;
164
165 let request_id = pdu_decoder.read_integer()?;
166 let error_status = pdu_decoder.read_integer()?;
167 let error_index = pdu_decoder.read_integer()?;
168 let varbinds = decode_varbind_list(&mut pdu_decoder)?;
169
170 if pdu_type != PduType::GetBulkRequest {
175 if error_index < 0 {
176 tracing::debug!(target: "async_snmp::pdu", { offset = pdu_decoder.offset(), error_index = error_index, kind = %DecodeErrorKind::NegativeErrorIndex { value: error_index } }, "decode error");
177 return Err(Error::MalformedResponse {
178 target: UNKNOWN_TARGET,
179 }
180 .boxed());
181 }
182 if error_index > 0 && (error_index as usize) > varbinds.len() {
183 tracing::debug!(target: "async_snmp::pdu", { offset = pdu_decoder.offset(), error_index = error_index, varbind_count = varbinds.len(), kind = %DecodeErrorKind::ErrorIndexOutOfBounds {
184 index: error_index,
185 varbind_count: varbinds.len(),
186 } }, "decode error");
187 return Err(Error::MalformedResponse {
188 target: UNKNOWN_TARGET,
189 }
190 .boxed());
191 }
192 }
193
194 Ok(Pdu {
195 pdu_type,
196 request_id,
197 error_status,
198 error_index,
199 varbinds,
200 })
201 }
202
203 pub fn is_error(&self) -> bool {
205 self.error_status != 0
206 }
207
208 pub fn error_status_enum(&self) -> ErrorStatus {
210 ErrorStatus::from_i32(self.error_status)
211 }
212
213 pub fn to_response(&self) -> Self {
218 Self {
219 pdu_type: PduType::Response,
220 request_id: self.request_id,
221 error_status: 0,
222 error_index: 0,
223 varbinds: self.varbinds.clone(),
224 }
225 }
226
227 pub fn to_error_response(&self, error_status: ErrorStatus, error_index: i32) -> Self {
229 Self {
230 pdu_type: PduType::Response,
231 request_id: self.request_id,
232 error_status: error_status.as_i32(),
233 error_index,
234 varbinds: self.varbinds.clone(),
235 }
236 }
237
238 pub fn is_notification(&self) -> bool {
240 matches!(
241 self.pdu_type,
242 PduType::TrapV1 | PduType::TrapV2 | PduType::InformRequest
243 )
244 }
245
246 pub fn is_confirmed(&self) -> bool {
248 matches!(
249 self.pdu_type,
250 PduType::GetRequest
251 | PduType::GetNextRequest
252 | PduType::GetBulkRequest
253 | PduType::SetRequest
254 | PduType::InformRequest
255 )
256 }
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
261#[repr(i32)]
262pub enum GenericTrap {
263 ColdStart = 0,
265 WarmStart = 1,
267 LinkDown = 2,
269 LinkUp = 3,
271 AuthenticationFailure = 4,
273 EgpNeighborLoss = 5,
275 EnterpriseSpecific = 6,
277}
278
279impl GenericTrap {
280 pub fn from_i32(v: i32) -> Option<Self> {
282 match v {
283 0 => Some(Self::ColdStart),
284 1 => Some(Self::WarmStart),
285 2 => Some(Self::LinkDown),
286 3 => Some(Self::LinkUp),
287 4 => Some(Self::AuthenticationFailure),
288 5 => Some(Self::EgpNeighborLoss),
289 6 => Some(Self::EnterpriseSpecific),
290 _ => None,
291 }
292 }
293
294 pub fn as_i32(self) -> i32 {
296 self as i32
297 }
298}
299
300#[derive(Debug, Clone)]
305pub struct TrapV1Pdu {
306 pub enterprise: Oid,
308 pub agent_addr: [u8; 4],
310 pub generic_trap: i32,
312 pub specific_trap: i32,
314 pub time_stamp: u32,
316 pub varbinds: Vec<VarBind>,
318}
319
320impl TrapV1Pdu {
321 pub fn new(
323 enterprise: Oid,
324 agent_addr: [u8; 4],
325 generic_trap: GenericTrap,
326 specific_trap: i32,
327 time_stamp: u32,
328 varbinds: Vec<VarBind>,
329 ) -> Self {
330 Self {
331 enterprise,
332 agent_addr,
333 generic_trap: generic_trap.as_i32(),
334 specific_trap,
335 time_stamp,
336 varbinds,
337 }
338 }
339
340 pub fn generic_trap_enum(&self) -> Option<GenericTrap> {
342 GenericTrap::from_i32(self.generic_trap)
343 }
344
345 pub fn is_enterprise_specific(&self) -> bool {
347 self.generic_trap == GenericTrap::EnterpriseSpecific as i32
348 }
349
350 pub fn v2_trap_oid(&self) -> crate::Result<Oid> {
397 if self.is_enterprise_specific() {
398 if self.specific_trap < 0 {
399 return Err(Error::InvalidOid("specific_trap cannot be negative".into()).boxed());
400 }
401 let mut arcs: Vec<u32> = self.enterprise.arcs().to_vec();
402 arcs.push(0);
403 arcs.push(self.specific_trap as u32);
404 Ok(Oid::new(arcs))
405 } else {
406 if self.generic_trap < 0 {
407 return Err(Error::InvalidOid("generic_trap cannot be negative".into()).boxed());
408 }
409 if self.generic_trap == i32::MAX {
410 return Err(Error::InvalidOid("generic_trap overflow".into()).boxed());
411 }
412 let trap_num = self.generic_trap + 1;
413 Ok(crate::oid!(1, 3, 6, 1, 6, 3, 1, 1, 5).child(trap_num as u32))
414 }
415 }
416
417 pub fn encode(&self, buf: &mut EncodeBuf) {
419 buf.push_constructed(tag::pdu::TRAP_V1, |buf| {
420 encode_varbind_list(buf, &self.varbinds);
421 buf.push_unsigned32(tag::application::TIMETICKS, self.time_stamp);
422 buf.push_integer(self.specific_trap);
423 buf.push_integer(self.generic_trap);
424 buf.push_bytes(&self.agent_addr);
427 buf.push_length(4);
428 buf.push_tag(tag::application::IP_ADDRESS);
429 buf.push_oid(&self.enterprise);
430 });
431 }
432
433 pub fn decode(decoder: &mut Decoder) -> Result<Self> {
435 let mut pdu = decoder.read_constructed(tag::pdu::TRAP_V1)?;
436
437 let enterprise = pdu.read_oid()?;
439
440 let agent_tag = pdu.read_tag()?;
442 if agent_tag != tag::application::IP_ADDRESS {
443 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), expected = 0x40_u8, actual = agent_tag, kind = %DecodeErrorKind::UnexpectedTag {
444 expected: 0x40,
445 actual: agent_tag,
446 } }, "decode error");
447 return Err(Error::MalformedResponse {
448 target: UNKNOWN_TARGET,
449 }
450 .boxed());
451 }
452 let agent_len = pdu.read_length()?;
453 if agent_len != 4 {
454 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), length = agent_len, kind = %DecodeErrorKind::InvalidIpAddressLength { length: agent_len } }, "decode error");
455 return Err(Error::MalformedResponse {
456 target: UNKNOWN_TARGET,
457 }
458 .boxed());
459 }
460 let agent_bytes = pdu.read_bytes(4)?;
461 let agent_addr = [
462 agent_bytes[0],
463 agent_bytes[1],
464 agent_bytes[2],
465 agent_bytes[3],
466 ];
467
468 let generic_trap = pdu.read_integer()?;
470
471 let specific_trap = pdu.read_integer()?;
473
474 let ts_tag = pdu.read_tag()?;
476 if ts_tag != tag::application::TIMETICKS {
477 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), expected = 0x43_u8, actual = ts_tag, kind = %DecodeErrorKind::UnexpectedTag {
478 expected: 0x43,
479 actual: ts_tag,
480 } }, "decode error");
481 return Err(Error::MalformedResponse {
482 target: UNKNOWN_TARGET,
483 }
484 .boxed());
485 }
486 let ts_len = pdu.read_length()?;
487 let time_stamp = pdu.read_unsigned32_value(ts_len)?;
488
489 let varbinds = decode_varbind_list(&mut pdu)?;
491
492 Ok(TrapV1Pdu {
493 enterprise,
494 agent_addr,
495 generic_trap,
496 specific_trap,
497 time_stamp,
498 varbinds,
499 })
500 }
501}
502
503#[derive(Debug, Clone)]
505pub struct GetBulkPdu {
506 pub request_id: i32,
508 pub non_repeaters: i32,
510 pub max_repetitions: i32,
512 pub varbinds: Vec<VarBind>,
514}
515
516impl GetBulkPdu {
517 pub fn new(request_id: i32, non_repeaters: i32, max_repetitions: i32, oids: &[Oid]) -> Self {
519 Self {
520 request_id,
521 non_repeaters,
522 max_repetitions,
523 varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
524 }
525 }
526
527 pub fn encode(&self, buf: &mut EncodeBuf) {
529 buf.push_constructed(tag::pdu::GET_BULK_REQUEST, |buf| {
530 encode_varbind_list(buf, &self.varbinds);
531 buf.push_integer(self.max_repetitions);
532 buf.push_integer(self.non_repeaters);
533 buf.push_integer(self.request_id);
534 });
535 }
536
537 pub fn decode(decoder: &mut Decoder) -> Result<Self> {
539 let mut pdu = decoder.read_constructed(tag::pdu::GET_BULK_REQUEST)?;
540
541 let request_id = pdu.read_integer()?;
542 let non_repeaters = pdu.read_integer()?;
543 let max_repetitions = pdu.read_integer()?;
544 let varbinds = decode_varbind_list(&mut pdu)?;
545
546 if non_repeaters < 0 {
548 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), non_repeaters = non_repeaters, kind = %DecodeErrorKind::NegativeNonRepeaters {
549 value: non_repeaters,
550 } }, "decode error");
551 return Err(Error::MalformedResponse {
552 target: UNKNOWN_TARGET,
553 }
554 .boxed());
555 }
556 if max_repetitions < 0 {
557 tracing::debug!(target: "async_snmp::pdu", { offset = pdu.offset(), max_repetitions = max_repetitions, kind = %DecodeErrorKind::NegativeMaxRepetitions {
558 value: max_repetitions,
559 } }, "decode error");
560 return Err(Error::MalformedResponse {
561 target: UNKNOWN_TARGET,
562 }
563 .boxed());
564 }
565
566 Ok(GetBulkPdu {
567 request_id,
568 non_repeaters,
569 max_repetitions,
570 varbinds,
571 })
572 }
573}
574
575#[cfg(test)]
576mod tests {
577 use super::*;
578 use crate::oid;
579
580 struct RawPdu {
585 pdu_type: u8,
586 request_id: i32,
587 error_status: i32,
588 error_index: i32,
589 varbinds: Vec<VarBind>,
590 }
591
592 impl RawPdu {
593 fn response(
594 request_id: i32,
595 error_status: i32,
596 error_index: i32,
597 varbinds: Vec<VarBind>,
598 ) -> Self {
599 Self {
600 pdu_type: PduType::Response.tag(),
601 request_id,
602 error_status,
603 error_index,
604 varbinds,
605 }
606 }
607
608 fn encode(&self) -> bytes::Bytes {
609 let mut buf = EncodeBuf::new();
610 buf.push_constructed(self.pdu_type, |buf| {
611 encode_varbind_list(buf, &self.varbinds);
612 buf.push_integer(self.error_index);
613 buf.push_integer(self.error_status);
614 buf.push_integer(self.request_id);
615 });
616 buf.finish()
617 }
618 }
619
620 struct RawGetBulkPdu {
622 request_id: i32,
623 non_repeaters: i32,
624 max_repetitions: i32,
625 varbinds: Vec<VarBind>,
626 }
627
628 impl RawGetBulkPdu {
629 fn new(
630 request_id: i32,
631 non_repeaters: i32,
632 max_repetitions: i32,
633 varbinds: Vec<VarBind>,
634 ) -> Self {
635 Self {
636 request_id,
637 non_repeaters,
638 max_repetitions,
639 varbinds,
640 }
641 }
642
643 fn encode(&self) -> bytes::Bytes {
644 let mut buf = EncodeBuf::new();
645 buf.push_constructed(tag::pdu::GET_BULK_REQUEST, |buf| {
646 encode_varbind_list(buf, &self.varbinds);
647 buf.push_integer(self.max_repetitions);
648 buf.push_integer(self.non_repeaters);
649 buf.push_integer(self.request_id);
650 });
651 buf.finish()
652 }
653 }
654
655 #[test]
656 fn test_get_request_roundtrip() {
657 let pdu = Pdu::get_request(12345, &[oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)]);
658
659 let mut buf = EncodeBuf::new();
660 pdu.encode(&mut buf);
661 let bytes = buf.finish();
662
663 let mut decoder = Decoder::new(bytes);
664 let decoded = Pdu::decode(&mut decoder).unwrap();
665
666 assert_eq!(decoded.pdu_type, PduType::GetRequest);
667 assert_eq!(decoded.request_id, 12345);
668 assert_eq!(decoded.varbinds.len(), 1);
669 }
670
671 #[test]
672 fn test_getbulk_roundtrip() {
673 let pdu = GetBulkPdu::new(12345, 0, 10, &[oid!(1, 3, 6, 1, 2, 1, 1)]);
674
675 let mut buf = EncodeBuf::new();
676 pdu.encode(&mut buf);
677 let bytes = buf.finish();
678
679 let mut decoder = Decoder::new(bytes);
680 let decoded = GetBulkPdu::decode(&mut decoder).unwrap();
681
682 assert_eq!(decoded.request_id, 12345);
683 assert_eq!(decoded.non_repeaters, 0);
684 assert_eq!(decoded.max_repetitions, 10);
685 }
686
687 #[test]
688 fn test_trap_v1_roundtrip() {
689 use crate::value::Value;
690 use crate::varbind::VarBind;
691
692 let trap = TrapV1Pdu::new(
693 oid!(1, 3, 6, 1, 4, 1, 9999), [192, 168, 1, 1], GenericTrap::LinkDown,
696 0,
697 12345678, vec![VarBind::new(
699 oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
700 Value::Integer(1),
701 )],
702 );
703
704 let mut buf = EncodeBuf::new();
705 trap.encode(&mut buf);
706 let bytes = buf.finish();
707
708 let mut decoder = Decoder::new(bytes);
709 let decoded = TrapV1Pdu::decode(&mut decoder).unwrap();
710
711 assert_eq!(decoded.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999));
712 assert_eq!(decoded.agent_addr, [192, 168, 1, 1]);
713 assert_eq!(decoded.generic_trap, GenericTrap::LinkDown as i32);
714 assert_eq!(decoded.specific_trap, 0);
715 assert_eq!(decoded.time_stamp, 12345678);
716 assert_eq!(decoded.varbinds.len(), 1);
717 }
718
719 #[test]
720 fn test_trap_v1_enterprise_specific() {
721 let trap = TrapV1Pdu::new(
722 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
723 [10, 0, 0, 1],
724 GenericTrap::EnterpriseSpecific,
725 42, 100,
727 vec![],
728 );
729
730 assert!(trap.is_enterprise_specific());
731 assert_eq!(
732 trap.generic_trap_enum(),
733 Some(GenericTrap::EnterpriseSpecific)
734 );
735
736 let mut buf = EncodeBuf::new();
737 trap.encode(&mut buf);
738 let bytes = buf.finish();
739
740 let mut decoder = Decoder::new(bytes);
741 let decoded = TrapV1Pdu::decode(&mut decoder).unwrap();
742
743 assert_eq!(decoded.specific_trap, 42);
744 }
745
746 #[test]
747 fn test_trap_v1_v2_trap_oid_generic_traps() {
748 let test_cases = [
752 (GenericTrap::ColdStart, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1)),
753 (GenericTrap::WarmStart, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 2)),
754 (GenericTrap::LinkDown, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3)),
755 (GenericTrap::LinkUp, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 4)),
756 (
757 GenericTrap::AuthenticationFailure,
758 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 5),
759 ),
760 (
761 GenericTrap::EgpNeighborLoss,
762 oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 6),
763 ),
764 ];
765
766 for (generic_trap, expected_oid) in test_cases {
767 let trap = TrapV1Pdu::new(
768 oid!(1, 3, 6, 1, 4, 1, 9999),
769 [192, 168, 1, 1],
770 generic_trap,
771 0,
772 12345,
773 vec![],
774 );
775 assert_eq!(
776 trap.v2_trap_oid().unwrap(),
777 expected_oid,
778 "Failed for {:?}",
779 generic_trap
780 );
781 }
782 }
783
784 #[test]
785 fn test_trap_v1_v2_trap_oid_enterprise_specific() {
786 let trap = TrapV1Pdu::new(
788 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
789 [192, 168, 1, 1],
790 GenericTrap::EnterpriseSpecific,
791 42,
792 12345,
793 vec![],
794 );
795
796 assert_eq!(
798 trap.v2_trap_oid().unwrap(),
799 oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2, 0, 42)
800 );
801 }
802
803 #[test]
804 fn test_trap_v1_v2_trap_oid_enterprise_specific_zero() {
805 let trap = TrapV1Pdu::new(
807 oid!(1, 3, 6, 1, 4, 1, 1234),
808 [10, 0, 0, 1],
809 GenericTrap::EnterpriseSpecific,
810 0,
811 100,
812 vec![],
813 );
814
815 assert_eq!(
817 trap.v2_trap_oid().unwrap(),
818 oid!(1, 3, 6, 1, 4, 1, 1234, 0, 0)
819 );
820 }
821
822 #[test]
823 fn test_pdu_to_response() {
824 use crate::value::Value;
825 use crate::varbind::VarBind;
826
827 let inform = Pdu {
828 pdu_type: PduType::InformRequest,
829 request_id: 99999,
830 error_status: 0,
831 error_index: 0,
832 varbinds: vec![
833 VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::TimeTicks(12345)),
834 VarBind::new(
835 oid!(1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0),
836 Value::ObjectIdentifier(oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1)),
837 ),
838 ],
839 };
840
841 let response = inform.to_response();
842
843 assert_eq!(response.pdu_type, PduType::Response);
844 assert_eq!(response.request_id, 99999);
845 assert_eq!(response.error_status, 0);
846 assert_eq!(response.error_index, 0);
847 assert_eq!(response.varbinds.len(), 2);
848 }
849
850 #[test]
851 fn test_pdu_is_confirmed() {
852 let get = Pdu::get_request(1, &[oid!(1, 3, 6, 1)]);
853 assert!(get.is_confirmed());
854
855 let inform = Pdu {
856 pdu_type: PduType::InformRequest,
857 request_id: 1,
858 error_status: 0,
859 error_index: 0,
860 varbinds: vec![],
861 };
862 assert!(inform.is_confirmed());
863
864 let trap = Pdu {
865 pdu_type: PduType::TrapV2,
866 request_id: 1,
867 error_status: 0,
868 error_index: 0,
869 varbinds: vec![],
870 };
871 assert!(!trap.is_confirmed());
872 assert!(trap.is_notification());
873 }
874
875 #[test]
876 fn test_decode_rejects_negative_error_index() {
877 let raw = RawPdu::response(1, 0, -1, vec![VarBind::null(oid!(1, 3, 6, 1))]);
879 let encoded = raw.encode();
880
881 let mut decoder = Decoder::new(encoded);
882 let result = Pdu::decode(&mut decoder);
883
884 assert!(result.is_err(), "should reject negative error_index");
885 let err = result.unwrap_err();
886 assert!(
887 matches!(&*err, crate::error::Error::MalformedResponse { .. }),
888 "expected MalformedResponse, got {:?}",
889 err
890 );
891 }
892
893 #[test]
894 fn test_decode_rejects_error_index_beyond_varbinds() {
895 let raw = RawPdu::response(1, 5, 5, vec![VarBind::null(oid!(1, 3, 6, 1))]);
897 let encoded = raw.encode();
898
899 let mut decoder = Decoder::new(encoded);
900 let result = Pdu::decode(&mut decoder);
901
902 assert!(result.is_err(), "should reject error_index beyond varbinds");
903 let err = result.unwrap_err();
904 assert!(
905 matches!(&*err, crate::error::Error::MalformedResponse { .. }),
906 "expected MalformedResponse, got {:?}",
907 err
908 );
909 }
910
911 #[test]
912 fn test_decode_accepts_valid_error_index_zero() {
913 let raw = RawPdu::response(1, 0, 0, vec![VarBind::null(oid!(1, 3, 6, 1))]);
915 let encoded = raw.encode();
916
917 let mut decoder = Decoder::new(encoded);
918 let decoded = Pdu::decode(&mut decoder);
919 assert!(decoded.is_ok(), "error_index=0 should be valid");
920 }
921
922 #[test]
923 fn test_decode_accepts_error_index_within_bounds() {
924 let raw = RawPdu::response(1, 5, 1, vec![VarBind::null(oid!(1, 3, 6, 1))]);
926 let encoded = raw.encode();
927
928 let mut decoder = Decoder::new(encoded);
929 let result = Pdu::decode(&mut decoder);
930 assert!(
931 result.is_ok(),
932 "error_index=1 with 1 varbind should be valid"
933 );
934 }
935
936 #[test]
937 fn test_decode_rejects_negative_non_repeaters() {
938 let raw = RawGetBulkPdu::new(1, -1, 10, vec![VarBind::null(oid!(1, 3, 6, 1))]);
939 let encoded = raw.encode();
940
941 let mut decoder = Decoder::new(encoded);
942 let result = GetBulkPdu::decode(&mut decoder);
943
944 assert!(result.is_err(), "should reject negative non_repeaters");
945 let err = result.unwrap_err();
946 assert!(
947 matches!(&*err, crate::error::Error::MalformedResponse { .. }),
948 "expected MalformedResponse, got {:?}",
949 err
950 );
951 }
952
953 #[test]
954 fn test_decode_rejects_negative_max_repetitions() {
955 let raw = RawGetBulkPdu::new(1, 0, -5, vec![VarBind::null(oid!(1, 3, 6, 1))]);
956 let encoded = raw.encode();
957
958 let mut decoder = Decoder::new(encoded);
959 let result = GetBulkPdu::decode(&mut decoder);
960
961 assert!(result.is_err(), "should reject negative max_repetitions");
962 let err = result.unwrap_err();
963 assert!(
964 matches!(&*err, crate::error::Error::MalformedResponse { .. }),
965 "expected MalformedResponse, got {:?}",
966 err
967 );
968 }
969
970 #[test]
971 fn test_decode_accepts_valid_getbulk_params() {
972 let raw = RawGetBulkPdu::new(1, 0, 10, vec![VarBind::null(oid!(1, 3, 6, 1))]);
973 let encoded = raw.encode();
974
975 let mut decoder = Decoder::new(encoded);
976 let result = GetBulkPdu::decode(&mut decoder);
977 assert!(result.is_ok(), "valid GETBULK params should be accepted");
978
979 let pdu = result.unwrap();
980 assert_eq!(pdu.non_repeaters, 0);
981 assert_eq!(pdu.max_repetitions, 10);
982 }
983
984 #[test]
985 fn test_pdu_decode_getbulk_with_large_max_repetitions() {
986 let raw = RawGetBulkPdu::new(12345, 0, 25, vec![VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1))]);
990 let encoded = raw.encode();
991
992 let mut decoder = Decoder::new(encoded);
993 let result = Pdu::decode(&mut decoder);
994 assert!(
995 result.is_ok(),
996 "Pdu::decode should accept GETBULK with max_repetitions > varbinds.len(), got {:?}",
997 result.err()
998 );
999
1000 let pdu = result.unwrap();
1001 assert_eq!(pdu.pdu_type, PduType::GetBulkRequest);
1002 assert_eq!(pdu.request_id, 12345);
1003 assert_eq!(pdu.error_status, 0);
1005 assert_eq!(pdu.error_index, 25);
1006 assert_eq!(pdu.varbinds.len(), 1);
1007 }
1008}