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    pub fn null(oid: Oid) -> Self {
27        Self {
28            oid,
29            value: Value::Null,
30        }
31    }
32
33    /// Encode to BER.
34    pub fn encode(&self, buf: &mut EncodeBuf) {
35        buf.push_sequence(|buf| {
36            self.value.encode(buf);
37            buf.push_oid(&self.oid);
38        });
39    }
40
41    /// Returns the exact encoded size of this VarBind in bytes.
42    ///
43    /// Computes the size arithmetically without allocating.
44    /// Useful for response size estimation in GETBULK processing.
45    pub fn encoded_size(&self) -> usize {
46        use crate::ber::length_encoded_len;
47
48        // VarBind is SEQUENCE { oid, value }
49        let oid_len = self.oid.ber_encoded_len();
50        let value_len = self.value.ber_encoded_len();
51        let content_len = oid_len + value_len;
52
53        // SEQUENCE tag (1) + length encoding + content
54        1 + length_encoded_len(content_len) + content_len
55    }
56
57    /// Decode from BER.
58    pub fn decode(decoder: &mut Decoder) -> Result<Self> {
59        let mut seq = decoder.read_sequence()?;
60        let oid = seq.read_oid()?;
61        let value = Value::decode(&mut seq)?;
62        Ok(VarBind { oid, value })
63    }
64}
65
66impl std::fmt::Display for VarBind {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        write!(f, "{} = {}", self.oid, self.value)
69    }
70}
71
72/// Encodes a list of VarBinds to BER format.
73///
74/// Writes the VarBinds as a SEQUENCE of SEQUENCE elements, where each inner
75/// SEQUENCE contains an OID and its associated value.
76pub fn encode_varbind_list(buf: &mut EncodeBuf, varbinds: &[VarBind]) {
77    buf.push_sequence(|buf| {
78        // Encode in reverse order since we're using reverse buffer
79        for vb in varbinds.iter().rev() {
80            vb.encode(buf);
81        }
82    });
83}
84
85/// Decodes a BER-encoded VarBind list into a vector of VarBinds.
86///
87/// Expects a SEQUENCE containing zero or more VarBind SEQUENCE elements.
88pub fn decode_varbind_list(decoder: &mut Decoder) -> Result<Vec<VarBind>> {
89    let mut seq = decoder.read_sequence()?;
90
91    // Estimate capacity: typical VarBind is 20-50 bytes, use 16 as conservative divisor
92    // to minimize reallocations while not over-allocating
93    let estimated_capacity = (seq.remaining() / 16).max(1);
94    let mut varbinds = Vec::with_capacity(estimated_capacity);
95
96    while !seq.is_empty() {
97        varbinds.push(VarBind::decode(&mut seq)?);
98    }
99
100    Ok(varbinds)
101}
102
103/// Encodes OIDs with NULL values for GET requests.
104///
105/// Creates a VarBind list where each OID is paired with a NULL value,
106/// as required by SNMP GET, GETNEXT, and GETBULK request PDUs.
107pub fn encode_null_varbinds(buf: &mut EncodeBuf, oids: &[Oid]) {
108    buf.push_sequence(|buf| {
109        for oid in oids.iter().rev() {
110            buf.push_sequence(|buf| {
111                buf.push_null();
112                buf.push_oid(oid);
113            });
114        }
115    });
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use crate::oid;
122    use bytes::Bytes;
123
124    #[test]
125    fn test_varbind_roundtrip() {
126        let vb = VarBind::new(oid!(1, 3, 6, 1), Value::Integer(42));
127
128        let mut buf = EncodeBuf::new();
129        vb.encode(&mut buf);
130        let bytes = buf.finish();
131
132        let mut decoder = Decoder::new(bytes);
133        let decoded = VarBind::decode(&mut decoder).unwrap();
134
135        assert_eq!(vb, decoded);
136    }
137
138    #[test]
139    fn test_varbind_list_roundtrip() {
140        let varbinds = vec![
141            VarBind::new(oid!(1, 3, 6, 1), Value::Integer(1)),
142            VarBind::new(oid!(1, 3, 6, 2), Value::Integer(2)),
143        ];
144
145        let mut buf = EncodeBuf::new();
146        encode_varbind_list(&mut buf, &varbinds);
147        let bytes = buf.finish();
148
149        let mut decoder = Decoder::new(bytes);
150        let decoded = decode_varbind_list(&mut decoder).unwrap();
151
152        assert_eq!(varbinds, decoded);
153    }
154
155    // ========================================================================
156    // Exception Value VarBind Tests
157    // ========================================================================
158
159    #[test]
160    fn test_varbind_no_such_object() {
161        let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::NoSuchObject);
162
163        let mut buf = EncodeBuf::new();
164        vb.encode(&mut buf);
165        let bytes = buf.finish();
166
167        let mut decoder = Decoder::new(bytes);
168        let decoded = VarBind::decode(&mut decoder).unwrap();
169
170        assert_eq!(vb, decoded);
171        assert!(decoded.value.is_exception());
172    }
173
174    #[test]
175    fn test_varbind_no_such_instance() {
176        let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::NoSuchInstance);
177
178        let mut buf = EncodeBuf::new();
179        vb.encode(&mut buf);
180        let bytes = buf.finish();
181
182        let mut decoder = Decoder::new(bytes);
183        let decoded = VarBind::decode(&mut decoder).unwrap();
184
185        assert_eq!(vb, decoded);
186        assert!(decoded.value.is_exception());
187    }
188
189    #[test]
190    fn test_varbind_end_of_mib_view() {
191        let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::EndOfMibView);
192
193        let mut buf = EncodeBuf::new();
194        vb.encode(&mut buf);
195        let bytes = buf.finish();
196
197        let mut decoder = Decoder::new(bytes);
198        let decoded = VarBind::decode(&mut decoder).unwrap();
199
200        assert_eq!(vb, decoded);
201        assert!(decoded.value.is_exception());
202    }
203
204    #[test]
205    fn test_varbind_zero_length_oid_end_of_mib_view() {
206        // Some devices send endOfMibView with a zero-length OID instead of echoing
207        // back the requested OID (RFC 3416 violation). net-snmp accepts this by
208        // treating 06 00 as OID 0.0; we accept it by returning an empty OID.
209        // The bytes are: 30 04 06 00 82 00
210        //   30 04  - SEQUENCE length 4
211        //   06 00  - OID tag, zero-length content (malformed)
212        //   82 00  - endOfMibView, length 0
213        let bytes = bytes::Bytes::from_static(&[0x30, 0x04, 0x06, 0x00, 0x82, 0x00]);
214        let mut decoder = Decoder::new(bytes);
215        let vb = VarBind::decode(&mut decoder).unwrap();
216        assert!(vb.oid.is_empty());
217        assert_eq!(vb.value, Value::EndOfMibView);
218    }
219
220    #[test]
221    fn test_varbind_octetstring_length_exceeds_sequence() {
222        // MikroTik firmware bug: OctetString value declares a length 1 byte
223        // larger than what fits in the enclosing varbind SEQUENCE. We clamp to
224        // the available bytes rather than rejecting the varbind.
225        //
226        // Packet: 30 0e 06 08 2b 06 01 02 01 01 01 00 04 03 61 62
227        //   30 0e  - varbind SEQUENCE, length 14
228        //   06 08  - OID tag, length 8
229        //   2b 06 01 02 01 01 01 00  - OID content (1.3.6.1.2.1.1.1.0)
230        //   04 03  - OctetString tag, declared length 3 (WRONG - only 2 bytes remain)
231        //   61 62  - 2 bytes of actual data ("ab")
232        let bytes = bytes::Bytes::from_static(&[
233            0x30, 0x0e, // SEQUENCE length 14
234            0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x01, 0x00, // OID
235            0x04, 0x03, 0x61, 0x62, // OctetString: declares 3, has 2
236        ]);
237        let mut decoder = Decoder::new(bytes);
238        let vb = VarBind::decode(&mut decoder).unwrap();
239        assert_eq!(vb.oid, crate::oid!(1, 3, 6, 1, 2, 1, 1, 1, 0));
240        assert_eq!(
241            vb.value,
242            Value::OctetString(bytes::Bytes::from_static(b"ab"))
243        );
244    }
245
246    // ========================================================================
247    // VarBind List Edge Cases
248    // ========================================================================
249
250    #[test]
251    fn test_varbind_list_empty() {
252        let varbinds: Vec<VarBind> = vec![];
253
254        let mut buf = EncodeBuf::new();
255        encode_varbind_list(&mut buf, &varbinds);
256        let bytes = buf.finish();
257
258        let mut decoder = Decoder::new(bytes);
259        let decoded = decode_varbind_list(&mut decoder).unwrap();
260
261        assert!(decoded.is_empty());
262    }
263
264    #[test]
265    fn test_varbind_list_single() {
266        let varbinds = vec![VarBind::new(oid!(1, 3, 6, 1), Value::Integer(42))];
267
268        let mut buf = EncodeBuf::new();
269        encode_varbind_list(&mut buf, &varbinds);
270        let bytes = buf.finish();
271
272        let mut decoder = Decoder::new(bytes);
273        let decoded = decode_varbind_list(&mut decoder).unwrap();
274
275        assert_eq!(varbinds, decoded);
276    }
277
278    #[test]
279    fn test_varbind_list_with_exceptions() {
280        let varbinds = vec![
281            VarBind::new(
282                oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
283                Value::OctetString(Bytes::from_static(b"Linux router")),
284            ),
285            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 99, 0), Value::NoSuchObject),
286            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::TimeTicks(123456)),
287            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 100, 0), Value::NoSuchInstance),
288        ];
289
290        let mut buf = EncodeBuf::new();
291        encode_varbind_list(&mut buf, &varbinds);
292        let bytes = buf.finish();
293
294        let mut decoder = Decoder::new(bytes);
295        let decoded = decode_varbind_list(&mut decoder).unwrap();
296
297        assert_eq!(varbinds, decoded);
298        assert!(!decoded[0].value.is_exception());
299        assert!(decoded[1].value.is_exception());
300        assert!(!decoded[2].value.is_exception());
301        assert!(decoded[3].value.is_exception());
302    }
303
304    #[test]
305    fn test_varbind_list_all_exceptions() {
306        let varbinds = vec![
307            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::NoSuchObject),
308            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 2, 0), Value::NoSuchInstance),
309            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::EndOfMibView),
310        ];
311
312        let mut buf = EncodeBuf::new();
313        encode_varbind_list(&mut buf, &varbinds);
314        let bytes = buf.finish();
315
316        let mut decoder = Decoder::new(bytes);
317        let decoded = decode_varbind_list(&mut decoder).unwrap();
318
319        assert_eq!(varbinds, decoded);
320        assert!(decoded.iter().all(|vb| vb.value.is_exception()));
321    }
322
323    #[test]
324    fn test_varbind_list_mixed_value_types() {
325        let varbinds = vec![
326            VarBind::new(
327                oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
328                Value::OctetString(Bytes::from_static(b"test")),
329            ),
330            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 2, 0), Value::Integer(42)),
331            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::Counter32(1000)),
332            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 4, 0), Value::Gauge32(500)),
333            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 5, 0), Value::TimeTicks(99999)),
334            VarBind::new(
335                oid!(1, 3, 6, 1, 2, 1, 1, 6, 0),
336                Value::IpAddress([192, 168, 1, 1]),
337            ),
338            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 7, 0), Value::Counter64(u64::MAX)),
339            VarBind::new(
340                oid!(1, 3, 6, 1, 2, 1, 1, 8, 0),
341                Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4)),
342            ),
343            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 9, 0), Value::Null),
344        ];
345
346        let mut buf = EncodeBuf::new();
347        encode_varbind_list(&mut buf, &varbinds);
348        let bytes = buf.finish();
349
350        let mut decoder = Decoder::new(bytes);
351        let decoded = decode_varbind_list(&mut decoder).unwrap();
352
353        assert_eq!(varbinds, decoded);
354    }
355
356    #[test]
357    fn test_null_varbinds_encoding() {
358        let oids = vec![
359            oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
360            oid!(1, 3, 6, 1, 2, 1, 1, 3, 0),
361            oid!(1, 3, 6, 1, 2, 1, 1, 5, 0),
362        ];
363
364        let mut buf = EncodeBuf::new();
365        encode_null_varbinds(&mut buf, &oids);
366        let bytes = buf.finish();
367
368        let mut decoder = Decoder::new(bytes);
369        let decoded = decode_varbind_list(&mut decoder).unwrap();
370
371        assert_eq!(decoded.len(), 3);
372        for (i, vb) in decoded.iter().enumerate() {
373            assert_eq!(vb.oid, oids[i]);
374            assert_eq!(vb.value, Value::Null);
375        }
376    }
377
378    #[test]
379    fn test_null_varbinds_empty() {
380        let oids: Vec<Oid> = vec![];
381
382        let mut buf = EncodeBuf::new();
383        encode_null_varbinds(&mut buf, &oids);
384        let bytes = buf.finish();
385
386        let mut decoder = Decoder::new(bytes);
387        let decoded = decode_varbind_list(&mut decoder).unwrap();
388
389        assert!(decoded.is_empty());
390    }
391
392    // ========================================================================
393    // VarBind Display Tests
394    // ========================================================================
395
396    #[test]
397    fn test_varbind_display() {
398        let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::Integer(42));
399        let display = format!("{}", vb);
400        assert!(display.contains("1.3.6.1.2.1.1.1.0"));
401        assert!(display.contains("42"));
402    }
403
404    #[test]
405    fn test_varbind_display_exception() {
406        let vb = VarBind::new(oid!(1, 3, 6, 1), Value::NoSuchObject);
407        let display = format!("{}", vb);
408        assert!(display.contains("noSuchObject"));
409    }
410
411    // ========================================================================
412    // VarBind::null() Constructor Test
413    // ========================================================================
414
415    #[test]
416    fn test_varbind_null_constructor() {
417        let vb = VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0));
418        assert_eq!(vb.oid, oid!(1, 3, 6, 1, 2, 1, 1, 1, 0));
419        assert_eq!(vb.value, Value::Null);
420    }
421
422    // ========================================================================
423    // VarBind::encoded_size() Tests
424    // ========================================================================
425
426    /// Helper to verify encoded_size() matches actual encoding length
427    fn verify_encoded_size(vb: &VarBind) {
428        let mut buf = EncodeBuf::new();
429        vb.encode(&mut buf);
430        let actual = buf.len();
431        let computed = vb.encoded_size();
432        assert_eq!(
433            computed, actual,
434            "encoded_size mismatch for {:?}: computed={}, actual={}",
435            vb, computed, 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}