Skip to main content

async_snmp/
varbind.rs

1//! Variable binding (`VarBind`) type.
2//!
3//! A `VarBind` pairs an OID with a value.
4
5use crate::ber::{Decoder, EncodeBuf};
6use crate::error::Result;
7use crate::oid::Oid;
8use crate::value::Value;
9
10/// Variable binding - an OID-value pair.
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub struct VarBind {
13    /// The object identifier.
14    pub oid: Oid,
15    /// The value.
16    pub value: Value,
17}
18
19impl VarBind {
20    /// Create a new `VarBind`.
21    pub fn new(oid: Oid, value: Value) -> Self {
22        Self { oid, value }
23    }
24
25    /// Create a `VarBind` with a NULL value (for GET requests).
26    #[must_use]
27    pub fn null(oid: Oid) -> Self {
28        Self {
29            oid,
30            value: Value::Null,
31        }
32    }
33
34    /// Encode to BER.
35    pub fn encode(&self, buf: &mut EncodeBuf) {
36        buf.push_sequence(|buf| {
37            self.value.encode(buf);
38            buf.push_oid(&self.oid);
39        });
40    }
41
42    /// Returns the exact encoded size of this `VarBind` in bytes.
43    ///
44    /// Computes the size arithmetically without allocating.
45    /// Useful for response size estimation in GETBULK processing.
46    pub fn encoded_size(&self) -> usize {
47        use crate::ber::length_encoded_len;
48
49        // VarBind is SEQUENCE { oid, value }
50        let oid_len = self.oid.ber_encoded_size();
51        let value_len = self.value.ber_encoded_size();
52        let content_len = oid_len + value_len;
53
54        // SEQUENCE tag (1) + length encoding + content
55        1 + length_encoded_len(content_len) + content_len
56    }
57
58    /// Decode from BER.
59    pub fn decode(decoder: &mut Decoder) -> Result<Self> {
60        let mut seq = decoder.read_sequence()?;
61        let oid = seq.read_oid()?;
62        let value = Value::decode(&mut seq)?;
63        Ok(VarBind { oid, value })
64    }
65}
66
67impl std::fmt::Display for VarBind {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        write!(f, "{} = {}", self.oid, self.value)
70    }
71}
72
73/// Encodes a list of `VarBind`s to BER format.
74///
75/// Writes the `VarBind`s as a SEQUENCE of SEQUENCE elements, where each inner
76/// SEQUENCE contains an OID and its associated value.
77pub fn encode_varbind_list(buf: &mut EncodeBuf, varbinds: &[VarBind]) {
78    buf.push_sequence(|buf| {
79        // Encode in reverse order since we're using reverse buffer
80        for vb in varbinds.iter().rev() {
81            vb.encode(buf);
82        }
83    });
84}
85
86/// Decodes a BER-encoded `VarBind` list into a vector of `VarBind`s.
87///
88/// Expects a SEQUENCE containing zero or more `VarBind` SEQUENCE elements.
89pub fn decode_varbind_list(decoder: &mut Decoder) -> Result<Vec<VarBind>> {
90    let mut seq = decoder.read_sequence()?;
91
92    // Estimate capacity: typical VarBind is 20-50 bytes, use 16 as conservative divisor
93    // to minimize reallocations while not over-allocating
94    let estimated_capacity = (seq.remaining() / 16).max(1);
95    let mut varbinds = Vec::with_capacity(estimated_capacity);
96
97    while !seq.is_empty() {
98        varbinds.push(VarBind::decode(&mut seq)?);
99    }
100
101    Ok(varbinds)
102}
103
104/// Encodes OIDs with NULL values for GET requests.
105///
106/// Creates a `VarBind` list where each OID is paired with a NULL value,
107/// as required by SNMP GET, GETNEXT, and GETBULK request PDUs.
108pub fn encode_null_varbinds(buf: &mut EncodeBuf, oids: &[Oid]) {
109    buf.push_sequence(|buf| {
110        for oid in oids.iter().rev() {
111            buf.push_sequence(|buf| {
112                buf.push_null();
113                buf.push_oid(oid);
114            });
115        }
116    });
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use crate::oid;
123    use bytes::Bytes;
124
125    #[test]
126    fn test_varbind_roundtrip() {
127        let vb = VarBind::new(oid!(1, 3, 6, 1), Value::Integer(42));
128
129        let mut buf = EncodeBuf::new();
130        vb.encode(&mut buf);
131        let bytes = buf.finish();
132
133        let mut decoder = Decoder::new(bytes);
134        let decoded = VarBind::decode(&mut decoder).unwrap();
135
136        assert_eq!(vb, decoded);
137    }
138
139    #[test]
140    fn test_varbind_list_roundtrip() {
141        let varbinds = vec![
142            VarBind::new(oid!(1, 3, 6, 1), Value::Integer(1)),
143            VarBind::new(oid!(1, 3, 6, 2), Value::Integer(2)),
144        ];
145
146        let mut buf = EncodeBuf::new();
147        encode_varbind_list(&mut buf, &varbinds);
148        let bytes = buf.finish();
149
150        let mut decoder = Decoder::new(bytes);
151        let decoded = decode_varbind_list(&mut decoder).unwrap();
152
153        assert_eq!(varbinds, decoded);
154    }
155
156    // ========================================================================
157    // Exception Value VarBind Tests
158    // ========================================================================
159
160    #[test]
161    fn test_varbind_no_such_object() {
162        let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::NoSuchObject);
163
164        let mut buf = EncodeBuf::new();
165        vb.encode(&mut buf);
166        let bytes = buf.finish();
167
168        let mut decoder = Decoder::new(bytes);
169        let decoded = VarBind::decode(&mut decoder).unwrap();
170
171        assert_eq!(vb, decoded);
172        assert!(decoded.value.is_exception());
173    }
174
175    #[test]
176    fn test_varbind_no_such_instance() {
177        let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::NoSuchInstance);
178
179        let mut buf = EncodeBuf::new();
180        vb.encode(&mut buf);
181        let bytes = buf.finish();
182
183        let mut decoder = Decoder::new(bytes);
184        let decoded = VarBind::decode(&mut decoder).unwrap();
185
186        assert_eq!(vb, decoded);
187        assert!(decoded.value.is_exception());
188    }
189
190    #[test]
191    fn test_varbind_end_of_mib_view() {
192        let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::EndOfMibView);
193
194        let mut buf = EncodeBuf::new();
195        vb.encode(&mut buf);
196        let bytes = buf.finish();
197
198        let mut decoder = Decoder::new(bytes);
199        let decoded = VarBind::decode(&mut decoder).unwrap();
200
201        assert_eq!(vb, decoded);
202        assert!(decoded.value.is_exception());
203    }
204
205    #[test]
206    fn test_varbind_zero_length_oid_end_of_mib_view() {
207        // Some devices send endOfMibView with a zero-length OID instead of echoing
208        // back the requested OID (RFC 3416 violation). net-snmp accepts this by
209        // treating 06 00 as OID 0.0; we accept it by returning an empty OID.
210        // The bytes are: 30 04 06 00 82 00
211        //   30 04  - SEQUENCE length 4
212        //   06 00  - OID tag, zero-length content (malformed)
213        //   82 00  - endOfMibView, length 0
214        let bytes = bytes::Bytes::from_static(&[0x30, 0x04, 0x06, 0x00, 0x82, 0x00]);
215        let mut decoder = Decoder::new(bytes);
216        let vb = VarBind::decode(&mut decoder).unwrap();
217        assert!(vb.oid.is_empty());
218        assert_eq!(vb.value, Value::EndOfMibView);
219    }
220
221    #[test]
222    fn test_varbind_octetstring_length_exceeds_sequence() {
223        // MikroTik firmware bug: OctetString value declares a length 1 byte
224        // larger than what fits in the enclosing varbind SEQUENCE. We clamp to
225        // the available bytes rather than rejecting the varbind.
226        //
227        // Packet: 30 0e 06 08 2b 06 01 02 01 01 01 00 04 03 61 62
228        //   30 0e  - varbind SEQUENCE, length 14
229        //   06 08  - OID tag, length 8
230        //   2b 06 01 02 01 01 01 00  - OID content (1.3.6.1.2.1.1.1.0)
231        //   04 03  - OctetString tag, declared length 3 (WRONG - only 2 bytes remain)
232        //   61 62  - 2 bytes of actual data ("ab")
233        let bytes = bytes::Bytes::from_static(&[
234            0x30, 0x0e, // SEQUENCE length 14
235            0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x01, 0x00, // OID
236            0x04, 0x03, 0x61, 0x62, // OctetString: declares 3, has 2
237        ]);
238        let mut decoder = Decoder::new(bytes);
239        let vb = VarBind::decode(&mut decoder).unwrap();
240        assert_eq!(vb.oid, crate::oid!(1, 3, 6, 1, 2, 1, 1, 1, 0));
241        assert_eq!(
242            vb.value,
243            Value::OctetString(bytes::Bytes::from_static(b"ab"))
244        );
245    }
246
247    // ========================================================================
248    // VarBind List Edge Cases
249    // ========================================================================
250
251    #[test]
252    fn test_varbind_list_empty() {
253        let varbinds: Vec<VarBind> = vec![];
254
255        let mut buf = EncodeBuf::new();
256        encode_varbind_list(&mut buf, &varbinds);
257        let bytes = buf.finish();
258
259        let mut decoder = Decoder::new(bytes);
260        let decoded = decode_varbind_list(&mut decoder).unwrap();
261
262        assert!(decoded.is_empty());
263    }
264
265    #[test]
266    fn test_varbind_list_single() {
267        let varbinds = vec![VarBind::new(oid!(1, 3, 6, 1), Value::Integer(42))];
268
269        let mut buf = EncodeBuf::new();
270        encode_varbind_list(&mut buf, &varbinds);
271        let bytes = buf.finish();
272
273        let mut decoder = Decoder::new(bytes);
274        let decoded = decode_varbind_list(&mut decoder).unwrap();
275
276        assert_eq!(varbinds, decoded);
277    }
278
279    #[test]
280    fn test_varbind_list_with_exceptions() {
281        let varbinds = vec![
282            VarBind::new(
283                oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
284                Value::OctetString(Bytes::from_static(b"Linux router")),
285            ),
286            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 99, 0), Value::NoSuchObject),
287            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::TimeTicks(123_456)),
288            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 100, 0), Value::NoSuchInstance),
289        ];
290
291        let mut buf = EncodeBuf::new();
292        encode_varbind_list(&mut buf, &varbinds);
293        let bytes = buf.finish();
294
295        let mut decoder = Decoder::new(bytes);
296        let decoded = decode_varbind_list(&mut decoder).unwrap();
297
298        assert_eq!(varbinds, decoded);
299        assert!(!decoded[0].value.is_exception());
300        assert!(decoded[1].value.is_exception());
301        assert!(!decoded[2].value.is_exception());
302        assert!(decoded[3].value.is_exception());
303    }
304
305    #[test]
306    fn test_varbind_list_all_exceptions() {
307        let varbinds = vec![
308            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::NoSuchObject),
309            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 2, 0), Value::NoSuchInstance),
310            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::EndOfMibView),
311        ];
312
313        let mut buf = EncodeBuf::new();
314        encode_varbind_list(&mut buf, &varbinds);
315        let bytes = buf.finish();
316
317        let mut decoder = Decoder::new(bytes);
318        let decoded = decode_varbind_list(&mut decoder).unwrap();
319
320        assert_eq!(varbinds, decoded);
321        assert!(decoded.iter().all(|vb| vb.value.is_exception()));
322    }
323
324    #[test]
325    fn test_varbind_list_mixed_value_types() {
326        let varbinds = vec![
327            VarBind::new(
328                oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
329                Value::OctetString(Bytes::from_static(b"test")),
330            ),
331            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 2, 0), Value::Integer(42)),
332            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::Counter32(1000)),
333            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 4, 0), Value::Gauge32(500)),
334            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 5, 0), Value::TimeTicks(99999)),
335            VarBind::new(
336                oid!(1, 3, 6, 1, 2, 1, 1, 6, 0),
337                Value::IpAddress([192, 168, 1, 1]),
338            ),
339            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 7, 0), Value::Counter64(u64::MAX)),
340            VarBind::new(
341                oid!(1, 3, 6, 1, 2, 1, 1, 8, 0),
342                Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4)),
343            ),
344            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 9, 0), Value::Null),
345        ];
346
347        let mut buf = EncodeBuf::new();
348        encode_varbind_list(&mut buf, &varbinds);
349        let bytes = buf.finish();
350
351        let mut decoder = Decoder::new(bytes);
352        let decoded = decode_varbind_list(&mut decoder).unwrap();
353
354        assert_eq!(varbinds, decoded);
355    }
356
357    #[test]
358    fn test_null_varbinds_encoding() {
359        let oids = vec![
360            oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
361            oid!(1, 3, 6, 1, 2, 1, 1, 3, 0),
362            oid!(1, 3, 6, 1, 2, 1, 1, 5, 0),
363        ];
364
365        let mut buf = EncodeBuf::new();
366        encode_null_varbinds(&mut buf, &oids);
367        let bytes = buf.finish();
368
369        let mut decoder = Decoder::new(bytes);
370        let decoded = decode_varbind_list(&mut decoder).unwrap();
371
372        assert_eq!(decoded.len(), 3);
373        for (i, vb) in decoded.iter().enumerate() {
374            assert_eq!(vb.oid, oids[i]);
375            assert_eq!(vb.value, Value::Null);
376        }
377    }
378
379    #[test]
380    fn test_null_varbinds_empty() {
381        let oids: Vec<Oid> = vec![];
382
383        let mut buf = EncodeBuf::new();
384        encode_null_varbinds(&mut buf, &oids);
385        let bytes = buf.finish();
386
387        let mut decoder = Decoder::new(bytes);
388        let decoded = decode_varbind_list(&mut decoder).unwrap();
389
390        assert!(decoded.is_empty());
391    }
392
393    // ========================================================================
394    // VarBind Display Tests
395    // ========================================================================
396
397    #[test]
398    fn test_varbind_display() {
399        let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::Integer(42));
400        let display = format!("{vb}");
401        assert!(display.contains("1.3.6.1.2.1.1.1.0"));
402        assert!(display.contains("42"));
403    }
404
405    #[test]
406    fn test_varbind_display_exception() {
407        let vb = VarBind::new(oid!(1, 3, 6, 1), Value::NoSuchObject);
408        let display = format!("{vb}");
409        assert!(display.contains("noSuchObject"));
410    }
411
412    // ========================================================================
413    // VarBind::null() Constructor Test
414    // ========================================================================
415
416    #[test]
417    fn test_varbind_null_constructor() {
418        let vb = VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0));
419        assert_eq!(vb.oid, oid!(1, 3, 6, 1, 2, 1, 1, 1, 0));
420        assert_eq!(vb.value, Value::Null);
421    }
422
423    // ========================================================================
424    // VarBind::encoded_size() Tests
425    // ========================================================================
426
427    /// Helper to verify `encoded_size()` matches actual encoding length
428    fn verify_encoded_size(vb: &VarBind) {
429        let mut buf = EncodeBuf::new();
430        vb.encode(&mut buf);
431        let actual = buf.len();
432        let computed = vb.encoded_size();
433        assert_eq!(
434            computed, actual,
435            "encoded_size mismatch for {vb:?}: computed={computed}, actual={actual}"
436        );
437    }
438
439    #[test]
440    fn test_encoded_size_null() {
441        verify_encoded_size(&VarBind::null(oid!(1, 3, 6, 1)));
442        verify_encoded_size(&VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)));
443    }
444
445    #[test]
446    fn test_encoded_size_integer() {
447        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Integer(0)));
448        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Integer(127)));
449        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Integer(128)));
450        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Integer(-1)));
451        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Integer(i32::MAX)));
452        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Integer(i32::MIN)));
453    }
454
455    #[test]
456    fn test_encoded_size_octet_string() {
457        verify_encoded_size(&VarBind::new(
458            oid!(1, 3, 6, 1),
459            Value::OctetString(Bytes::new()),
460        ));
461        verify_encoded_size(&VarBind::new(
462            oid!(1, 3, 6, 1),
463            Value::OctetString(Bytes::from_static(b"hello world")),
464        ));
465        // Large string
466        verify_encoded_size(&VarBind::new(
467            oid!(1, 3, 6, 1),
468            Value::OctetString(Bytes::from(vec![0u8; 200])),
469        ));
470    }
471
472    #[test]
473    fn test_encoded_size_counters() {
474        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Counter32(0)));
475        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Counter32(u32::MAX)));
476        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Gauge32(12345)));
477        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::TimeTicks(99999)));
478        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Counter64(0)));
479        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Counter64(u64::MAX)));
480    }
481
482    #[test]
483    fn test_encoded_size_oid_value() {
484        verify_encoded_size(&VarBind::new(
485            oid!(1, 3, 6, 1, 2, 1, 1, 2, 0),
486            Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4, 1, 9999)),
487        ));
488    }
489
490    #[test]
491    fn test_encoded_size_exceptions() {
492        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::NoSuchObject));
493        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::NoSuchInstance));
494        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::EndOfMibView));
495    }
496
497    #[test]
498    fn test_encoded_size_ip_address() {
499        verify_encoded_size(&VarBind::new(
500            oid!(1, 3, 6, 1),
501            Value::IpAddress([192, 168, 1, 1]),
502        ));
503    }
504
505    mod proptests {
506        use super::*;
507        use crate::oid::Oid;
508        use proptest::prelude::*;
509
510        fn arb_oid() -> impl Strategy<Value = Oid> {
511            // Generate valid OIDs: first arc 0-2, second arc 0-39 (for arc1 < 2) or 0-999
512            (0u32..3, 0u32..40, prop::collection::vec(0u32..10000, 0..8)).prop_map(
513                |(arc1, arc2, rest)| {
514                    let mut arcs = vec![arc1, arc2];
515                    arcs.extend(rest);
516                    Oid::from_slice(&arcs)
517                },
518            )
519        }
520
521        fn arb_value() -> impl Strategy<Value = Value> {
522            prop_oneof![
523                any::<i32>().prop_map(Value::Integer),
524                prop::collection::vec(any::<u8>(), 0..256)
525                    .prop_map(|v| Value::OctetString(Bytes::from(v))),
526                Just(Value::Null),
527                arb_oid().prop_map(Value::ObjectIdentifier),
528                any::<[u8; 4]>().prop_map(Value::IpAddress),
529                any::<u32>().prop_map(Value::Counter32),
530                any::<u32>().prop_map(Value::Gauge32),
531                any::<u32>().prop_map(Value::TimeTicks),
532                any::<u64>().prop_map(Value::Counter64),
533                Just(Value::NoSuchObject),
534                Just(Value::NoSuchInstance),
535                Just(Value::EndOfMibView),
536                (any::<u8>(), prop::collection::vec(any::<u8>(), 0..256)).prop_map(
537                    |(tag, data)| Value::Unknown {
538                        tag,
539                        data: Bytes::from(data),
540                    }
541                ),
542            ]
543        }
544
545        proptest! {
546            #[test]
547            fn encoded_size_matches_encoding(
548                oid in arb_oid(),
549                value in arb_value()
550            ) {
551                let vb = VarBind::new(oid, value);
552                let mut buf = EncodeBuf::new();
553                vb.encode(&mut buf);
554                prop_assert_eq!(
555                    vb.encoded_size(),
556                    buf.len(),
557                    "encoded_size mismatch: computed={}, actual={}",
558                    vb.encoded_size(),
559                    buf.len()
560                );
561            }
562        }
563    }
564}