Skip to main content

async_snmp/ber/
length.rs

1//! BER length encoding and decoding.
2//!
3//! Length encoding follows X.690 Section 8.1.3:
4//! - Short form: Single byte, bit 8=0, value 0-127
5//! - Long form: Initial byte (bit 8=1, bits 7-1=count), followed by length bytes
6//! - Indefinite form (0x80): Rejected per net-snmp behavior
7
8use std::net::SocketAddr;
9
10use crate::error::internal::DecodeErrorKind;
11use crate::error::{Error, Result, UNKNOWN_TARGET};
12
13/// Maximum length we'll accept (to prevent `DoS`).
14///
15/// 2MB is far larger than any realistic SNMP message (typical messages are
16/// hundreds of bytes to a few KB). This provides a sanity check at the BER
17/// decode layer while still being generous enough for any legitimate use case.
18pub const MAX_LENGTH: usize = 0x20_0000; // 2MB
19
20/// Returns the number of bytes needed to encode a length value in BER.
21///
22/// Uses short form (1 byte) for lengths <= 127, long form otherwise.
23#[inline]
24pub(crate) const fn length_encoded_len(len: usize) -> usize {
25    if len <= 127 {
26        1
27    } else if len <= 0xFF {
28        2
29    } else if len <= 0xFFFF {
30        3
31    } else if len <= 0xFF_FFFF {
32        4
33    } else {
34        5
35    }
36}
37
38/// Returns the number of bytes needed for base-128 variable-length encoding.
39///
40/// Used for OID subidentifier encoding (X.690 Section 8.19.2).
41#[inline]
42pub(crate) const fn base128_len(value: u32) -> usize {
43    if value == 0 {
44        return 1;
45    }
46    // Count 7-bit groups needed: ceil(log2(value+1) / 7)
47    // For u32, this is at most 5 bytes
48    if value < 0x80 {
49        1
50    } else if value < 0x4000 {
51        2
52    } else if value < 0x20_0000 {
53        3
54    } else if value < 0x1000_0000 {
55        4
56    } else {
57        5
58    }
59}
60
61/// Returns the number of content bytes needed to encode a signed i32 in BER.
62#[inline]
63pub(crate) fn integer_content_len(value: i32) -> usize {
64    super::encode::encode_integer_stack(value).1
65}
66
67/// Returns the number of content bytes needed to encode an unsigned u32 in BER.
68#[inline]
69pub(crate) const fn unsigned32_content_len(value: u32) -> usize {
70    if value == 0 {
71        return 1;
72    }
73
74    let bytes = value.to_be_bytes();
75
76    // Skip leading zeros, but add a 0x00 prefix if MSB is set
77    if bytes[0] != 0 {
78        if bytes[0] & 0x80 != 0 { 5 } else { 4 }
79    } else if bytes[1] != 0 {
80        if bytes[1] & 0x80 != 0 { 4 } else { 3 }
81    } else if bytes[2] != 0 {
82        if bytes[2] & 0x80 != 0 { 3 } else { 2 }
83    } else if bytes[3] & 0x80 != 0 {
84        2
85    } else {
86        1
87    }
88}
89
90/// Returns the number of content bytes needed to encode an unsigned u64 in BER.
91#[inline]
92pub(crate) const fn unsigned64_content_len(value: u64) -> usize {
93    if value == 0 {
94        return 1;
95    }
96
97    let bytes = value.to_be_bytes();
98
99    // Find first non-zero byte and check if padding needed
100    if bytes[0] != 0 {
101        if bytes[0] & 0x80 != 0 { 9 } else { 8 }
102    } else if bytes[1] != 0 {
103        if bytes[1] & 0x80 != 0 { 8 } else { 7 }
104    } else if bytes[2] != 0 {
105        if bytes[2] & 0x80 != 0 { 7 } else { 6 }
106    } else if bytes[3] != 0 {
107        if bytes[3] & 0x80 != 0 { 6 } else { 5 }
108    } else if bytes[4] != 0 {
109        if bytes[4] & 0x80 != 0 { 5 } else { 4 }
110    } else if bytes[5] != 0 {
111        if bytes[5] & 0x80 != 0 { 4 } else { 3 }
112    } else if bytes[6] != 0 {
113        if bytes[6] & 0x80 != 0 { 3 } else { 2 }
114    } else if bytes[7] & 0x80 != 0 {
115        2
116    } else {
117        1
118    }
119}
120
121/// Encode a length value into the buffer (returns bytes in reverse order for prepending)
122///
123/// Uses short form for lengths <= 127, long form otherwise.
124#[must_use]
125pub fn encode_length(len: usize) -> ([u8; 5], usize) {
126    let mut buf = [0u8; 5];
127
128    if len <= 127 {
129        // Short form
130        buf[0] = len as u8;
131        (buf, 1)
132    } else if len <= 0xFF {
133        // Long form, 1 byte
134        buf[0] = len as u8;
135        buf[1] = 0x81;
136        (buf, 2)
137    } else if len <= 0xFFFF {
138        // Long form, 2 bytes
139        buf[0] = len as u8;
140        buf[1] = (len >> 8) as u8;
141        buf[2] = 0x82;
142        (buf, 3)
143    } else if len <= 0xFF_FFFF {
144        // Long form, 3 bytes
145        buf[0] = len as u8;
146        buf[1] = (len >> 8) as u8;
147        buf[2] = (len >> 16) as u8;
148        buf[3] = 0x83;
149        (buf, 4)
150    } else {
151        // Long form, 4 bytes
152        buf[0] = len as u8;
153        buf[1] = (len >> 8) as u8;
154        buf[2] = (len >> 16) as u8;
155        buf[3] = (len >> 24) as u8;
156        buf[4] = 0x84;
157        (buf, 5)
158    }
159}
160
161/// Parse a BER length from a byte slice, returning `Some((length, bytes_consumed))`.
162///
163/// Returns `None` if the data is empty, uses indefinite form, or contains an
164/// invalid or unsupported encoding. This is the infallible variant for contexts
165/// that use `Option`-based error handling rather than `Result`.
166pub(crate) fn parse_ber_length(data: &[u8]) -> Option<(usize, usize)> {
167    if data.is_empty() {
168        return None;
169    }
170
171    let first = data[0];
172
173    if first < 0x80 {
174        // Short form
175        return Some((first as usize, 1));
176    }
177
178    if first == 0x80 {
179        // Indefinite form - not supported
180        return None;
181    }
182
183    // Long form
184    let num_octets = (first & 0x7F) as usize;
185    if num_octets == 0 || num_octets > 8 || data.len() < 1 + num_octets {
186        return None;
187    }
188
189    let mut len: usize = 0;
190    for i in 0..num_octets {
191        len = (len << 8) | (data[1 + i] as usize);
192    }
193
194    // Reject lengths that exceed the sanity cap or the remaining buffer,
195    // matching decode_length. Without this, an attacker-controlled length
196    // (e.g. eight 0xFF octets producing usize::MAX) causes overflow/panic
197    // in callers that compute `pos + len`.
198    if len > MAX_LENGTH || len > data.len() - (1 + num_octets) {
199        return None;
200    }
201
202    Some((len, 1 + num_octets))
203}
204
205/// Decode a length from bytes, returning (length, `bytes_consumed`)
206///
207/// The `base_offset` parameter is used to report error offsets correctly
208/// when this is called from within a decoder. The `target` parameter provides
209/// the target address for error context.
210pub fn decode_length(
211    data: &[u8],
212    base_offset: usize,
213    target: Option<SocketAddr>,
214) -> Result<(usize, usize)> {
215    let target = target.unwrap_or(UNKNOWN_TARGET);
216
217    if data.is_empty() {
218        tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::TruncatedData }, "truncated data: unexpected end of input in length");
219        return Err(Error::MalformedResponse { target }.boxed());
220    }
221
222    let first = data[0];
223
224    if first == 0x80 {
225        // Indefinite length - rejected per net-snmp behavior
226        tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::IndefiniteLength }, "indefinite length encoding not supported");
227        return Err(Error::MalformedResponse { target }.boxed());
228    }
229
230    if first & 0x80 == 0 {
231        // Short form
232        Ok((first as usize, 1))
233    } else {
234        // Long form
235        let num_octets = (first & 0x7F) as usize;
236
237        if num_octets == 0 {
238            tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::InvalidLength }, "invalid length encoding: zero octets in long form");
239            return Err(Error::MalformedResponse { target }.boxed());
240        }
241
242        if num_octets > 8 {
243            // Net-snmp on 64-bit rejects > sizeof(long) = 8 length octets.
244            tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::LengthTooLong { octets: num_octets } }, "length encoding too long");
245            return Err(Error::MalformedResponse { target }.boxed());
246        }
247
248        if data.len() < 1 + num_octets {
249            tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::InsufficientData { needed: 1 + num_octets, available: data.len() } }, "truncated data in length field");
250            return Err(Error::MalformedResponse { target }.boxed());
251        }
252
253        let mut len: usize = 0;
254        for i in 0..num_octets {
255            len = (len << 8) | (data[1 + i] as usize);
256        }
257
258        if len > MAX_LENGTH {
259            tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::LengthExceedsMax { length: len, max: MAX_LENGTH } }, "length exceeds maximum");
260            return Err(Error::MalformedResponse { target }.boxed());
261        }
262
263        Ok((len, 1 + num_octets))
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    #[test]
272    fn test_short_form() {
273        assert_eq!(decode_length(&[0], 0, None).unwrap(), (0, 1));
274        assert_eq!(decode_length(&[127], 0, None).unwrap(), (127, 1));
275        assert_eq!(decode_length(&[1], 0, None).unwrap(), (1, 1));
276    }
277
278    #[test]
279    fn test_long_form_1_byte() {
280        assert_eq!(decode_length(&[0x81, 128], 0, None).unwrap(), (128, 2));
281        assert_eq!(decode_length(&[0x81, 255], 0, None).unwrap(), (255, 2));
282    }
283
284    #[test]
285    fn test_long_form_2_bytes() {
286        assert_eq!(
287            decode_length(&[0x82, 0x01, 0x00], 0, None).unwrap(),
288            (256, 3)
289        );
290        assert_eq!(
291            decode_length(&[0x82, 0xFF, 0xFF], 0, None).unwrap(),
292            (65535, 3)
293        );
294    }
295
296    #[test]
297    fn test_indefinite_rejected() {
298        assert!(decode_length(&[0x80], 0, None).is_err());
299    }
300
301    #[test]
302    fn test_encode_short() {
303        let (buf, len) = encode_length(0);
304        assert_eq!(&buf[..len], &[0]);
305
306        let (buf, len) = encode_length(127);
307        assert_eq!(&buf[..len], &[127]);
308    }
309
310    #[test]
311    fn test_encode_long() {
312        let (buf, len) = encode_length(128);
313        assert_eq!(&buf[..len], &[128, 0x81]);
314
315        let (buf, len) = encode_length(256);
316        assert_eq!(&buf[..len], &[0, 1, 0x82]);
317    }
318
319    #[test]
320    fn test_accept_oversized_length_encoding() {
321        // Non-minimal length encodings are valid per X.690 Section 8.1.3.5 Note 2
322        // 0x82 0x00 0x05 = length 5 using 2 bytes (minimal would be 0x05)
323        let result = decode_length(&[0x82, 0x00, 0x05], 0, None);
324        assert_eq!(result.unwrap(), (5, 3));
325
326        // 0x81 0x01 = length 1 using long form (non-minimal, minimal would be 0x01)
327        let result = decode_length(&[0x81, 0x01], 0, None);
328        assert_eq!(result.unwrap(), (1, 2));
329
330        // 0x82 0x00 0x7F = length 127 using 2 bytes (non-minimal, minimal would be 0x7F)
331        let result = decode_length(&[0x82, 0x00, 0x7F], 0, None);
332        assert_eq!(result.unwrap(), (127, 3));
333
334        // 0x83 0x00 0x00 0x80 = length 128 using 3 bytes (non-minimal, minimal would be 0x81 0x80)
335        let result = decode_length(&[0x83, 0x00, 0x00, 0x80], 0, None);
336        assert_eq!(result.unwrap(), (128, 4));
337    }
338
339    #[test]
340    fn test_length_encoded_len() {
341        assert_eq!(length_encoded_len(0), 1);
342        assert_eq!(length_encoded_len(127), 1);
343        assert_eq!(length_encoded_len(128), 2);
344        assert_eq!(length_encoded_len(255), 2);
345        assert_eq!(length_encoded_len(256), 3);
346        assert_eq!(length_encoded_len(65535), 3);
347        assert_eq!(length_encoded_len(65536), 4);
348    }
349
350    #[test]
351    fn test_base128_len() {
352        assert_eq!(base128_len(0), 1);
353        assert_eq!(base128_len(127), 1);
354        assert_eq!(base128_len(128), 2);
355        assert_eq!(base128_len(16_383), 2);
356        assert_eq!(base128_len(16_384), 3);
357        assert_eq!(base128_len(2_097_151), 3);
358        assert_eq!(base128_len(2_097_152), 4);
359        assert_eq!(base128_len(268_435_455), 4);
360        assert_eq!(base128_len(268_435_456), 5);
361        assert_eq!(base128_len(u32::MAX), 5);
362    }
363
364    #[test]
365    fn test_integer_content_len() {
366        // Zero
367        assert_eq!(integer_content_len(0), 1);
368        // Small positive
369        assert_eq!(integer_content_len(1), 1);
370        assert_eq!(integer_content_len(127), 1);
371        // Needs padding byte
372        assert_eq!(integer_content_len(128), 2);
373        assert_eq!(integer_content_len(255), 2);
374        // Larger values
375        assert_eq!(integer_content_len(256), 2);
376        assert_eq!(integer_content_len(32767), 2);
377        assert_eq!(integer_content_len(32768), 3);
378        // Negative
379        assert_eq!(integer_content_len(-1), 1);
380        assert_eq!(integer_content_len(-128), 1);
381        assert_eq!(integer_content_len(-129), 2);
382        // Extremes
383        assert_eq!(integer_content_len(i32::MAX), 4);
384        assert_eq!(integer_content_len(i32::MIN), 4);
385    }
386
387    #[test]
388    fn test_unsigned32_content_len() {
389        assert_eq!(unsigned32_content_len(0), 1);
390        assert_eq!(unsigned32_content_len(127), 1);
391        assert_eq!(unsigned32_content_len(128), 2); // needs padding
392        assert_eq!(unsigned32_content_len(255), 2); // needs padding
393        assert_eq!(unsigned32_content_len(256), 2);
394        assert_eq!(unsigned32_content_len(u32::MAX), 5); // needs padding
395    }
396
397    #[test]
398    fn test_unsigned64_content_len() {
399        assert_eq!(unsigned64_content_len(0), 1);
400        assert_eq!(unsigned64_content_len(127), 1);
401        assert_eq!(unsigned64_content_len(128), 2); // needs padding
402        assert_eq!(unsigned64_content_len(u64::MAX), 9); // needs padding
403    }
404
405    #[test]
406    fn test_long_form_5_to_8_byte_length_accepted() {
407        // Net-snmp on 64-bit allows up to sizeof(long)=8 length octets.
408        // Non-minimal 5-byte encoding of length 5.
409        let result = decode_length(&[0x85, 0x00, 0x00, 0x00, 0x00, 0x05], 0, None);
410        assert_eq!(result.unwrap(), (5, 6));
411
412        // 8-byte encoding of length 1.
413        let result = decode_length(
414            &[0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
415            0,
416            None,
417        );
418        assert_eq!(result.unwrap(), (1, 9));
419
420        // 9 bytes (> 8) must still be rejected.
421        let result = decode_length(
422            &[0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
423            0,
424            None,
425        );
426        assert!(result.is_err());
427    }
428
429    #[test]
430    fn test_parse_ber_length_rejects_oversized() {
431        // Long-form length of usize::MAX (0x88 + eight 0xFF octets) must be
432        // rejected rather than returned, which would overflow `pos + len`
433        // arithmetic in callers.
434        let huge = [0x88, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
435        assert_eq!(parse_ber_length(&huge), None);
436
437        // Long-form length exceeding MAX_LENGTH is rejected even with a
438        // plausible encoding.
439        let over = MAX_LENGTH + 1;
440        let over_bytes = [
441            0x84,
442            ((over >> 24) & 0xFF) as u8,
443            ((over >> 16) & 0xFF) as u8,
444            ((over >> 8) & 0xFF) as u8,
445            (over & 0xFF) as u8,
446        ];
447        assert_eq!(parse_ber_length(&over_bytes), None);
448
449        // Long-form length exceeding the remaining buffer is rejected.
450        // 0x81 0x05 claims 5 content bytes but only 2 follow.
451        assert_eq!(parse_ber_length(&[0x81, 0x05, 0x00, 0x00]), None);
452
453        // Long-form length that exactly matches the remaining buffer is
454        // accepted. 0x81 0x80 = 128 content bytes.
455        let mut ok = vec![0x81, 0x80];
456        ok.extend(std::iter::repeat_n(0u8, 128));
457        assert_eq!(parse_ber_length(&ok), Some((128, 2)));
458    }
459
460    #[test]
461    fn test_max_length_enforced() {
462        // Length at exactly MAX_LENGTH should succeed
463        let max = MAX_LENGTH;
464        let max_bytes = [
465            0x83,
466            ((max >> 16) & 0xFF) as u8,
467            ((max >> 8) & 0xFF) as u8,
468            (max & 0xFF) as u8,
469        ];
470        let result = decode_length(&max_bytes, 0, None);
471        assert_eq!(result.unwrap(), (MAX_LENGTH, 4));
472
473        // Length exceeding MAX_LENGTH should fail (use 4-byte encoding)
474        let over = MAX_LENGTH + 1;
475        let over_bytes = [
476            0x84, // 4 length bytes follow
477            ((over >> 24) & 0xFF) as u8,
478            ((over >> 16) & 0xFF) as u8,
479            ((over >> 8) & 0xFF) as u8,
480            (over & 0xFF) as u8,
481        ];
482        let result = decode_length(&over_bytes, 0, None);
483        assert!(result.is_err());
484        let err = result.unwrap_err();
485        assert!(
486            matches!(*err, Error::MalformedResponse { .. }),
487            "Expected MalformedResponse error, got {err:?}"
488        );
489    }
490
491    mod proptests {
492        use super::*;
493        use crate::ber::EncodeBuf;
494        use proptest::prelude::*;
495
496        proptest! {
497            #[test]
498            fn integer_content_len_matches_encoder(value: i32) {
499                // Encode the integer: tag (1 byte) + length (1 byte for i32) + content
500                let mut buf = EncodeBuf::new();
501                buf.push_integer(value);
502                let encoded_len = buf.len();
503                // For i32, content is at most 4 bytes, so length encoding is always 1 byte
504                // Total = 1 (tag) + 1 (length) + content_len
505                let actual_content_len = encoded_len - 2;
506                prop_assert_eq!(
507                    integer_content_len(value),
508                    actual_content_len,
509                    "Mismatch for value {}: computed={}, actual={}",
510                    value,
511                    integer_content_len(value),
512                    actual_content_len
513                );
514            }
515
516            #[test]
517            fn unsigned32_content_len_matches_encoder(value: u32) {
518                let mut buf = EncodeBuf::new();
519                buf.push_unsigned32(crate::ber::tag::application::COUNTER32, value);
520                let encoded_len = buf.len();
521                // For u32, content is at most 5 bytes, so length encoding is always 1 byte
522                let actual_content_len = encoded_len - 2;
523                prop_assert_eq!(
524                    unsigned32_content_len(value),
525                    actual_content_len,
526                    "Mismatch for value {}", value
527                );
528            }
529
530            #[test]
531            fn unsigned64_content_len_matches_encoder(value: u64) {
532                let mut buf = EncodeBuf::new();
533                buf.push_integer64(value);
534                let encoded_len = buf.len();
535                // For u64, content is at most 9 bytes, so length encoding is always 1 byte
536                let actual_content_len = encoded_len - 2;
537                prop_assert_eq!(
538                    unsigned64_content_len(value),
539                    actual_content_len,
540                    "Mismatch for value {}", value
541                );
542            }
543        }
544    }
545}