Skip to main content

async_snmp/ber/
encode.rs

1//! BER encoding.
2//!
3//! Uses a reverse buffer approach: writes from end backwards to avoid
4//! needing to pre-calculate lengths.
5
6use super::length::encode_length;
7use super::tag;
8use bytes::Bytes;
9
10/// Buffer for BER encoding that writes backwards.
11///
12/// This approach avoids needing to pre-calculate content lengths:
13/// we write the content first, then prepend the length and tag.
14pub struct EncodeBuf {
15    buf: Vec<u8>,
16}
17
18impl EncodeBuf {
19    /// Create a new encode buffer with default capacity.
20    #[must_use]
21    pub fn new() -> Self {
22        Self::with_capacity(512)
23    }
24
25    /// Create a new encode buffer with specified capacity.
26    #[must_use]
27    pub fn with_capacity(capacity: usize) -> Self {
28        Self {
29            buf: Vec::with_capacity(capacity),
30        }
31    }
32
33    /// Push a single byte (prepends to front).
34    pub fn push_byte(&mut self, byte: u8) {
35        self.buf.push(byte);
36    }
37
38    /// Push multiple bytes (prepends to front, reversed).
39    pub fn push_bytes(&mut self, bytes: &[u8]) {
40        self.buf.extend(bytes.iter().rev());
41    }
42
43    /// Push a BER length encoding.
44    pub fn push_length(&mut self, len: usize) {
45        let (bytes, count) = encode_length(len);
46        // The encode_length returns bytes in reverse order for prepending
47        for byte in bytes.iter().take(count) {
48            self.buf.push(*byte);
49        }
50    }
51
52    /// Push a BER tag.
53    pub fn push_tag(&mut self, tag: u8) {
54        self.buf.push(tag);
55    }
56
57    /// Get the current length of encoded data.
58    #[must_use]
59    pub fn len(&self) -> usize {
60        self.buf.len()
61    }
62
63    /// Check if buffer is empty.
64    #[must_use]
65    pub fn is_empty(&self) -> bool {
66        self.buf.is_empty()
67    }
68
69    /// Encode a constructed type (SEQUENCE, PDU, etc).
70    ///
71    /// Calls the closure to encode contents, then wraps with length and tag.
72    pub fn push_constructed<F>(&mut self, tag: u8, f: F)
73    where
74        F: FnOnce(&mut Self),
75    {
76        let start_len = self.len();
77        f(self);
78        let content_len = self.len() - start_len;
79        self.push_length(content_len);
80        self.push_tag(tag);
81    }
82
83    /// Encode a SEQUENCE.
84    pub fn push_sequence<F>(&mut self, f: F)
85    where
86        F: FnOnce(&mut Self),
87    {
88        self.push_constructed(tag::universal::SEQUENCE, f);
89    }
90
91    /// Encode an INTEGER.
92    pub fn push_integer(&mut self, value: i32) {
93        let (arr, len) = encode_integer_stack(value);
94        // Valid bytes are at the end of the array
95        self.push_bytes(&arr[4 - len..]);
96        self.push_length(len);
97        self.push_tag(tag::universal::INTEGER);
98    }
99
100    /// Encode a 64-bit integer (for Counter64).
101    pub fn push_integer64(&mut self, value: u64) {
102        let (arr, len) = encode_integer64_stack(value);
103        // Valid bytes are at the end of the array
104        self.push_bytes(&arr[9 - len..]);
105        self.push_length(len);
106        self.push_tag(tag::application::COUNTER64);
107    }
108
109    /// Encode an unsigned 32-bit integer with a specific tag.
110    pub fn push_unsigned32(&mut self, tag: u8, value: u32) {
111        let (arr, len) = encode_unsigned32_stack(value);
112        // Valid bytes are at the end of the array
113        self.push_bytes(&arr[5 - len..]);
114        self.push_length(len);
115        self.push_tag(tag);
116    }
117
118    /// Encode an OCTET STRING.
119    pub fn push_octet_string(&mut self, data: &[u8]) {
120        self.push_bytes(data);
121        self.push_length(data.len());
122        self.push_tag(tag::universal::OCTET_STRING);
123    }
124
125    /// Encode a NULL.
126    pub fn push_null(&mut self) {
127        self.push_length(0);
128        self.push_tag(tag::universal::NULL);
129    }
130
131    /// Encode an OBJECT IDENTIFIER.
132    pub fn push_oid(&mut self, oid: &crate::oid::Oid) {
133        let ber = oid.to_ber_smallvec();
134        self.push_bytes(&ber);
135        self.push_length(ber.len());
136        self.push_tag(tag::universal::OBJECT_IDENTIFIER);
137    }
138
139    /// Encode an IP address.
140    pub fn push_ip_address(&mut self, addr: [u8; 4]) {
141        self.push_bytes(&addr);
142        self.push_length(4);
143        self.push_tag(tag::application::IP_ADDRESS);
144    }
145
146    /// Finalize and return the encoded bytes.
147    ///
148    /// The buffer is reversed to produce the correct order.
149    #[must_use]
150    pub fn finish(mut self) -> Bytes {
151        self.buf.reverse();
152        Bytes::from(self.buf)
153    }
154
155    /// Finalize and return as `Vec<u8>`.
156    #[must_use]
157    pub fn finish_vec(mut self) -> Vec<u8> {
158        self.buf.reverse();
159        self.buf
160    }
161}
162
163impl Default for EncodeBuf {
164    fn default() -> Self {
165        Self::new()
166    }
167}
168
169/// Encode a signed 32-bit integer in minimal BER form.
170///
171/// Returns a stack-allocated array and the number of valid bytes.
172/// The valid bytes are at the END of the array (for reverse-buffer compatibility).
173#[inline]
174pub(super) fn encode_integer_stack(value: i32) -> ([u8; 4], usize) {
175    let bytes = value.to_be_bytes();
176
177    // Find first significant byte
178    let mut start = 0;
179    if value >= 0 {
180        // For positive/zero, skip leading 0x00 bytes (but keep one if needed for sign)
181        while start < 3 && bytes[start] == 0 && bytes[start + 1] & 0x80 == 0 {
182            start += 1;
183        }
184    } else {
185        // For negative, skip leading 0xFF bytes (but keep one if needed for sign)
186        while start < 3 && bytes[start] == 0xFF && bytes[start + 1] & 0x80 != 0 {
187            start += 1;
188        }
189    }
190
191    (bytes, 4 - start)
192}
193
194/// Encode an unsigned 32-bit integer.
195///
196/// Returns a stack-allocated array and the number of valid bytes.
197/// The valid bytes are at the END of the array (for reverse-buffer compatibility).
198#[inline]
199fn encode_unsigned32_stack(value: u32) -> ([u8; 5], usize) {
200    if value == 0 {
201        return ([0, 0, 0, 0, 0], 1);
202    }
203
204    let bytes = value.to_be_bytes();
205    let mut start = 0;
206
207    // Skip leading zeros, but add a 0x00 prefix if MSB is set (to avoid sign extension)
208    while start < 3 && bytes[start] == 0 {
209        start += 1;
210    }
211
212    if bytes[start] & 0x80 != 0 {
213        // Need to add a leading 0x00 to indicate positive
214        let mut result = [0u8; 5];
215        result[1..].copy_from_slice(&bytes);
216        (result, 5 - start)
217    } else {
218        let mut result = [0u8; 5];
219        result[1..].copy_from_slice(&bytes);
220        (result, 4 - start)
221    }
222}
223
224/// Encode an unsigned 64-bit integer.
225///
226/// Returns a stack-allocated array and the number of valid bytes.
227/// The valid bytes are at the END of the array (for reverse-buffer compatibility).
228#[inline]
229fn encode_integer64_stack(value: u64) -> ([u8; 9], usize) {
230    if value == 0 {
231        return ([0; 9], 1);
232    }
233
234    let bytes = value.to_be_bytes();
235    let mut start = 0;
236
237    // Skip leading zeros, but add a 0x00 prefix if MSB is set
238    while start < 7 && bytes[start] == 0 {
239        start += 1;
240    }
241
242    if bytes[start] & 0x80 != 0 {
243        // Need to add a leading 0x00 to indicate positive
244        let mut result = [0u8; 9];
245        result[1..].copy_from_slice(&bytes);
246        (result, 9 - start)
247    } else {
248        let mut result = [0u8; 9];
249        result[1..].copy_from_slice(&bytes);
250        (result, 8 - start)
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    /// Helper to extract the valid bytes from stack-based integer encoding
259    fn encode_integer(value: i32) -> Vec<u8> {
260        let (arr, len) = encode_integer_stack(value);
261        arr[4 - len..].to_vec()
262    }
263
264    /// Helper to extract the valid bytes from stack-based unsigned32 encoding
265    fn encode_unsigned32(value: u32) -> Vec<u8> {
266        let (arr, len) = encode_unsigned32_stack(value);
267        arr[5 - len..].to_vec()
268    }
269
270    #[test]
271    fn test_encode_integer() {
272        assert_eq!(encode_integer(0), vec![0]);
273        assert_eq!(encode_integer(1), vec![1]);
274        assert_eq!(encode_integer(127), vec![127]);
275        assert_eq!(encode_integer(128), vec![0, 128]);
276        assert_eq!(encode_integer(-1), vec![0xFF]);
277        assert_eq!(encode_integer(-128), vec![0x80]);
278        assert_eq!(encode_integer(-129), vec![0xFF, 0x7F]);
279    }
280
281    #[test]
282    fn test_encode_unsigned32() {
283        assert_eq!(encode_unsigned32(0), vec![0]);
284        assert_eq!(encode_unsigned32(127), vec![127]);
285        assert_eq!(encode_unsigned32(128), vec![0, 128]);
286        assert_eq!(encode_unsigned32(255), vec![0, 255]);
287        assert_eq!(encode_unsigned32(256), vec![1, 0]);
288    }
289
290    #[test]
291    fn test_encode_null() {
292        let mut buf = EncodeBuf::new();
293        buf.push_null();
294        let bytes = buf.finish();
295        assert_eq!(&bytes[..], &[0x05, 0x00]);
296    }
297
298    #[test]
299    fn test_encode_integer_value() {
300        let mut buf = EncodeBuf::new();
301        buf.push_integer(42);
302        let bytes = buf.finish();
303        assert_eq!(&bytes[..], &[0x02, 0x01, 0x2A]);
304    }
305
306    #[test]
307    fn test_encode_sequence() {
308        let mut buf = EncodeBuf::new();
309        buf.push_sequence(|buf| {
310            // Reverse buffer: push in reverse order for forward output
311            buf.push_integer(2);
312            buf.push_integer(1);
313        });
314        let bytes = buf.finish();
315        // SEQUENCE { INTEGER 1, INTEGER 2 }
316        assert_eq!(
317            &bytes[..],
318            &[0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02]
319        );
320    }
321}