async_snmp/pdu/
mod.rs

1//! SNMP Protocol Data Units (PDUs).
2//!
3//! PDUs represent the different SNMP operations.
4
5use crate::ber::{Decoder, EncodeBuf, tag};
6use crate::error::{DecodeErrorKind, Error, ErrorStatus, Result};
7use crate::oid::Oid;
8use crate::varbind::{VarBind, decode_varbind_list, encode_varbind_list};
9
10/// PDU type tag.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[repr(u8)]
13pub enum PduType {
14    /// GET request - retrieve specific OID values.
15    GetRequest = 0xA0,
16    /// GET-NEXT request - retrieve the next OID in the MIB tree.
17    GetNextRequest = 0xA1,
18    /// Response to a request from an agent.
19    Response = 0xA2,
20    /// SET request - modify OID values.
21    SetRequest = 0xA3,
22    /// SNMPv1 trap - unsolicited notification from an agent.
23    TrapV1 = 0xA4,
24    /// GET-BULK request - efficient bulk retrieval of table data.
25    GetBulkRequest = 0xA5,
26    /// INFORM request - acknowledged notification.
27    InformRequest = 0xA6,
28    /// SNMPv2c/v3 trap - unsolicited notification from an agent.
29    TrapV2 = 0xA7,
30    /// Report - used in SNMPv3 for engine discovery and error reporting.
31    Report = 0xA8,
32}
33
34impl PduType {
35    /// Create from tag byte.
36    pub fn from_tag(tag: u8) -> Option<Self> {
37        match tag {
38            0xA0 => Some(Self::GetRequest),
39            0xA1 => Some(Self::GetNextRequest),
40            0xA2 => Some(Self::Response),
41            0xA3 => Some(Self::SetRequest),
42            0xA4 => Some(Self::TrapV1),
43            0xA5 => Some(Self::GetBulkRequest),
44            0xA6 => Some(Self::InformRequest),
45            0xA7 => Some(Self::TrapV2),
46            0xA8 => Some(Self::Report),
47            _ => None,
48        }
49    }
50
51    /// Get the tag byte.
52    pub fn tag(self) -> u8 {
53        self as u8
54    }
55}
56
57impl std::fmt::Display for PduType {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        match self {
60            Self::GetRequest => write!(f, "GetRequest"),
61            Self::GetNextRequest => write!(f, "GetNextRequest"),
62            Self::Response => write!(f, "Response"),
63            Self::SetRequest => write!(f, "SetRequest"),
64            Self::TrapV1 => write!(f, "TrapV1"),
65            Self::GetBulkRequest => write!(f, "GetBulkRequest"),
66            Self::InformRequest => write!(f, "InformRequest"),
67            Self::TrapV2 => write!(f, "TrapV2"),
68            Self::Report => write!(f, "Report"),
69        }
70    }
71}
72
73/// Generic PDU structure for request/response operations.
74#[derive(Debug, Clone)]
75pub struct Pdu {
76    /// PDU type
77    pub pdu_type: PduType,
78    /// Request ID for correlating requests and responses
79    pub request_id: i32,
80    /// Error status (0 for requests, error code for responses)
81    pub error_status: i32,
82    /// Error index (1-based index of problematic varbind)
83    pub error_index: i32,
84    /// Variable bindings
85    pub varbinds: Vec<VarBind>,
86}
87
88impl Pdu {
89    /// Create a new GET request PDU.
90    pub fn get_request(request_id: i32, oids: &[Oid]) -> Self {
91        Self {
92            pdu_type: PduType::GetRequest,
93            request_id,
94            error_status: 0,
95            error_index: 0,
96            varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
97        }
98    }
99
100    /// Create a new GETNEXT request PDU.
101    pub fn get_next_request(request_id: i32, oids: &[Oid]) -> Self {
102        Self {
103            pdu_type: PduType::GetNextRequest,
104            request_id,
105            error_status: 0,
106            error_index: 0,
107            varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
108        }
109    }
110
111    /// Create a new SET request PDU.
112    pub fn set_request(request_id: i32, varbinds: Vec<VarBind>) -> Self {
113        Self {
114            pdu_type: PduType::SetRequest,
115            request_id,
116            error_status: 0,
117            error_index: 0,
118            varbinds,
119        }
120    }
121
122    /// Create a GETBULK request PDU.
123    ///
124    /// Note: For GETBULK, error_status holds non_repeaters and error_index holds max_repetitions.
125    pub fn get_bulk(
126        request_id: i32,
127        non_repeaters: i32,
128        max_repetitions: i32,
129        varbinds: Vec<VarBind>,
130    ) -> Self {
131        Self {
132            pdu_type: PduType::GetBulkRequest,
133            request_id,
134            error_status: non_repeaters,
135            error_index: max_repetitions,
136            varbinds,
137        }
138    }
139
140    /// Encode to BER.
141    pub fn encode(&self, buf: &mut EncodeBuf) {
142        buf.push_constructed(self.pdu_type.tag(), |buf| {
143            encode_varbind_list(buf, &self.varbinds);
144            buf.push_integer(self.error_index);
145            buf.push_integer(self.error_status);
146            buf.push_integer(self.request_id);
147        });
148    }
149
150    /// Decode from BER (after tag has been peeked).
151    pub fn decode(decoder: &mut Decoder) -> Result<Self> {
152        let tag = decoder.read_tag()?;
153        let pdu_type = PduType::from_tag(tag)
154            .ok_or_else(|| Error::decode(decoder.offset(), DecodeErrorKind::UnknownPduType(tag)))?;
155
156        let len = decoder.read_length()?;
157        let mut pdu_decoder = decoder.sub_decoder(len)?;
158
159        let request_id = pdu_decoder.read_integer()?;
160        let error_status = pdu_decoder.read_integer()?;
161        let error_index = pdu_decoder.read_integer()?;
162        let varbinds = decode_varbind_list(&mut pdu_decoder)?;
163
164        Ok(Pdu {
165            pdu_type,
166            request_id,
167            error_status,
168            error_index,
169            varbinds,
170        })
171    }
172
173    /// Check if this is an error response.
174    pub fn is_error(&self) -> bool {
175        self.error_status != 0
176    }
177
178    /// Get the error status as an enum.
179    pub fn error_status_enum(&self) -> ErrorStatus {
180        ErrorStatus::from_i32(self.error_status)
181    }
182
183    /// Create a Response PDU from this PDU (for Inform handling).
184    ///
185    /// The response copies the request_id and variable bindings,
186    /// sets error_status and error_index to 0, and changes the PDU type to Response.
187    pub fn to_response(&self) -> Self {
188        Self {
189            pdu_type: PduType::Response,
190            request_id: self.request_id,
191            error_status: 0,
192            error_index: 0,
193            varbinds: self.varbinds.clone(),
194        }
195    }
196
197    /// Create a Response PDU with specific error status.
198    pub fn to_error_response(&self, error_status: ErrorStatus, error_index: i32) -> Self {
199        Self {
200            pdu_type: PduType::Response,
201            request_id: self.request_id,
202            error_status: error_status.as_i32(),
203            error_index,
204            varbinds: self.varbinds.clone(),
205        }
206    }
207
208    /// Check if this is a notification PDU (Trap or Inform).
209    pub fn is_notification(&self) -> bool {
210        matches!(
211            self.pdu_type,
212            PduType::TrapV1 | PduType::TrapV2 | PduType::InformRequest
213        )
214    }
215
216    /// Check if this is a confirmed-class PDU (requires response).
217    pub fn is_confirmed(&self) -> bool {
218        matches!(
219            self.pdu_type,
220            PduType::GetRequest
221                | PduType::GetNextRequest
222                | PduType::GetBulkRequest
223                | PduType::SetRequest
224                | PduType::InformRequest
225        )
226    }
227}
228
229/// SNMPv1 generic trap types (RFC 1157 Section 4.1.6).
230#[derive(Debug, Clone, Copy, PartialEq, Eq)]
231#[repr(i32)]
232pub enum GenericTrap {
233    /// coldStart(0) - agent is reinitializing, config may change
234    ColdStart = 0,
235    /// warmStart(1) - agent is reinitializing, config unchanged
236    WarmStart = 1,
237    /// linkDown(2) - communication link failure
238    LinkDown = 2,
239    /// linkUp(3) - communication link came up
240    LinkUp = 3,
241    /// authenticationFailure(4) - improperly authenticated message received
242    AuthenticationFailure = 4,
243    /// egpNeighborLoss(5) - EGP peer marked down
244    EgpNeighborLoss = 5,
245    /// enterpriseSpecific(6) - vendor-specific trap, see specific_trap field
246    EnterpriseSpecific = 6,
247}
248
249impl GenericTrap {
250    /// Create from integer value.
251    pub fn from_i32(v: i32) -> Option<Self> {
252        match v {
253            0 => Some(Self::ColdStart),
254            1 => Some(Self::WarmStart),
255            2 => Some(Self::LinkDown),
256            3 => Some(Self::LinkUp),
257            4 => Some(Self::AuthenticationFailure),
258            5 => Some(Self::EgpNeighborLoss),
259            6 => Some(Self::EnterpriseSpecific),
260            _ => None,
261        }
262    }
263
264    /// Get the integer value.
265    pub fn as_i32(self) -> i32 {
266        self as i32
267    }
268}
269
270/// SNMPv1 Trap PDU (RFC 1157 Section 4.1.6).
271///
272/// This PDU type has a completely different structure from other PDUs.
273/// It is only used in SNMPv1 and is replaced by SNMPv2-Trap in v2c/v3.
274#[derive(Debug, Clone)]
275pub struct TrapV1Pdu {
276    /// Enterprise OID (sysObjectID of the entity generating the trap)
277    pub enterprise: Oid,
278    /// Agent address (IP address of the agent generating the trap)
279    pub agent_addr: [u8; 4],
280    /// Generic trap type
281    pub generic_trap: i32,
282    /// Specific trap code (meaningful when generic_trap is enterpriseSpecific)
283    pub specific_trap: i32,
284    /// Time since the network entity was last (re)initialized (in hundredths of seconds)
285    pub time_stamp: u32,
286    /// Variable bindings containing "interesting" information
287    pub varbinds: Vec<VarBind>,
288}
289
290impl TrapV1Pdu {
291    /// Create a new SNMPv1 Trap PDU.
292    pub fn new(
293        enterprise: Oid,
294        agent_addr: [u8; 4],
295        generic_trap: GenericTrap,
296        specific_trap: i32,
297        time_stamp: u32,
298        varbinds: Vec<VarBind>,
299    ) -> Self {
300        Self {
301            enterprise,
302            agent_addr,
303            generic_trap: generic_trap.as_i32(),
304            specific_trap,
305            time_stamp,
306            varbinds,
307        }
308    }
309
310    /// Get the generic trap type as an enum.
311    pub fn generic_trap_enum(&self) -> Option<GenericTrap> {
312        GenericTrap::from_i32(self.generic_trap)
313    }
314
315    /// Check if this is an enterprise-specific trap.
316    pub fn is_enterprise_specific(&self) -> bool {
317        self.generic_trap == GenericTrap::EnterpriseSpecific as i32
318    }
319
320    /// Convert to SNMPv2 trap OID (RFC 3584 Section 3).
321    ///
322    /// RFC 3584 defines how to translate SNMPv1 trap information to SNMPv2
323    /// snmpTrapOID.0 format:
324    ///
325    /// - For generic traps 0-5 (coldStart through egpNeighborLoss):
326    ///   The trap OID is `snmpTraps.{generic_trap + 1}` (1.3.6.1.6.3.1.1.5.{1-6})
327    ///
328    /// - For enterprise-specific traps (generic_trap = 6):
329    ///   The trap OID is `enterprise.0.specific_trap`
330    ///
331    /// # Example
332    ///
333    /// ```rust
334    /// use async_snmp::pdu::{TrapV1Pdu, GenericTrap};
335    /// use async_snmp::oid;
336    ///
337    /// // Generic trap (linkDown = 2) -> snmpTraps.3
338    /// let trap = TrapV1Pdu::new(
339    ///     oid!(1, 3, 6, 1, 4, 1, 9999),
340    ///     [192, 168, 1, 1],
341    ///     GenericTrap::LinkDown,
342    ///     0,
343    ///     12345,
344    ///     vec![],
345    /// );
346    /// assert_eq!(trap.v2_trap_oid(), oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3));
347    ///
348    /// // Enterprise-specific trap -> enterprise.0.specific_trap
349    /// let trap = TrapV1Pdu::new(
350    ///     oid!(1, 3, 6, 1, 4, 1, 9999),
351    ///     [192, 168, 1, 1],
352    ///     GenericTrap::EnterpriseSpecific,
353    ///     42,
354    ///     12345,
355    ///     vec![],
356    /// );
357    /// assert_eq!(trap.v2_trap_oid(), oid!(1, 3, 6, 1, 4, 1, 9999, 0, 42));
358    /// ```
359    pub fn v2_trap_oid(&self) -> Oid {
360        if self.is_enterprise_specific() {
361            // Enterprise-specific: enterprise.0.specific_trap
362            let mut arcs: Vec<u32> = self.enterprise.arcs().to_vec();
363            arcs.push(0);
364            arcs.push(self.specific_trap as u32);
365            Oid::new(arcs)
366        } else {
367            // Generic trap: snmpTraps.{generic_trap + 1}
368            // snmpTraps = 1.3.6.1.6.3.1.1.5
369            let trap_num = self.generic_trap + 1;
370            crate::oid!(1, 3, 6, 1, 6, 3, 1, 1, 5).child(trap_num as u32)
371        }
372    }
373
374    /// Encode to BER.
375    pub fn encode(&self, buf: &mut EncodeBuf) {
376        buf.push_constructed(tag::pdu::TRAP_V1, |buf| {
377            encode_varbind_list(buf, &self.varbinds);
378            buf.push_unsigned32(tag::application::TIMETICKS, self.time_stamp);
379            buf.push_integer(self.specific_trap);
380            buf.push_integer(self.generic_trap);
381            // NetworkAddress is APPLICATION 0 IMPLICIT IpAddress
382            // IpAddress is APPLICATION 0 IMPLICIT OCTET STRING (SIZE (4))
383            buf.push_bytes(&self.agent_addr);
384            buf.push_length(4);
385            buf.push_tag(tag::application::IP_ADDRESS);
386            buf.push_oid(&self.enterprise);
387        });
388    }
389
390    /// Decode from BER (after tag has been peeked).
391    pub fn decode(decoder: &mut Decoder) -> Result<Self> {
392        let mut pdu = decoder.read_constructed(tag::pdu::TRAP_V1)?;
393
394        // enterprise OBJECT IDENTIFIER
395        let enterprise = pdu.read_oid()?;
396
397        // agent-addr NetworkAddress (IpAddress)
398        let agent_tag = pdu.read_tag()?;
399        if agent_tag != tag::application::IP_ADDRESS {
400            return Err(Error::decode(
401                pdu.offset(),
402                DecodeErrorKind::UnexpectedTag {
403                    expected: 0x40,
404                    actual: agent_tag,
405                },
406            ));
407        }
408        let agent_len = pdu.read_length()?;
409        if agent_len != 4 {
410            return Err(Error::decode(
411                pdu.offset(),
412                DecodeErrorKind::InvalidIpAddressLength { length: agent_len },
413            ));
414        }
415        let agent_bytes = pdu.read_bytes(4)?;
416        let agent_addr = [
417            agent_bytes[0],
418            agent_bytes[1],
419            agent_bytes[2],
420            agent_bytes[3],
421        ];
422
423        // generic-trap INTEGER
424        let generic_trap = pdu.read_integer()?;
425
426        // specific-trap INTEGER
427        let specific_trap = pdu.read_integer()?;
428
429        // time-stamp TimeTicks
430        let ts_tag = pdu.read_tag()?;
431        if ts_tag != tag::application::TIMETICKS {
432            return Err(Error::decode(
433                pdu.offset(),
434                DecodeErrorKind::UnexpectedTag {
435                    expected: 0x43,
436                    actual: ts_tag,
437                },
438            ));
439        }
440        let ts_len = pdu.read_length()?;
441        let time_stamp = pdu.read_unsigned32_value(ts_len)?;
442
443        // variable-bindings
444        let varbinds = decode_varbind_list(&mut pdu)?;
445
446        Ok(TrapV1Pdu {
447            enterprise,
448            agent_addr,
449            generic_trap,
450            specific_trap,
451            time_stamp,
452            varbinds,
453        })
454    }
455}
456
457/// GETBULK request PDU.
458#[derive(Debug, Clone)]
459pub struct GetBulkPdu {
460    /// Request ID
461    pub request_id: i32,
462    /// Number of non-repeating OIDs
463    pub non_repeaters: i32,
464    /// Maximum repetitions for repeating OIDs
465    pub max_repetitions: i32,
466    /// Variable bindings
467    pub varbinds: Vec<VarBind>,
468}
469
470impl GetBulkPdu {
471    /// Create a new GETBULK request.
472    pub fn new(request_id: i32, non_repeaters: i32, max_repetitions: i32, oids: &[Oid]) -> Self {
473        Self {
474            request_id,
475            non_repeaters,
476            max_repetitions,
477            varbinds: oids.iter().map(|oid| VarBind::null(oid.clone())).collect(),
478        }
479    }
480
481    /// Encode to BER.
482    pub fn encode(&self, buf: &mut EncodeBuf) {
483        buf.push_constructed(tag::pdu::GET_BULK_REQUEST, |buf| {
484            encode_varbind_list(buf, &self.varbinds);
485            buf.push_integer(self.max_repetitions);
486            buf.push_integer(self.non_repeaters);
487            buf.push_integer(self.request_id);
488        });
489    }
490
491    /// Decode from BER.
492    pub fn decode(decoder: &mut Decoder) -> Result<Self> {
493        let mut pdu = decoder.read_constructed(tag::pdu::GET_BULK_REQUEST)?;
494
495        let request_id = pdu.read_integer()?;
496        let non_repeaters = pdu.read_integer()?;
497        let max_repetitions = pdu.read_integer()?;
498        let varbinds = decode_varbind_list(&mut pdu)?;
499
500        Ok(GetBulkPdu {
501            request_id,
502            non_repeaters,
503            max_repetitions,
504            varbinds,
505        })
506    }
507}
508
509#[cfg(test)]
510mod tests {
511    use super::*;
512    use crate::oid;
513
514    #[test]
515    fn test_get_request_roundtrip() {
516        let pdu = Pdu::get_request(12345, &[oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)]);
517
518        let mut buf = EncodeBuf::new();
519        pdu.encode(&mut buf);
520        let bytes = buf.finish();
521
522        let mut decoder = Decoder::new(bytes);
523        let decoded = Pdu::decode(&mut decoder).unwrap();
524
525        assert_eq!(decoded.pdu_type, PduType::GetRequest);
526        assert_eq!(decoded.request_id, 12345);
527        assert_eq!(decoded.varbinds.len(), 1);
528    }
529
530    #[test]
531    fn test_getbulk_roundtrip() {
532        let pdu = GetBulkPdu::new(12345, 0, 10, &[oid!(1, 3, 6, 1, 2, 1, 1)]);
533
534        let mut buf = EncodeBuf::new();
535        pdu.encode(&mut buf);
536        let bytes = buf.finish();
537
538        let mut decoder = Decoder::new(bytes);
539        let decoded = GetBulkPdu::decode(&mut decoder).unwrap();
540
541        assert_eq!(decoded.request_id, 12345);
542        assert_eq!(decoded.non_repeaters, 0);
543        assert_eq!(decoded.max_repetitions, 10);
544    }
545
546    #[test]
547    fn test_trap_v1_roundtrip() {
548        use crate::value::Value;
549        use crate::varbind::VarBind;
550
551        let trap = TrapV1Pdu::new(
552            oid!(1, 3, 6, 1, 4, 1, 9999), // enterprise OID
553            [192, 168, 1, 1],             // agent address
554            GenericTrap::LinkDown,
555            0,
556            12345678, // time stamp
557            vec![VarBind::new(
558                oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1),
559                Value::Integer(1),
560            )],
561        );
562
563        let mut buf = EncodeBuf::new();
564        trap.encode(&mut buf);
565        let bytes = buf.finish();
566
567        let mut decoder = Decoder::new(bytes);
568        let decoded = TrapV1Pdu::decode(&mut decoder).unwrap();
569
570        assert_eq!(decoded.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999));
571        assert_eq!(decoded.agent_addr, [192, 168, 1, 1]);
572        assert_eq!(decoded.generic_trap, GenericTrap::LinkDown as i32);
573        assert_eq!(decoded.specific_trap, 0);
574        assert_eq!(decoded.time_stamp, 12345678);
575        assert_eq!(decoded.varbinds.len(), 1);
576    }
577
578    #[test]
579    fn test_trap_v1_enterprise_specific() {
580        let trap = TrapV1Pdu::new(
581            oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
582            [10, 0, 0, 1],
583            GenericTrap::EnterpriseSpecific,
584            42, // specific trap number
585            100,
586            vec![],
587        );
588
589        assert!(trap.is_enterprise_specific());
590        assert_eq!(
591            trap.generic_trap_enum(),
592            Some(GenericTrap::EnterpriseSpecific)
593        );
594
595        let mut buf = EncodeBuf::new();
596        trap.encode(&mut buf);
597        let bytes = buf.finish();
598
599        let mut decoder = Decoder::new(bytes);
600        let decoded = TrapV1Pdu::decode(&mut decoder).unwrap();
601
602        assert_eq!(decoded.specific_trap, 42);
603    }
604
605    #[test]
606    fn test_trap_v1_v2_trap_oid_generic_traps() {
607        // Test all generic trap types translate to correct snmpTraps.X OIDs
608        // RFC 3584 Section 3: snmpTraps.{generic_trap + 1}
609
610        let test_cases = [
611            (GenericTrap::ColdStart, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1)),
612            (GenericTrap::WarmStart, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 2)),
613            (GenericTrap::LinkDown, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 3)),
614            (GenericTrap::LinkUp, oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 4)),
615            (
616                GenericTrap::AuthenticationFailure,
617                oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 5),
618            ),
619            (
620                GenericTrap::EgpNeighborLoss,
621                oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 6),
622            ),
623        ];
624
625        for (generic_trap, expected_oid) in test_cases {
626            let trap = TrapV1Pdu::new(
627                oid!(1, 3, 6, 1, 4, 1, 9999),
628                [192, 168, 1, 1],
629                generic_trap,
630                0,
631                12345,
632                vec![],
633            );
634            assert_eq!(
635                trap.v2_trap_oid(),
636                expected_oid,
637                "Failed for {:?}",
638                generic_trap
639            );
640        }
641    }
642
643    #[test]
644    fn test_trap_v1_v2_trap_oid_enterprise_specific() {
645        // RFC 3584 Section 3: enterprise.0.specific_trap
646        let trap = TrapV1Pdu::new(
647            oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2),
648            [192, 168, 1, 1],
649            GenericTrap::EnterpriseSpecific,
650            42,
651            12345,
652            vec![],
653        );
654
655        // Expected: 1.3.6.1.4.1.9999.1.2.0.42
656        assert_eq!(
657            trap.v2_trap_oid(),
658            oid!(1, 3, 6, 1, 4, 1, 9999, 1, 2, 0, 42)
659        );
660    }
661
662    #[test]
663    fn test_trap_v1_v2_trap_oid_enterprise_specific_zero() {
664        // Edge case: specific_trap = 0
665        let trap = TrapV1Pdu::new(
666            oid!(1, 3, 6, 1, 4, 1, 1234),
667            [10, 0, 0, 1],
668            GenericTrap::EnterpriseSpecific,
669            0,
670            100,
671            vec![],
672        );
673
674        // Expected: 1.3.6.1.4.1.1234.0.0
675        assert_eq!(trap.v2_trap_oid(), oid!(1, 3, 6, 1, 4, 1, 1234, 0, 0));
676    }
677
678    #[test]
679    fn test_pdu_to_response() {
680        use crate::value::Value;
681        use crate::varbind::VarBind;
682
683        let inform = Pdu {
684            pdu_type: PduType::InformRequest,
685            request_id: 99999,
686            error_status: 0,
687            error_index: 0,
688            varbinds: vec![
689                VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::TimeTicks(12345)),
690                VarBind::new(
691                    oid!(1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0),
692                    Value::ObjectIdentifier(oid!(1, 3, 6, 1, 6, 3, 1, 1, 5, 1)),
693                ),
694            ],
695        };
696
697        let response = inform.to_response();
698
699        assert_eq!(response.pdu_type, PduType::Response);
700        assert_eq!(response.request_id, 99999);
701        assert_eq!(response.error_status, 0);
702        assert_eq!(response.error_index, 0);
703        assert_eq!(response.varbinds.len(), 2);
704    }
705
706    #[test]
707    fn test_pdu_is_confirmed() {
708        let get = Pdu::get_request(1, &[oid!(1, 3, 6, 1)]);
709        assert!(get.is_confirmed());
710
711        let inform = Pdu {
712            pdu_type: PduType::InformRequest,
713            request_id: 1,
714            error_status: 0,
715            error_index: 0,
716            varbinds: vec![],
717        };
718        assert!(inform.is_confirmed());
719
720        let trap = Pdu {
721            pdu_type: PduType::TrapV2,
722            request_id: 1,
723            error_status: 0,
724            error_index: 0,
725            varbinds: vec![],
726        };
727        assert!(!trap.is_confirmed());
728        assert!(trap.is_notification());
729    }
730}