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    Some((len, 1 + num_octets))
195}
196
197/// Decode a length from bytes, returning (length, `bytes_consumed`)
198///
199/// The `base_offset` parameter is used to report error offsets correctly
200/// when this is called from within a decoder. The `target` parameter provides
201/// the target address for error context.
202pub fn decode_length(
203    data: &[u8],
204    base_offset: usize,
205    target: Option<SocketAddr>,
206) -> Result<(usize, usize)> {
207    let target = target.unwrap_or(UNKNOWN_TARGET);
208
209    if data.is_empty() {
210        tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::TruncatedData }, "truncated data: unexpected end of input in length");
211        return Err(Error::MalformedResponse { target }.boxed());
212    }
213
214    let first = data[0];
215
216    if first == 0x80 {
217        // Indefinite length - rejected per net-snmp behavior
218        tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::IndefiniteLength }, "indefinite length encoding not supported");
219        return Err(Error::MalformedResponse { target }.boxed());
220    }
221
222    if first & 0x80 == 0 {
223        // Short form
224        Ok((first as usize, 1))
225    } else {
226        // Long form
227        let num_octets = (first & 0x7F) as usize;
228
229        if num_octets == 0 {
230            tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::InvalidLength }, "invalid length encoding: zero octets in long form");
231            return Err(Error::MalformedResponse { target }.boxed());
232        }
233
234        if num_octets > 8 {
235            // Net-snmp on 64-bit rejects > sizeof(long) = 8 length octets.
236            tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::LengthTooLong { octets: num_octets } }, "length encoding too long");
237            return Err(Error::MalformedResponse { target }.boxed());
238        }
239
240        if data.len() < 1 + num_octets {
241            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");
242            return Err(Error::MalformedResponse { target }.boxed());
243        }
244
245        let mut len: usize = 0;
246        for i in 0..num_octets {
247            len = (len << 8) | (data[1 + i] as usize);
248        }
249
250        if len > MAX_LENGTH {
251            tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::LengthExceedsMax { length: len, max: MAX_LENGTH } }, "length exceeds maximum");
252            return Err(Error::MalformedResponse { target }.boxed());
253        }
254
255        Ok((len, 1 + num_octets))
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn test_short_form() {
265        assert_eq!(decode_length(&[0], 0, None).unwrap(), (0, 1));
266        assert_eq!(decode_length(&[127], 0, None).unwrap(), (127, 1));
267        assert_eq!(decode_length(&[1], 0, None).unwrap(), (1, 1));
268    }
269
270    #[test]
271    fn test_long_form_1_byte() {
272        assert_eq!(decode_length(&[0x81, 128], 0, None).unwrap(), (128, 2));
273        assert_eq!(decode_length(&[0x81, 255], 0, None).unwrap(), (255, 2));
274    }
275
276    #[test]
277    fn test_long_form_2_bytes() {
278        assert_eq!(
279            decode_length(&[0x82, 0x01, 0x00], 0, None).unwrap(),
280            (256, 3)
281        );
282        assert_eq!(
283            decode_length(&[0x82, 0xFF, 0xFF], 0, None).unwrap(),
284            (65535, 3)
285        );
286    }
287
288    #[test]
289    fn test_indefinite_rejected() {
290        assert!(decode_length(&[0x80], 0, None).is_err());
291    }
292
293    #[test]
294    fn test_encode_short() {
295        let (buf, len) = encode_length(0);
296        assert_eq!(&buf[..len], &[0]);
297
298        let (buf, len) = encode_length(127);
299        assert_eq!(&buf[..len], &[127]);
300    }
301
302    #[test]
303    fn test_encode_long() {
304        let (buf, len) = encode_length(128);
305        assert_eq!(&buf[..len], &[128, 0x81]);
306
307        let (buf, len) = encode_length(256);
308        assert_eq!(&buf[..len], &[0, 1, 0x82]);
309    }
310
311    #[test]
312    fn test_accept_oversized_length_encoding() {
313        // Non-minimal length encodings are valid per X.690 Section 8.1.3.5 Note 2
314        // 0x82 0x00 0x05 = length 5 using 2 bytes (minimal would be 0x05)
315        let result = decode_length(&[0x82, 0x00, 0x05], 0, None);
316        assert_eq!(result.unwrap(), (5, 3));
317
318        // 0x81 0x01 = length 1 using long form (non-minimal, minimal would be 0x01)
319        let result = decode_length(&[0x81, 0x01], 0, None);
320        assert_eq!(result.unwrap(), (1, 2));
321
322        // 0x82 0x00 0x7F = length 127 using 2 bytes (non-minimal, minimal would be 0x7F)
323        let result = decode_length(&[0x82, 0x00, 0x7F], 0, None);
324        assert_eq!(result.unwrap(), (127, 3));
325
326        // 0x83 0x00 0x00 0x80 = length 128 using 3 bytes (non-minimal, minimal would be 0x81 0x80)
327        let result = decode_length(&[0x83, 0x00, 0x00, 0x80], 0, None);
328        assert_eq!(result.unwrap(), (128, 4));
329    }
330
331    #[test]
332    fn test_length_encoded_len() {
333        assert_eq!(length_encoded_len(0), 1);
334        assert_eq!(length_encoded_len(127), 1);
335        assert_eq!(length_encoded_len(128), 2);
336        assert_eq!(length_encoded_len(255), 2);
337        assert_eq!(length_encoded_len(256), 3);
338        assert_eq!(length_encoded_len(65535), 3);
339        assert_eq!(length_encoded_len(65536), 4);
340    }
341
342    #[test]
343    fn test_base128_len() {
344        assert_eq!(base128_len(0), 1);
345        assert_eq!(base128_len(127), 1);
346        assert_eq!(base128_len(128), 2);
347        assert_eq!(base128_len(16_383), 2);
348        assert_eq!(base128_len(16_384), 3);
349        assert_eq!(base128_len(2_097_151), 3);
350        assert_eq!(base128_len(2_097_152), 4);
351        assert_eq!(base128_len(268_435_455), 4);
352        assert_eq!(base128_len(268_435_456), 5);
353        assert_eq!(base128_len(u32::MAX), 5);
354    }
355
356    #[test]
357    fn test_integer_content_len() {
358        // Zero
359        assert_eq!(integer_content_len(0), 1);
360        // Small positive
361        assert_eq!(integer_content_len(1), 1);
362        assert_eq!(integer_content_len(127), 1);
363        // Needs padding byte
364        assert_eq!(integer_content_len(128), 2);
365        assert_eq!(integer_content_len(255), 2);
366        // Larger values
367        assert_eq!(integer_content_len(256), 2);
368        assert_eq!(integer_content_len(32767), 2);
369        assert_eq!(integer_content_len(32768), 3);
370        // Negative
371        assert_eq!(integer_content_len(-1), 1);
372        assert_eq!(integer_content_len(-128), 1);
373        assert_eq!(integer_content_len(-129), 2);
374        // Extremes
375        assert_eq!(integer_content_len(i32::MAX), 4);
376        assert_eq!(integer_content_len(i32::MIN), 4);
377    }
378
379    #[test]
380    fn test_unsigned32_content_len() {
381        assert_eq!(unsigned32_content_len(0), 1);
382        assert_eq!(unsigned32_content_len(127), 1);
383        assert_eq!(unsigned32_content_len(128), 2); // needs padding
384        assert_eq!(unsigned32_content_len(255), 2); // needs padding
385        assert_eq!(unsigned32_content_len(256), 2);
386        assert_eq!(unsigned32_content_len(u32::MAX), 5); // needs padding
387    }
388
389    #[test]
390    fn test_unsigned64_content_len() {
391        assert_eq!(unsigned64_content_len(0), 1);
392        assert_eq!(unsigned64_content_len(127), 1);
393        assert_eq!(unsigned64_content_len(128), 2); // needs padding
394        assert_eq!(unsigned64_content_len(u64::MAX), 9); // needs padding
395    }
396
397    #[test]
398    fn test_long_form_5_to_8_byte_length_accepted() {
399        // Net-snmp on 64-bit allows up to sizeof(long)=8 length octets.
400        // Non-minimal 5-byte encoding of length 5.
401        let result = decode_length(&[0x85, 0x00, 0x00, 0x00, 0x00, 0x05], 0, None);
402        assert_eq!(result.unwrap(), (5, 6));
403
404        // 8-byte encoding of length 1.
405        let result = decode_length(
406            &[0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
407            0,
408            None,
409        );
410        assert_eq!(result.unwrap(), (1, 9));
411
412        // 9 bytes (> 8) must still be rejected.
413        let result = decode_length(
414            &[0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
415            0,
416            None,
417        );
418        assert!(result.is_err());
419    }
420
421    #[test]
422    fn test_max_length_enforced() {
423        // Length at exactly MAX_LENGTH should succeed
424        let max = MAX_LENGTH;
425        let max_bytes = [
426            0x83,
427            ((max >> 16) & 0xFF) as u8,
428            ((max >> 8) & 0xFF) as u8,
429            (max & 0xFF) as u8,
430        ];
431        let result = decode_length(&max_bytes, 0, None);
432        assert_eq!(result.unwrap(), (MAX_LENGTH, 4));
433
434        // Length exceeding MAX_LENGTH should fail (use 4-byte encoding)
435        let over = MAX_LENGTH + 1;
436        let over_bytes = [
437            0x84, // 4 length bytes follow
438            ((over >> 24) & 0xFF) as u8,
439            ((over >> 16) & 0xFF) as u8,
440            ((over >> 8) & 0xFF) as u8,
441            (over & 0xFF) as u8,
442        ];
443        let result = decode_length(&over_bytes, 0, None);
444        assert!(result.is_err());
445        let err = result.unwrap_err();
446        assert!(
447            matches!(*err, Error::MalformedResponse { .. }),
448            "Expected MalformedResponse error, got {err:?}"
449        );
450    }
451
452    mod proptests {
453        use super::*;
454        use crate::ber::EncodeBuf;
455        use proptest::prelude::*;
456
457        proptest! {
458            #[test]
459            fn integer_content_len_matches_encoder(value: i32) {
460                // Encode the integer: tag (1 byte) + length (1 byte for i32) + content
461                let mut buf = EncodeBuf::new();
462                buf.push_integer(value);
463                let encoded_len = buf.len();
464                // For i32, content is at most 4 bytes, so length encoding is always 1 byte
465                // Total = 1 (tag) + 1 (length) + content_len
466                let actual_content_len = encoded_len - 2;
467                prop_assert_eq!(
468                    integer_content_len(value),
469                    actual_content_len,
470                    "Mismatch for value {}: computed={}, actual={}",
471                    value,
472                    integer_content_len(value),
473                    actual_content_len
474                );
475            }
476
477            #[test]
478            fn unsigned32_content_len_matches_encoder(value: u32) {
479                let mut buf = EncodeBuf::new();
480                buf.push_unsigned32(crate::ber::tag::application::COUNTER32, value);
481                let encoded_len = buf.len();
482                // For u32, content is at most 5 bytes, so length encoding is always 1 byte
483                let actual_content_len = encoded_len - 2;
484                prop_assert_eq!(
485                    unsigned32_content_len(value),
486                    actual_content_len,
487                    "Mismatch for value {}", value
488                );
489            }
490
491            #[test]
492            fn unsigned64_content_len_matches_encoder(value: u64) {
493                let mut buf = EncodeBuf::new();
494                buf.push_integer64(value);
495                let encoded_len = buf.len();
496                // For u64, content is at most 9 bytes, so length encoding is always 1 byte
497                let actual_content_len = encoded_len - 2;
498                prop_assert_eq!(
499                    unsigned64_content_len(value),
500                    actual_content_len,
501                    "Mismatch for value {}", value
502                );
503            }
504        }
505    }
506}