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 = 0x200000; // 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 <= 0xFFFFFF {
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 < 0x200000 {
53        3
54    } else if value < 0x10000000 {
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) const fn integer_content_len(value: i32) -> usize {
64    let bytes = value.to_be_bytes();
65
66    if value >= 0 {
67        // For positive/zero, skip leading 0x00 bytes (but keep one if next byte has MSB set)
68        if bytes[0] != 0 {
69            4
70        } else if bytes[1] != 0 || bytes[2] & 0x80 != 0 {
71            if bytes[1] & 0x80 != 0 { 4 } else { 3 }
72        } else if bytes[2] != 0 || bytes[3] & 0x80 != 0 {
73            if bytes[2] & 0x80 != 0 { 3 } else { 2 }
74        } else {
75            1
76        }
77    } else {
78        // For negative, skip leading 0xFF bytes (but keep one if next byte has MSB clear)
79        if bytes[0] != 0xFF {
80            4
81        } else if bytes[1] != 0xFF || bytes[2] & 0x80 == 0 {
82            if bytes[1] & 0x80 == 0 { 4 } else { 3 }
83        } else if bytes[2] != 0xFF || bytes[3] & 0x80 == 0 {
84            if bytes[2] & 0x80 == 0 { 3 } else { 2 }
85        } else {
86            1
87        }
88    }
89}
90
91/// Returns the number of content bytes needed to encode an unsigned u32 in BER.
92#[inline]
93pub(crate) const fn unsigned32_content_len(value: u32) -> usize {
94    if value == 0 {
95        return 1;
96    }
97
98    let bytes = value.to_be_bytes();
99
100    // Skip leading zeros, but add a 0x00 prefix if MSB is set
101    if bytes[0] != 0 {
102        if bytes[0] & 0x80 != 0 { 5 } else { 4 }
103    } else if bytes[1] != 0 {
104        if bytes[1] & 0x80 != 0 { 4 } else { 3 }
105    } else if bytes[2] != 0 {
106        if bytes[2] & 0x80 != 0 { 3 } else { 2 }
107    } else if bytes[3] & 0x80 != 0 {
108        2
109    } else {
110        1
111    }
112}
113
114/// Returns the number of content bytes needed to encode an unsigned u64 in BER.
115#[inline]
116pub(crate) const fn unsigned64_content_len(value: u64) -> usize {
117    if value == 0 {
118        return 1;
119    }
120
121    let bytes = value.to_be_bytes();
122
123    // Find first non-zero byte and check if padding needed
124    if bytes[0] != 0 {
125        if bytes[0] & 0x80 != 0 { 9 } else { 8 }
126    } else if bytes[1] != 0 {
127        if bytes[1] & 0x80 != 0 { 8 } else { 7 }
128    } else if bytes[2] != 0 {
129        if bytes[2] & 0x80 != 0 { 7 } else { 6 }
130    } else if bytes[3] != 0 {
131        if bytes[3] & 0x80 != 0 { 6 } else { 5 }
132    } else if bytes[4] != 0 {
133        if bytes[4] & 0x80 != 0 { 5 } else { 4 }
134    } else if bytes[5] != 0 {
135        if bytes[5] & 0x80 != 0 { 4 } else { 3 }
136    } else if bytes[6] != 0 {
137        if bytes[6] & 0x80 != 0 { 3 } else { 2 }
138    } else if bytes[7] & 0x80 != 0 {
139        2
140    } else {
141        1
142    }
143}
144
145/// Encode a length value into the buffer (returns bytes in reverse order for prepending)
146///
147/// Uses short form for lengths <= 127, long form otherwise.
148pub fn encode_length(len: usize) -> ([u8; 5], usize) {
149    let mut buf = [0u8; 5];
150
151    if len <= 127 {
152        // Short form
153        buf[0] = len as u8;
154        (buf, 1)
155    } else if len <= 0xFF {
156        // Long form, 1 byte
157        buf[0] = len as u8;
158        buf[1] = 0x81;
159        (buf, 2)
160    } else if len <= 0xFFFF {
161        // Long form, 2 bytes
162        buf[0] = len as u8;
163        buf[1] = (len >> 8) as u8;
164        buf[2] = 0x82;
165        (buf, 3)
166    } else if len <= 0xFFFFFF {
167        // Long form, 3 bytes
168        buf[0] = len as u8;
169        buf[1] = (len >> 8) as u8;
170        buf[2] = (len >> 16) as u8;
171        buf[3] = 0x83;
172        (buf, 4)
173    } else {
174        // Long form, 4 bytes
175        buf[0] = len as u8;
176        buf[1] = (len >> 8) as u8;
177        buf[2] = (len >> 16) as u8;
178        buf[3] = (len >> 24) as u8;
179        buf[4] = 0x84;
180        (buf, 5)
181    }
182}
183
184/// Decode a length from bytes, returning (length, bytes_consumed)
185///
186/// The `base_offset` parameter is used to report error offsets correctly
187/// when this is called from within a decoder. The `target` parameter provides
188/// the target address for error context.
189pub fn decode_length(
190    data: &[u8],
191    base_offset: usize,
192    target: Option<SocketAddr>,
193) -> Result<(usize, usize)> {
194    let target = target.unwrap_or(UNKNOWN_TARGET);
195
196    if data.is_empty() {
197        tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::TruncatedData }, "truncated data: unexpected end of input in length");
198        return Err(Error::MalformedResponse { target }.boxed());
199    }
200
201    let first = data[0];
202
203    if first == 0x80 {
204        // Indefinite length - rejected per net-snmp behavior
205        tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::IndefiniteLength }, "indefinite length encoding not supported");
206        return Err(Error::MalformedResponse { target }.boxed());
207    }
208
209    if first & 0x80 == 0 {
210        // Short form
211        Ok((first as usize, 1))
212    } else {
213        // Long form
214        let num_octets = (first & 0x7F) as usize;
215
216        if num_octets == 0 {
217            tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::InvalidLength }, "invalid length encoding: zero octets in long form");
218            return Err(Error::MalformedResponse { target }.boxed());
219        }
220
221        if num_octets > 4 {
222            tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::LengthTooLong { octets: num_octets } }, "length encoding too long");
223            return Err(Error::MalformedResponse { target }.boxed());
224        }
225
226        if data.len() < 1 + num_octets {
227            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");
228            return Err(Error::MalformedResponse { target }.boxed());
229        }
230
231        let mut len: usize = 0;
232        for i in 0..num_octets {
233            len = (len << 8) | (data[1 + i] as usize);
234        }
235
236        if len > MAX_LENGTH {
237            tracing::debug!(target: "async_snmp::ber", { snmp.offset = %base_offset, kind = %DecodeErrorKind::LengthExceedsMax { length: len, max: MAX_LENGTH } }, "length exceeds maximum");
238            return Err(Error::MalformedResponse { target }.boxed());
239        }
240
241        Ok((len, 1 + num_octets))
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_short_form() {
251        assert_eq!(decode_length(&[0], 0, None).unwrap(), (0, 1));
252        assert_eq!(decode_length(&[127], 0, None).unwrap(), (127, 1));
253        assert_eq!(decode_length(&[1], 0, None).unwrap(), (1, 1));
254    }
255
256    #[test]
257    fn test_long_form_1_byte() {
258        assert_eq!(decode_length(&[0x81, 128], 0, None).unwrap(), (128, 2));
259        assert_eq!(decode_length(&[0x81, 255], 0, None).unwrap(), (255, 2));
260    }
261
262    #[test]
263    fn test_long_form_2_bytes() {
264        assert_eq!(
265            decode_length(&[0x82, 0x01, 0x00], 0, None).unwrap(),
266            (256, 3)
267        );
268        assert_eq!(
269            decode_length(&[0x82, 0xFF, 0xFF], 0, None).unwrap(),
270            (65535, 3)
271        );
272    }
273
274    #[test]
275    fn test_indefinite_rejected() {
276        assert!(decode_length(&[0x80], 0, None).is_err());
277    }
278
279    #[test]
280    fn test_encode_short() {
281        let (buf, len) = encode_length(0);
282        assert_eq!(&buf[..len], &[0]);
283
284        let (buf, len) = encode_length(127);
285        assert_eq!(&buf[..len], &[127]);
286    }
287
288    #[test]
289    fn test_encode_long() {
290        let (buf, len) = encode_length(128);
291        assert_eq!(&buf[..len], &[128, 0x81]);
292
293        let (buf, len) = encode_length(256);
294        assert_eq!(&buf[..len], &[0, 1, 0x82]);
295    }
296
297    #[test]
298    fn test_accept_oversized_length_encoding() {
299        // Non-minimal length encodings are valid per X.690 Section 8.1.3.5 Note 2
300        // 0x82 0x00 0x05 = length 5 using 2 bytes (minimal would be 0x05)
301        let result = decode_length(&[0x82, 0x00, 0x05], 0, None);
302        assert_eq!(result.unwrap(), (5, 3));
303
304        // 0x81 0x01 = length 1 using long form (non-minimal, minimal would be 0x01)
305        let result = decode_length(&[0x81, 0x01], 0, None);
306        assert_eq!(result.unwrap(), (1, 2));
307
308        // 0x82 0x00 0x7F = length 127 using 2 bytes (non-minimal, minimal would be 0x7F)
309        let result = decode_length(&[0x82, 0x00, 0x7F], 0, None);
310        assert_eq!(result.unwrap(), (127, 3));
311
312        // 0x83 0x00 0x00 0x80 = length 128 using 3 bytes (non-minimal, minimal would be 0x81 0x80)
313        let result = decode_length(&[0x83, 0x00, 0x00, 0x80], 0, None);
314        assert_eq!(result.unwrap(), (128, 4));
315    }
316
317    #[test]
318    fn test_length_encoded_len() {
319        assert_eq!(length_encoded_len(0), 1);
320        assert_eq!(length_encoded_len(127), 1);
321        assert_eq!(length_encoded_len(128), 2);
322        assert_eq!(length_encoded_len(255), 2);
323        assert_eq!(length_encoded_len(256), 3);
324        assert_eq!(length_encoded_len(65535), 3);
325        assert_eq!(length_encoded_len(65536), 4);
326    }
327
328    #[test]
329    fn test_base128_len() {
330        assert_eq!(base128_len(0), 1);
331        assert_eq!(base128_len(127), 1);
332        assert_eq!(base128_len(128), 2);
333        assert_eq!(base128_len(16383), 2);
334        assert_eq!(base128_len(16384), 3);
335        assert_eq!(base128_len(2097151), 3);
336        assert_eq!(base128_len(2097152), 4);
337        assert_eq!(base128_len(268435455), 4);
338        assert_eq!(base128_len(268435456), 5);
339        assert_eq!(base128_len(u32::MAX), 5);
340    }
341
342    #[test]
343    fn test_integer_content_len() {
344        // Zero
345        assert_eq!(integer_content_len(0), 1);
346        // Small positive
347        assert_eq!(integer_content_len(1), 1);
348        assert_eq!(integer_content_len(127), 1);
349        // Needs padding byte
350        assert_eq!(integer_content_len(128), 2);
351        assert_eq!(integer_content_len(255), 2);
352        // Larger values
353        assert_eq!(integer_content_len(256), 2);
354        assert_eq!(integer_content_len(32767), 2);
355        assert_eq!(integer_content_len(32768), 3);
356        // Negative
357        assert_eq!(integer_content_len(-1), 1);
358        assert_eq!(integer_content_len(-128), 1);
359        assert_eq!(integer_content_len(-129), 2);
360        // Extremes
361        assert_eq!(integer_content_len(i32::MAX), 4);
362        assert_eq!(integer_content_len(i32::MIN), 4);
363    }
364
365    #[test]
366    fn test_unsigned32_content_len() {
367        assert_eq!(unsigned32_content_len(0), 1);
368        assert_eq!(unsigned32_content_len(127), 1);
369        assert_eq!(unsigned32_content_len(128), 2); // needs padding
370        assert_eq!(unsigned32_content_len(255), 2); // needs padding
371        assert_eq!(unsigned32_content_len(256), 2);
372        assert_eq!(unsigned32_content_len(u32::MAX), 5); // needs padding
373    }
374
375    #[test]
376    fn test_unsigned64_content_len() {
377        assert_eq!(unsigned64_content_len(0), 1);
378        assert_eq!(unsigned64_content_len(127), 1);
379        assert_eq!(unsigned64_content_len(128), 2); // needs padding
380        assert_eq!(unsigned64_content_len(u64::MAX), 9); // needs padding
381    }
382
383    #[test]
384    fn test_max_length_enforced() {
385        // Length at exactly MAX_LENGTH should succeed
386        let max = MAX_LENGTH;
387        let max_bytes = [
388            0x83,
389            ((max >> 16) & 0xFF) as u8,
390            ((max >> 8) & 0xFF) as u8,
391            (max & 0xFF) as u8,
392        ];
393        let result = decode_length(&max_bytes, 0, None);
394        assert_eq!(result.unwrap(), (MAX_LENGTH, 4));
395
396        // Length exceeding MAX_LENGTH should fail (use 4-byte encoding)
397        let over = MAX_LENGTH + 1;
398        let over_bytes = [
399            0x84, // 4 length bytes follow
400            ((over >> 24) & 0xFF) as u8,
401            ((over >> 16) & 0xFF) as u8,
402            ((over >> 8) & 0xFF) as u8,
403            (over & 0xFF) as u8,
404        ];
405        let result = decode_length(&over_bytes, 0, None);
406        assert!(result.is_err());
407        let err = result.unwrap_err();
408        assert!(
409            matches!(*err, Error::MalformedResponse { .. }),
410            "Expected MalformedResponse error, got {:?}",
411            err
412        );
413    }
414
415    mod proptests {
416        use super::*;
417        use crate::ber::EncodeBuf;
418        use proptest::prelude::*;
419
420        proptest! {
421            #[test]
422            fn integer_content_len_matches_encoder(value: i32) {
423                // Encode the integer: tag (1 byte) + length (1 byte for i32) + content
424                let mut buf = EncodeBuf::new();
425                buf.push_integer(value);
426                let encoded_len = buf.len();
427                // For i32, content is at most 4 bytes, so length encoding is always 1 byte
428                // Total = 1 (tag) + 1 (length) + content_len
429                let actual_content_len = encoded_len - 2;
430                prop_assert_eq!(
431                    integer_content_len(value),
432                    actual_content_len,
433                    "Mismatch for value {}: computed={}, actual={}",
434                    value,
435                    integer_content_len(value),
436                    actual_content_len
437                );
438            }
439
440            #[test]
441            fn unsigned32_content_len_matches_encoder(value: u32) {
442                let mut buf = EncodeBuf::new();
443                buf.push_unsigned32(crate::ber::tag::application::COUNTER32, value);
444                let encoded_len = buf.len();
445                // For u32, content is at most 5 bytes, so length encoding is always 1 byte
446                let actual_content_len = encoded_len - 2;
447                prop_assert_eq!(
448                    unsigned32_content_len(value),
449                    actual_content_len,
450                    "Mismatch for value {}", value
451                );
452            }
453
454            #[test]
455            fn unsigned64_content_len_matches_encoder(value: u64) {
456                let mut buf = EncodeBuf::new();
457                buf.push_integer64(value);
458                let encoded_len = buf.len();
459                // For u64, content is at most 9 bytes, so length encoding is always 1 byte
460                let actual_content_len = encoded_len - 2;
461                prop_assert_eq!(
462                    unsigned64_content_len(value),
463                    actual_content_len,
464                    "Mismatch for value {}", value
465                );
466            }
467        }
468    }
469}