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    // ========================================================================
205    // VarBind List Edge Cases
206    // ========================================================================
207
208    #[test]
209    fn test_varbind_list_empty() {
210        let varbinds: Vec<VarBind> = vec![];
211
212        let mut buf = EncodeBuf::new();
213        encode_varbind_list(&mut buf, &varbinds);
214        let bytes = buf.finish();
215
216        let mut decoder = Decoder::new(bytes);
217        let decoded = decode_varbind_list(&mut decoder).unwrap();
218
219        assert!(decoded.is_empty());
220    }
221
222    #[test]
223    fn test_varbind_list_single() {
224        let varbinds = vec![VarBind::new(oid!(1, 3, 6, 1), Value::Integer(42))];
225
226        let mut buf = EncodeBuf::new();
227        encode_varbind_list(&mut buf, &varbinds);
228        let bytes = buf.finish();
229
230        let mut decoder = Decoder::new(bytes);
231        let decoded = decode_varbind_list(&mut decoder).unwrap();
232
233        assert_eq!(varbinds, decoded);
234    }
235
236    #[test]
237    fn test_varbind_list_with_exceptions() {
238        let varbinds = vec![
239            VarBind::new(
240                oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
241                Value::OctetString(Bytes::from_static(b"Linux router")),
242            ),
243            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 99, 0), Value::NoSuchObject),
244            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::TimeTicks(123456)),
245            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 100, 0), Value::NoSuchInstance),
246        ];
247
248        let mut buf = EncodeBuf::new();
249        encode_varbind_list(&mut buf, &varbinds);
250        let bytes = buf.finish();
251
252        let mut decoder = Decoder::new(bytes);
253        let decoded = decode_varbind_list(&mut decoder).unwrap();
254
255        assert_eq!(varbinds, decoded);
256        assert!(!decoded[0].value.is_exception());
257        assert!(decoded[1].value.is_exception());
258        assert!(!decoded[2].value.is_exception());
259        assert!(decoded[3].value.is_exception());
260    }
261
262    #[test]
263    fn test_varbind_list_all_exceptions() {
264        let varbinds = vec![
265            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::NoSuchObject),
266            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 2, 0), Value::NoSuchInstance),
267            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::EndOfMibView),
268        ];
269
270        let mut buf = EncodeBuf::new();
271        encode_varbind_list(&mut buf, &varbinds);
272        let bytes = buf.finish();
273
274        let mut decoder = Decoder::new(bytes);
275        let decoded = decode_varbind_list(&mut decoder).unwrap();
276
277        assert_eq!(varbinds, decoded);
278        assert!(decoded.iter().all(|vb| vb.value.is_exception()));
279    }
280
281    #[test]
282    fn test_varbind_list_mixed_value_types() {
283        let varbinds = vec![
284            VarBind::new(
285                oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
286                Value::OctetString(Bytes::from_static(b"test")),
287            ),
288            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 2, 0), Value::Integer(42)),
289            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::Counter32(1000)),
290            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 4, 0), Value::Gauge32(500)),
291            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 5, 0), Value::TimeTicks(99999)),
292            VarBind::new(
293                oid!(1, 3, 6, 1, 2, 1, 1, 6, 0),
294                Value::IpAddress([192, 168, 1, 1]),
295            ),
296            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 7, 0), Value::Counter64(u64::MAX)),
297            VarBind::new(
298                oid!(1, 3, 6, 1, 2, 1, 1, 8, 0),
299                Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4)),
300            ),
301            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 9, 0), Value::Null),
302        ];
303
304        let mut buf = EncodeBuf::new();
305        encode_varbind_list(&mut buf, &varbinds);
306        let bytes = buf.finish();
307
308        let mut decoder = Decoder::new(bytes);
309        let decoded = decode_varbind_list(&mut decoder).unwrap();
310
311        assert_eq!(varbinds, decoded);
312    }
313
314    #[test]
315    fn test_null_varbinds_encoding() {
316        let oids = vec![
317            oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
318            oid!(1, 3, 6, 1, 2, 1, 1, 3, 0),
319            oid!(1, 3, 6, 1, 2, 1, 1, 5, 0),
320        ];
321
322        let mut buf = EncodeBuf::new();
323        encode_null_varbinds(&mut buf, &oids);
324        let bytes = buf.finish();
325
326        let mut decoder = Decoder::new(bytes);
327        let decoded = decode_varbind_list(&mut decoder).unwrap();
328
329        assert_eq!(decoded.len(), 3);
330        for (i, vb) in decoded.iter().enumerate() {
331            assert_eq!(vb.oid, oids[i]);
332            assert_eq!(vb.value, Value::Null);
333        }
334    }
335
336    #[test]
337    fn test_null_varbinds_empty() {
338        let oids: Vec<Oid> = vec![];
339
340        let mut buf = EncodeBuf::new();
341        encode_null_varbinds(&mut buf, &oids);
342        let bytes = buf.finish();
343
344        let mut decoder = Decoder::new(bytes);
345        let decoded = decode_varbind_list(&mut decoder).unwrap();
346
347        assert!(decoded.is_empty());
348    }
349
350    // ========================================================================
351    // VarBind Display Tests
352    // ========================================================================
353
354    #[test]
355    fn test_varbind_display() {
356        let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::Integer(42));
357        let display = format!("{}", vb);
358        assert!(display.contains("1.3.6.1.2.1.1.1.0"));
359        assert!(display.contains("42"));
360    }
361
362    #[test]
363    fn test_varbind_display_exception() {
364        let vb = VarBind::new(oid!(1, 3, 6, 1), Value::NoSuchObject);
365        let display = format!("{}", vb);
366        assert!(display.contains("noSuchObject"));
367    }
368
369    // ========================================================================
370    // VarBind::null() Constructor Test
371    // ========================================================================
372
373    #[test]
374    fn test_varbind_null_constructor() {
375        let vb = VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0));
376        assert_eq!(vb.oid, oid!(1, 3, 6, 1, 2, 1, 1, 1, 0));
377        assert_eq!(vb.value, Value::Null);
378    }
379
380    // ========================================================================
381    // VarBind::encoded_size() Tests
382    // ========================================================================
383
384    /// Helper to verify encoded_size() matches actual encoding length
385    fn verify_encoded_size(vb: &VarBind) {
386        let mut buf = EncodeBuf::new();
387        vb.encode(&mut buf);
388        let actual = buf.len();
389        let computed = vb.encoded_size();
390        assert_eq!(
391            computed, actual,
392            "encoded_size mismatch for {:?}: computed={}, actual={}",
393            vb, computed, actual
394        );
395    }
396
397    #[test]
398    fn test_encoded_size_null() {
399        verify_encoded_size(&VarBind::null(oid!(1, 3, 6, 1)));
400        verify_encoded_size(&VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)));
401    }
402
403    #[test]
404    fn test_encoded_size_integer() {
405        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Integer(0)));
406        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Integer(127)));
407        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Integer(128)));
408        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Integer(-1)));
409        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Integer(i32::MAX)));
410        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Integer(i32::MIN)));
411    }
412
413    #[test]
414    fn test_encoded_size_octet_string() {
415        verify_encoded_size(&VarBind::new(
416            oid!(1, 3, 6, 1),
417            Value::OctetString(Bytes::new()),
418        ));
419        verify_encoded_size(&VarBind::new(
420            oid!(1, 3, 6, 1),
421            Value::OctetString(Bytes::from_static(b"hello world")),
422        ));
423        // Large string
424        verify_encoded_size(&VarBind::new(
425            oid!(1, 3, 6, 1),
426            Value::OctetString(Bytes::from(vec![0u8; 200])),
427        ));
428    }
429
430    #[test]
431    fn test_encoded_size_counters() {
432        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Counter32(0)));
433        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Counter32(u32::MAX)));
434        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Gauge32(12345)));
435        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::TimeTicks(99999)));
436        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Counter64(0)));
437        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::Counter64(u64::MAX)));
438    }
439
440    #[test]
441    fn test_encoded_size_oid_value() {
442        verify_encoded_size(&VarBind::new(
443            oid!(1, 3, 6, 1, 2, 1, 1, 2, 0),
444            Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4, 1, 9999)),
445        ));
446    }
447
448    #[test]
449    fn test_encoded_size_exceptions() {
450        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::NoSuchObject));
451        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::NoSuchInstance));
452        verify_encoded_size(&VarBind::new(oid!(1, 3, 6, 1), Value::EndOfMibView));
453    }
454
455    #[test]
456    fn test_encoded_size_ip_address() {
457        verify_encoded_size(&VarBind::new(
458            oid!(1, 3, 6, 1),
459            Value::IpAddress([192, 168, 1, 1]),
460        ));
461    }
462
463    mod proptests {
464        use super::*;
465        use crate::oid::Oid;
466        use proptest::prelude::*;
467
468        fn arb_oid() -> impl Strategy<Value = Oid> {
469            // Generate valid OIDs: first arc 0-2, second arc 0-39 (for arc1 < 2) or 0-999
470            (0u32..3, 0u32..40, prop::collection::vec(0u32..10000, 0..8)).prop_map(
471                |(arc1, arc2, rest)| {
472                    let mut arcs = vec![arc1, arc2];
473                    arcs.extend(rest);
474                    Oid::from_slice(&arcs)
475                },
476            )
477        }
478
479        fn arb_value() -> impl Strategy<Value = Value> {
480            prop_oneof![
481                any::<i32>().prop_map(Value::Integer),
482                prop::collection::vec(any::<u8>(), 0..256)
483                    .prop_map(|v| Value::OctetString(Bytes::from(v))),
484                Just(Value::Null),
485                arb_oid().prop_map(Value::ObjectIdentifier),
486                any::<[u8; 4]>().prop_map(Value::IpAddress),
487                any::<u32>().prop_map(Value::Counter32),
488                any::<u32>().prop_map(Value::Gauge32),
489                any::<u32>().prop_map(Value::TimeTicks),
490                any::<u64>().prop_map(Value::Counter64),
491                Just(Value::NoSuchObject),
492                Just(Value::NoSuchInstance),
493                Just(Value::EndOfMibView),
494            ]
495        }
496
497        proptest! {
498            #[test]
499            fn encoded_size_matches_encoding(
500                oid in arb_oid(),
501                value in arb_value()
502            ) {
503                let vb = VarBind::new(oid, value);
504                let mut buf = EncodeBuf::new();
505                vb.encode(&mut buf);
506                prop_assert_eq!(
507                    vb.encoded_size(),
508                    buf.len(),
509                    "encoded_size mismatch: computed={}, actual={}",
510                    vb.encoded_size(),
511                    buf.len()
512                );
513            }
514        }
515    }
516}