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