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)]
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    /// This encodes the VarBind to a temporary buffer to determine the exact size.
44    /// Useful for response size estimation in GETBULK processing.
45    pub fn encoded_size(&self) -> usize {
46        let mut buf = EncodeBuf::new();
47        self.encode(&mut buf);
48        buf.len()
49    }
50
51    /// Decode from BER.
52    pub fn decode(decoder: &mut Decoder) -> Result<Self> {
53        let mut seq = decoder.read_sequence()?;
54        let oid = seq.read_oid()?;
55        let value = Value::decode(&mut seq)?;
56        Ok(VarBind { oid, value })
57    }
58}
59
60impl std::fmt::Display for VarBind {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        write!(f, "{} = {}", self.oid, self.value)
63    }
64}
65
66/// Encodes a list of VarBinds to BER format.
67///
68/// Writes the VarBinds as a SEQUENCE of SEQUENCE elements, where each inner
69/// SEQUENCE contains an OID and its associated value.
70pub fn encode_varbind_list(buf: &mut EncodeBuf, varbinds: &[VarBind]) {
71    buf.push_sequence(|buf| {
72        // Encode in reverse order since we're using reverse buffer
73        for vb in varbinds.iter().rev() {
74            vb.encode(buf);
75        }
76    });
77}
78
79/// Decodes a BER-encoded VarBind list into a vector of VarBinds.
80///
81/// Expects a SEQUENCE containing zero or more VarBind SEQUENCE elements.
82pub fn decode_varbind_list(decoder: &mut Decoder) -> Result<Vec<VarBind>> {
83    let mut seq = decoder.read_sequence()?;
84
85    // Estimate capacity: typical VarBind is 20-50 bytes, use 16 as conservative divisor
86    // to minimize reallocations while not over-allocating
87    let estimated_capacity = (seq.remaining() / 16).max(1);
88    let mut varbinds = Vec::with_capacity(estimated_capacity);
89
90    while !seq.is_empty() {
91        varbinds.push(VarBind::decode(&mut seq)?);
92    }
93
94    Ok(varbinds)
95}
96
97/// Encodes OIDs with NULL values for GET requests.
98///
99/// Creates a VarBind list where each OID is paired with a NULL value,
100/// as required by SNMP GET, GETNEXT, and GETBULK request PDUs.
101pub fn encode_null_varbinds(buf: &mut EncodeBuf, oids: &[Oid]) {
102    buf.push_sequence(|buf| {
103        for oid in oids.iter().rev() {
104            buf.push_sequence(|buf| {
105                buf.push_null();
106                buf.push_oid(oid);
107            });
108        }
109    });
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use crate::oid;
116    use bytes::Bytes;
117
118    #[test]
119    fn test_varbind_roundtrip() {
120        let vb = VarBind::new(oid!(1, 3, 6, 1), Value::Integer(42));
121
122        let mut buf = EncodeBuf::new();
123        vb.encode(&mut buf);
124        let bytes = buf.finish();
125
126        let mut decoder = Decoder::new(bytes);
127        let decoded = VarBind::decode(&mut decoder).unwrap();
128
129        assert_eq!(vb, decoded);
130    }
131
132    #[test]
133    fn test_varbind_list_roundtrip() {
134        let varbinds = vec![
135            VarBind::new(oid!(1, 3, 6, 1), Value::Integer(1)),
136            VarBind::new(oid!(1, 3, 6, 2), Value::Integer(2)),
137        ];
138
139        let mut buf = EncodeBuf::new();
140        encode_varbind_list(&mut buf, &varbinds);
141        let bytes = buf.finish();
142
143        let mut decoder = Decoder::new(bytes);
144        let decoded = decode_varbind_list(&mut decoder).unwrap();
145
146        assert_eq!(varbinds, decoded);
147    }
148
149    // ========================================================================
150    // Exception Value VarBind Tests
151    // ========================================================================
152
153    #[test]
154    fn test_varbind_no_such_object() {
155        let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::NoSuchObject);
156
157        let mut buf = EncodeBuf::new();
158        vb.encode(&mut buf);
159        let bytes = buf.finish();
160
161        let mut decoder = Decoder::new(bytes);
162        let decoded = VarBind::decode(&mut decoder).unwrap();
163
164        assert_eq!(vb, decoded);
165        assert!(decoded.value.is_exception());
166    }
167
168    #[test]
169    fn test_varbind_no_such_instance() {
170        let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::NoSuchInstance);
171
172        let mut buf = EncodeBuf::new();
173        vb.encode(&mut buf);
174        let bytes = buf.finish();
175
176        let mut decoder = Decoder::new(bytes);
177        let decoded = VarBind::decode(&mut decoder).unwrap();
178
179        assert_eq!(vb, decoded);
180        assert!(decoded.value.is_exception());
181    }
182
183    #[test]
184    fn test_varbind_end_of_mib_view() {
185        let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::EndOfMibView);
186
187        let mut buf = EncodeBuf::new();
188        vb.encode(&mut buf);
189        let bytes = buf.finish();
190
191        let mut decoder = Decoder::new(bytes);
192        let decoded = VarBind::decode(&mut decoder).unwrap();
193
194        assert_eq!(vb, decoded);
195        assert!(decoded.value.is_exception());
196    }
197
198    // ========================================================================
199    // VarBind List Edge Cases
200    // ========================================================================
201
202    #[test]
203    fn test_varbind_list_empty() {
204        let varbinds: Vec<VarBind> = vec![];
205
206        let mut buf = EncodeBuf::new();
207        encode_varbind_list(&mut buf, &varbinds);
208        let bytes = buf.finish();
209
210        let mut decoder = Decoder::new(bytes);
211        let decoded = decode_varbind_list(&mut decoder).unwrap();
212
213        assert!(decoded.is_empty());
214    }
215
216    #[test]
217    fn test_varbind_list_single() {
218        let varbinds = vec![VarBind::new(oid!(1, 3, 6, 1), Value::Integer(42))];
219
220        let mut buf = EncodeBuf::new();
221        encode_varbind_list(&mut buf, &varbinds);
222        let bytes = buf.finish();
223
224        let mut decoder = Decoder::new(bytes);
225        let decoded = decode_varbind_list(&mut decoder).unwrap();
226
227        assert_eq!(varbinds, decoded);
228    }
229
230    #[test]
231    fn test_varbind_list_with_exceptions() {
232        let varbinds = vec![
233            VarBind::new(
234                oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
235                Value::OctetString(Bytes::from_static(b"Linux router")),
236            ),
237            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 99, 0), Value::NoSuchObject),
238            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::TimeTicks(123456)),
239            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 100, 0), Value::NoSuchInstance),
240        ];
241
242        let mut buf = EncodeBuf::new();
243        encode_varbind_list(&mut buf, &varbinds);
244        let bytes = buf.finish();
245
246        let mut decoder = Decoder::new(bytes);
247        let decoded = decode_varbind_list(&mut decoder).unwrap();
248
249        assert_eq!(varbinds, decoded);
250        assert!(!decoded[0].value.is_exception());
251        assert!(decoded[1].value.is_exception());
252        assert!(!decoded[2].value.is_exception());
253        assert!(decoded[3].value.is_exception());
254    }
255
256    #[test]
257    fn test_varbind_list_all_exceptions() {
258        let varbinds = vec![
259            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::NoSuchObject),
260            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 2, 0), Value::NoSuchInstance),
261            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::EndOfMibView),
262        ];
263
264        let mut buf = EncodeBuf::new();
265        encode_varbind_list(&mut buf, &varbinds);
266        let bytes = buf.finish();
267
268        let mut decoder = Decoder::new(bytes);
269        let decoded = decode_varbind_list(&mut decoder).unwrap();
270
271        assert_eq!(varbinds, decoded);
272        assert!(decoded.iter().all(|vb| vb.value.is_exception()));
273    }
274
275    #[test]
276    fn test_varbind_list_mixed_value_types() {
277        let varbinds = vec![
278            VarBind::new(
279                oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
280                Value::OctetString(Bytes::from_static(b"test")),
281            ),
282            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 2, 0), Value::Integer(42)),
283            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 3, 0), Value::Counter32(1000)),
284            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 4, 0), Value::Gauge32(500)),
285            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 5, 0), Value::TimeTicks(99999)),
286            VarBind::new(
287                oid!(1, 3, 6, 1, 2, 1, 1, 6, 0),
288                Value::IpAddress([192, 168, 1, 1]),
289            ),
290            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 7, 0), Value::Counter64(u64::MAX)),
291            VarBind::new(
292                oid!(1, 3, 6, 1, 2, 1, 1, 8, 0),
293                Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4)),
294            ),
295            VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 9, 0), Value::Null),
296        ];
297
298        let mut buf = EncodeBuf::new();
299        encode_varbind_list(&mut buf, &varbinds);
300        let bytes = buf.finish();
301
302        let mut decoder = Decoder::new(bytes);
303        let decoded = decode_varbind_list(&mut decoder).unwrap();
304
305        assert_eq!(varbinds, decoded);
306    }
307
308    #[test]
309    fn test_null_varbinds_encoding() {
310        let oids = vec![
311            oid!(1, 3, 6, 1, 2, 1, 1, 1, 0),
312            oid!(1, 3, 6, 1, 2, 1, 1, 3, 0),
313            oid!(1, 3, 6, 1, 2, 1, 1, 5, 0),
314        ];
315
316        let mut buf = EncodeBuf::new();
317        encode_null_varbinds(&mut buf, &oids);
318        let bytes = buf.finish();
319
320        let mut decoder = Decoder::new(bytes);
321        let decoded = decode_varbind_list(&mut decoder).unwrap();
322
323        assert_eq!(decoded.len(), 3);
324        for (i, vb) in decoded.iter().enumerate() {
325            assert_eq!(vb.oid, oids[i]);
326            assert_eq!(vb.value, Value::Null);
327        }
328    }
329
330    #[test]
331    fn test_null_varbinds_empty() {
332        let oids: Vec<Oid> = vec![];
333
334        let mut buf = EncodeBuf::new();
335        encode_null_varbinds(&mut buf, &oids);
336        let bytes = buf.finish();
337
338        let mut decoder = Decoder::new(bytes);
339        let decoded = decode_varbind_list(&mut decoder).unwrap();
340
341        assert!(decoded.is_empty());
342    }
343
344    // ========================================================================
345    // VarBind Display Tests
346    // ========================================================================
347
348    #[test]
349    fn test_varbind_display() {
350        let vb = VarBind::new(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0), Value::Integer(42));
351        let display = format!("{}", vb);
352        assert!(display.contains("1.3.6.1.2.1.1.1.0"));
353        assert!(display.contains("42"));
354    }
355
356    #[test]
357    fn test_varbind_display_exception() {
358        let vb = VarBind::new(oid!(1, 3, 6, 1), Value::NoSuchObject);
359        let display = format!("{}", vb);
360        assert!(display.contains("noSuchObject"));
361    }
362
363    // ========================================================================
364    // VarBind::null() Constructor Test
365    // ========================================================================
366
367    #[test]
368    fn test_varbind_null_constructor() {
369        let vb = VarBind::null(oid!(1, 3, 6, 1, 2, 1, 1, 1, 0));
370        assert_eq!(vb.oid, oid!(1, 3, 6, 1, 2, 1, 1, 1, 0));
371        assert_eq!(vb.value, Value::Null);
372    }
373}