use std::net::SocketAddr;
use super::length::decode_length;
use super::tag;
use crate::error::internal::DecodeErrorKind;
use crate::error::{Error, Result, UNKNOWN_TARGET};
use crate::oid::Oid;
use bytes::Bytes;
pub struct Decoder {
data: Bytes,
offset: usize,
target: Option<SocketAddr>,
}
impl Decoder {
pub fn new(data: Bytes) -> Self {
Self {
data,
offset: 0,
target: None,
}
}
pub fn with_target(data: Bytes, target: SocketAddr) -> Self {
Self {
data,
offset: 0,
target: Some(target),
}
}
pub fn from_slice(data: &[u8]) -> Self {
Self::new(Bytes::copy_from_slice(data))
}
fn target(&self) -> SocketAddr {
self.target.unwrap_or(UNKNOWN_TARGET)
}
fn malformed(&self) -> Box<crate::error::Error> {
Error::MalformedResponse {
target: self.target(),
}
.boxed()
}
pub fn offset(&self) -> usize {
self.offset
}
pub fn remaining(&self) -> usize {
self.data.len() - self.offset
}
pub fn is_empty(&self) -> bool {
self.offset >= self.data.len()
}
pub fn peek_byte(&self) -> Option<u8> {
if self.offset < self.data.len() {
Some(self.data[self.offset])
} else {
None
}
}
pub fn peek_tag(&self) -> Option<u8> {
let byte = self.peek_byte()?;
if byte & 0x1F == 0x1F {
return None;
}
Some(byte)
}
pub fn read_byte(&mut self) -> Result<u8> {
if self.offset >= self.data.len() {
tracing::debug!(target: "async_snmp::ber", { snmp.offset = %self.offset, kind = %DecodeErrorKind::TruncatedData }, "truncated data: unexpected end of input");
return Err(self.malformed());
}
let byte = self.data[self.offset];
self.offset += 1;
Ok(byte)
}
pub fn read_tag(&mut self) -> Result<u8> {
let tag = self.read_byte()?;
if tag & 0x1F == 0x1F {
tracing::debug!(target: "async_snmp::ber", { snmp.offset = %self.offset - 1, kind = %DecodeErrorKind::UnexpectedTag { expected: 0, actual: tag } }, "multi-byte tag not supported");
return Err(self.malformed());
}
Ok(tag)
}
pub fn read_length(&mut self) -> Result<usize> {
let (len, consumed) = decode_length(&self.data[self.offset..], self.offset, self.target)?;
self.offset += consumed;
Ok(len)
}
pub fn read_bytes(&mut self, len: usize) -> Result<Bytes> {
if self.offset.saturating_add(len) > self.data.len() {
tracing::debug!(target: "async_snmp::ber", { snmp.offset = %self.offset, kind = %DecodeErrorKind::InsufficientData { needed: len, available: self.remaining() } }, "insufficient data");
return Err(self.malformed());
}
let bytes = self.data.slice(self.offset..self.offset + len);
self.offset += len;
Ok(bytes)
}
pub fn expect_tag(&mut self, expected: u8) -> Result<usize> {
let tag = self.read_tag()?;
if tag != expected {
tracing::debug!(target: "async_snmp::ber", { snmp.offset = %self.offset - 1, kind = %DecodeErrorKind::UnexpectedTag { expected, actual: tag } }, "unexpected tag");
return Err(self.malformed());
}
self.read_length()
}
pub fn read_integer(&mut self) -> Result<i32> {
let len = self.expect_tag(tag::universal::INTEGER)?;
self.read_integer_value(len)
}
pub fn read_integer_value(&mut self, len: usize) -> Result<i32> {
if len == 0 {
tracing::debug!(target: "async_snmp::ber", { snmp.offset = %self.offset, kind = %DecodeErrorKind::ZeroLengthInteger }, "zero-length integer");
return Err(self.malformed());
}
if len > 8 {
tracing::debug!(target: "async_snmp::ber", { snmp.offset = %self.offset, kind = %DecodeErrorKind::IntegerTooLong { length: len } }, "integer encoding too long");
return Err(self.malformed());
}
let bytes = self.read_bytes(len)?;
let is_negative = bytes[0] & 0x80 != 0;
let mut value: i64 = if is_negative { -1 } else { 0 };
for &byte in bytes.iter() {
value = (value << 8) | (byte as i64);
}
Ok(value as i32)
}
pub fn read_integer64(&mut self, expected_tag: u8) -> Result<u64> {
let len = self.expect_tag(expected_tag)?;
self.read_integer64_value(len)
}
pub fn read_integer64_value(&mut self, len: usize) -> Result<u64> {
if len == 0 {
tracing::warn!(target: "async_snmp::ber", { snmp.offset = %self.offset }, "zero-length Counter64; interpreting as 0");
return Ok(0);
}
if len > 9 {
tracing::debug!(target: "async_snmp::ber", { snmp.offset = %self.offset, kind = %DecodeErrorKind::Integer64TooLong { length: len } }, "integer64 too long");
return Err(self.malformed());
}
let bytes = self.read_bytes(len)?;
if len == 9 && bytes[0] != 0x00 {
tracing::debug!(target: "async_snmp::ber", { snmp.offset = %self.offset, kind = %DecodeErrorKind::Integer64MissingLeadingZero }, "9-octet integer64 missing leading zero");
return Err(self.malformed());
}
let mut value: u64 = 0;
for &byte in bytes.iter() {
value = (value << 8) | (byte as u64);
}
Ok(value)
}
pub fn read_unsigned32(&mut self, expected_tag: u8) -> Result<u32> {
let len = self.expect_tag(expected_tag)?;
self.read_unsigned32_value(len)
}
pub fn read_unsigned32_value(&mut self, len: usize) -> Result<u32> {
if len == 0 {
tracing::debug!(target: "async_snmp::ber", { snmp.offset = %self.offset, kind = %DecodeErrorKind::ZeroLengthInteger }, "zero-length integer");
return Err(self.malformed());
}
if len > 9 {
tracing::debug!(target: "async_snmp::ber", { snmp.offset = %self.offset, kind = %DecodeErrorKind::Unsigned32TooLong { length: len } }, "unsigned32 encoding too long");
return Err(self.malformed());
}
let bytes = self.read_bytes(len)?;
if len == 9 && bytes[0] != 0x00 {
tracing::debug!(target: "async_snmp::ber", { snmp.offset = %self.offset, kind = %DecodeErrorKind::Unsigned32MissingLeadingZero }, "9-octet unsigned32 missing leading zero");
return Err(self.malformed());
}
let mut value: u64 = 0;
for &byte in bytes.iter() {
value = (value << 8) | (byte as u64);
}
Ok(value as u32)
}
pub fn read_octet_string(&mut self) -> Result<Bytes> {
let len = self.expect_tag(tag::universal::OCTET_STRING)?;
self.read_bytes(len)
}
pub fn read_null(&mut self) -> Result<()> {
let len = self.expect_tag(tag::universal::NULL)?;
if len != 0 {
tracing::debug!(target: "async_snmp::ber", { snmp.offset = %self.offset, kind = %DecodeErrorKind::InvalidNull }, "NULL with non-zero length");
return Err(self.malformed());
}
Ok(())
}
pub fn read_oid(&mut self) -> Result<Oid> {
let len = self.expect_tag(tag::universal::OBJECT_IDENTIFIER)?;
let bytes = self.read_bytes(len)?;
Oid::from_ber(&bytes)
}
pub fn read_oid_value(&mut self, len: usize) -> Result<Oid> {
let bytes = self.read_bytes(len)?;
Oid::from_ber(&bytes)
}
pub fn read_sequence(&mut self) -> Result<Decoder> {
self.read_constructed(tag::universal::SEQUENCE)
}
pub fn read_constructed(&mut self, expected_tag: u8) -> Result<Decoder> {
let len = self.expect_tag(expected_tag)?;
let content = self.read_bytes(len)?;
Ok(Decoder {
data: content,
offset: 0,
target: self.target,
})
}
pub fn read_ip_address(&mut self) -> Result<[u8; 4]> {
let len = self.expect_tag(tag::application::IP_ADDRESS)?;
if len != 4 {
tracing::debug!(target: "async_snmp::ber", { snmp.offset = %self.offset, kind = %DecodeErrorKind::InvalidIpAddressLength { length: len } }, "IP address must be 4 bytes");
return Err(self.malformed());
}
let bytes = self.read_bytes(4)?;
Ok([bytes[0], bytes[1], bytes[2], bytes[3]])
}
pub fn skip_tlv(&mut self) -> Result<()> {
let _tag = self.read_tag()?;
let len = self.read_length()?;
let new_offset = self.offset.saturating_add(len);
if new_offset > self.data.len() {
tracing::debug!(target: "async_snmp::ber", { snmp.offset = %self.offset, kind = %DecodeErrorKind::TlvOverflow }, "TLV extends past end of data");
return Err(self.malformed());
}
self.offset = new_offset;
Ok(())
}
pub fn sub_decoder(&mut self, len: usize) -> Result<Decoder> {
let content = self.read_bytes(len)?;
Ok(Decoder {
data: content,
offset: 0,
target: self.target,
})
}
pub fn as_bytes(&self) -> &Bytes {
&self.data
}
pub fn remaining_slice(&self) -> &[u8] {
&self.data[self.offset..]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decode_integer() {
let mut dec = Decoder::from_slice(&[0x02, 0x01, 0x00]);
assert_eq!(dec.read_integer().unwrap(), 0);
let mut dec = Decoder::from_slice(&[0x02, 0x01, 0x7F]);
assert_eq!(dec.read_integer().unwrap(), 127);
let mut dec = Decoder::from_slice(&[0x02, 0x02, 0x00, 0x80]);
assert_eq!(dec.read_integer().unwrap(), 128);
let mut dec = Decoder::from_slice(&[0x02, 0x01, 0xFF]);
assert_eq!(dec.read_integer().unwrap(), -1);
let mut dec = Decoder::from_slice(&[0x02, 0x01, 0x80]);
assert_eq!(dec.read_integer().unwrap(), -128);
}
#[test]
fn test_decode_null() {
let mut dec = Decoder::from_slice(&[0x05, 0x00]);
dec.read_null().unwrap();
}
#[test]
fn test_decode_octet_string() {
let mut dec = Decoder::from_slice(&[0x04, 0x05, b'h', b'e', b'l', b'l', b'o']);
let s = dec.read_octet_string().unwrap();
assert_eq!(&s[..], b"hello");
}
#[test]
fn test_decode_oid() {
let mut dec = Decoder::from_slice(&[0x06, 0x03, 0x2B, 0x06, 0x01]);
let oid = dec.read_oid().unwrap();
assert_eq!(oid.arcs(), &[1, 3, 6, 1]);
}
#[test]
fn test_decode_sequence() {
let mut dec = Decoder::from_slice(&[0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02]);
let mut seq = dec.read_sequence().unwrap();
assert_eq!(seq.read_integer().unwrap(), 1);
assert_eq!(seq.read_integer().unwrap(), 2);
}
#[test]
fn test_accept_non_minimal_integer() {
let mut dec = Decoder::from_slice(&[0x02, 0x02, 0x00, 0x01]);
assert_eq!(dec.read_integer().unwrap(), 1);
let mut dec = Decoder::from_slice(&[0x02, 0x02, 0x00, 0x7F]);
assert_eq!(dec.read_integer().unwrap(), 127);
let mut dec = Decoder::from_slice(&[0x02, 0x03, 0x00, 0x00, 0x80]);
assert_eq!(dec.read_integer().unwrap(), 128);
let mut dec = Decoder::from_slice(&[0x02, 0x02, 0xFF, 0xFF]);
assert_eq!(dec.read_integer().unwrap(), -1);
}
#[test]
fn test_integer_too_long_truncates() {
let mut dec = Decoder::from_slice(&[0x02, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05]);
assert_eq!(dec.read_integer().unwrap(), 0x02030405_i32);
let mut dec =
Decoder::from_slice(&[0x02, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
assert_eq!(dec.read_integer().unwrap(), 0x05060708_i32);
let mut dec = Decoder::from_slice(&[
0x02, 0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
]);
assert!(
dec.read_integer().is_err(),
"9-byte integer must be rejected"
);
}
#[test]
fn test_unsigned32_too_long_truncates() {
let mut dec = Decoder::from_slice(&[0x42, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
assert_eq!(dec.read_unsigned32(0x42).unwrap(), 0x03040506_u32);
let mut dec = Decoder::from_slice(&[
0x42, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
]);
assert_eq!(dec.read_unsigned32(0x42).unwrap(), u32::MAX);
let mut dec = Decoder::from_slice(&[
0x42, 0x0A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
]);
assert!(
dec.read_unsigned32(0x42).is_err(),
"10-byte unsigned32 must be rejected"
);
}
#[test]
fn test_zero_length_counter64_accepted() {
let mut dec = Decoder::from_slice(&[0x46, 0x00]);
let result = dec.read_integer64(0x46);
assert!(result.is_ok(), "zero-length Counter64 should be accepted");
assert_eq!(result.unwrap(), 0);
}
#[test]
fn test_counter64_nine_bytes_requires_leading_zero() {
let mut dec = Decoder::from_slice(&[
0x46, 0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
]);
let result = dec.read_integer64(0x46);
assert!(
result.is_err(),
"expected error for 9-byte Counter64 without leading zero"
);
let mut dec = Decoder::from_slice(&[
0x46, 0x09, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
]);
let result = dec.read_integer64(0x46);
assert!(
result.is_ok(),
"expected success for 9-byte Counter64 with leading zero"
);
assert_eq!(result.unwrap(), u64::MAX);
}
#[test]
fn test_unsigned32_nine_bytes_requires_leading_zero() {
let mut dec = Decoder::from_slice(&[
0x42, 0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
]);
assert!(
dec.read_unsigned32(0x42).is_err(),
"9-byte unsigned32 without leading zero must be rejected"
);
let mut dec = Decoder::from_slice(&[
0x42, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
]);
assert_eq!(dec.read_unsigned32(0x42).unwrap(), u32::MAX);
let mut dec = Decoder::from_slice(&[0x42, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00]);
assert_eq!(dec.read_unsigned32(0x42).unwrap(), 0u32);
}
#[test]
fn test_read_bytes_rejects_oversized_length() {
let mut dec = Decoder::from_slice(&[0x01, 0x02, 0x03]);
let result = dec.read_bytes(100);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(*err, crate::error::Error::MalformedResponse { .. }),
"expected MalformedResponse error, got {:?}",
err
);
}
#[test]
fn test_skip_tlv_rejects_oversized_length() {
let mut dec = Decoder::from_slice(&[0x04, 0x82, 0x01, 0x00, 0xAA, 0xBB, 0xCC]);
let result = dec.skip_tlv();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(*err, crate::error::Error::MalformedResponse { .. }),
"expected MalformedResponse error, got {:?}",
err
);
}
#[test]
fn test_read_tag_rejects_multi_byte_tag() {
let mut dec = Decoder::from_slice(&[0x1F, 0x02, 0x00]);
let result = dec.read_tag();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(*err, crate::error::Error::MalformedResponse { .. }),
"expected MalformedResponse error for multi-byte tag, got {:?}",
err
);
let mut dec = Decoder::from_slice(&[0x3F, 0x02, 0x00]);
let result = dec.read_tag();
assert!(result.is_err());
let mut dec = Decoder::from_slice(&[0x9F, 0x02, 0x00]);
let result = dec.read_tag();
assert!(result.is_err());
let mut dec = Decoder::from_slice(&[0x02, 0x01, 0x00]);
let result = dec.read_tag();
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0x02);
}
#[test]
fn test_peek_tag_rejects_multi_byte_tag() {
let dec = Decoder::from_slice(&[0x1F, 0x02, 0x00]);
let result = dec.peek_tag();
assert!(
result.is_none(),
"peek_tag should return None for multi-byte tag"
);
let dec = Decoder::from_slice(&[0x30, 0x00]);
let result = dec.peek_tag();
assert_eq!(result, Some(0x30));
}
}