1use crate::ber::{Decoder, EncodeBuf};
6use crate::error::{Error, Result, UNKNOWN_TARGET};
7use crate::oid::Oid;
8use crate::value::Value;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub struct VarBind {
13 pub oid: Oid,
15 pub value: Value,
17}
18
19impl VarBind {
20 pub fn new(oid: Oid, value: Value) -> Self {
22 Self { oid, value }
23 }
24
25 #[must_use]
27 pub fn null(oid: Oid) -> Self {
28 Self {
29 oid,
30 value: Value::Null,
31 }
32 }
33
34 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 pub fn encoded_size(&self) -> usize {
47 use crate::ber::length_encoded_len;
48
49 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 1 + length_encoded_len(content_len) + content_len
56 }
57
58 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
79pub fn encode_varbind_list(buf: &mut EncodeBuf, varbinds: &[VarBind]) {
84 buf.push_sequence(|buf| {
85 for vb in varbinds.iter().rev() {
87 vb.encode(buf);
88 }
89 });
90}
91
92pub fn decode_varbind_list(decoder: &mut Decoder) -> Result<Vec<VarBind>> {
96 let mut seq = decoder.read_sequence()?;
97
98 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
110pub 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 #[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 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 let bytes = bytes::Bytes::from_static(&[
240 0x30, 0x0e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x01, 0x00, 0x04, 0x03, 0x61, 0x62, ]);
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 #[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 #[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 #[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 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 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 (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}