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