use crate::ber::{Decoder, EncodeBuf, tag};
use crate::error::internal::DecodeErrorKind;
use crate::error::{Error, Result, UNKNOWN_TARGET};
use crate::format::hex;
use crate::oid::Oid;
use bytes::Bytes;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RowStatus {
Active = 1,
NotInService = 2,
NotReady = 3,
CreateAndGo = 4,
CreateAndWait = 5,
Destroy = 6,
}
impl TryFrom<i32> for RowStatus {
type Error = i32;
fn try_from(value: i32) -> std::result::Result<Self, i32> {
match value {
1 => Ok(Self::Active),
2 => Ok(Self::NotInService),
3 => Ok(Self::NotReady),
4 => Ok(Self::CreateAndGo),
5 => Ok(Self::CreateAndWait),
6 => Ok(Self::Destroy),
_ => Err(value),
}
}
}
impl RowStatus {
pub fn from_i32(value: i32) -> Option<Self> {
Self::try_from(value).ok()
}
}
impl From<RowStatus> for Value {
fn from(status: RowStatus) -> Self {
Value::Integer(status as i32)
}
}
impl std::fmt::Display for RowStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Active => write!(f, "active"),
Self::NotInService => write!(f, "notInService"),
Self::NotReady => write!(f, "notReady"),
Self::CreateAndGo => write!(f, "createAndGo"),
Self::CreateAndWait => write!(f, "createAndWait"),
Self::Destroy => write!(f, "destroy"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StorageType {
Other = 1,
Volatile = 2,
NonVolatile = 3,
Permanent = 4,
ReadOnly = 5,
}
impl TryFrom<i32> for StorageType {
type Error = i32;
fn try_from(value: i32) -> std::result::Result<Self, i32> {
match value {
1 => Ok(Self::Other),
2 => Ok(Self::Volatile),
3 => Ok(Self::NonVolatile),
4 => Ok(Self::Permanent),
5 => Ok(Self::ReadOnly),
_ => Err(value),
}
}
}
impl StorageType {
pub fn from_i32(value: i32) -> Option<Self> {
Self::try_from(value).ok()
}
}
impl From<StorageType> for Value {
fn from(storage: StorageType) -> Self {
Value::Integer(storage as i32)
}
}
impl std::fmt::Display for StorageType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Other => write!(f, "other"),
Self::Volatile => write!(f, "volatile"),
Self::NonVolatile => write!(f, "nonVolatile"),
Self::Permanent => write!(f, "permanent"),
Self::ReadOnly => write!(f, "readOnly"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Value {
Integer(i32),
OctetString(Bytes),
Null,
ObjectIdentifier(Oid),
IpAddress([u8; 4]),
Counter32(u32),
Gauge32(u32),
TimeTicks(u32),
Opaque(Bytes),
Counter64(u64),
NoSuchObject,
NoSuchInstance,
EndOfMibView,
Unknown { tag: u8, data: Bytes },
}
impl Value {
pub fn as_i32(&self) -> Option<i32> {
match self {
Value::Integer(v) => Some(*v),
_ => None,
}
}
pub fn as_u32(&self) -> Option<u32> {
match self {
Value::Counter32(v) | Value::Gauge32(v) | Value::TimeTicks(v) => Some(*v),
Value::Integer(v) if *v >= 0 => Some(*v as u32),
_ => None,
}
}
pub fn as_u64(&self) -> Option<u64> {
match self {
Value::Counter64(v) => Some(*v),
Value::Counter32(v) | Value::Gauge32(v) | Value::TimeTicks(v) => Some(*v as u64),
Value::Integer(v) if *v >= 0 => Some(*v as u64),
_ => None,
}
}
pub fn as_bytes(&self) -> Option<&[u8]> {
match self {
Value::OctetString(v) | Value::Opaque(v) => Some(v),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
self.as_bytes().and_then(|b| std::str::from_utf8(b).ok())
}
pub fn as_oid(&self) -> Option<&Oid> {
match self {
Value::ObjectIdentifier(oid) => Some(oid),
_ => None,
}
}
pub fn as_ip(&self) -> Option<std::net::Ipv4Addr> {
match self {
Value::IpAddress(bytes) => Some(std::net::Ipv4Addr::from(*bytes)),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
Value::Integer(v) => Some(*v as f64),
Value::Counter32(v) | Value::Gauge32(v) | Value::TimeTicks(v) => Some(*v as f64),
Value::Counter64(v) => Some(*v as f64),
_ => None,
}
}
pub fn as_f64_wrapped(&self) -> Option<f64> {
const MANTISSA_LIMIT: u64 = 1 << 53;
match self {
Value::Counter64(v) => Some((*v % MANTISSA_LIMIT) as f64),
_ => self.as_f64(),
}
}
pub fn as_decimal(&self, places: u8) -> Option<f64> {
let divisor = 10f64.powi(places as i32);
self.as_f64().map(|v| v / divisor)
}
pub fn as_duration(&self) -> Option<std::time::Duration> {
match self {
Value::TimeTicks(v) => Some(std::time::Duration::from_millis(*v as u64 * 10)),
_ => None,
}
}
pub fn as_opaque_float(&self) -> Option<f32> {
match self {
Value::Opaque(data)
if data.len() >= 7
&& data[0] == 0x9f && data[1] == 0x78 && data[2] == 0x04 =>
{
let bytes: [u8; 4] = data[3..7].try_into().ok()?;
Some(f32::from_be_bytes(bytes))
}
_ => None,
}
}
pub fn as_opaque_double(&self) -> Option<f64> {
match self {
Value::Opaque(data)
if data.len() >= 11
&& data[0] == 0x9f && data[1] == 0x79 && data[2] == 0x08 =>
{
let bytes: [u8; 8] = data[3..11].try_into().ok()?;
Some(f64::from_be_bytes(bytes))
}
_ => None,
}
}
pub fn as_opaque_counter64(&self) -> Option<u64> {
self.as_opaque_unsigned(0x76)
}
pub fn as_opaque_i64(&self) -> Option<i64> {
match self {
Value::Opaque(data)
if data.len() >= 4
&& data[0] == 0x9f && data[1] == 0x7a =>
{
let len = data[2] as usize;
if data.len() < 3 + len || len == 0 || len > 8 {
return None;
}
let bytes = &data[3..3 + len];
let is_negative = bytes[0] & 0x80 != 0;
let mut value: i64 = if is_negative { -1 } else { 0 };
for &byte in bytes {
value = (value << 8) | (byte as i64);
}
Some(value)
}
_ => None,
}
}
pub fn as_opaque_u64(&self) -> Option<u64> {
self.as_opaque_unsigned(0x7b)
}
fn as_opaque_unsigned(&self, expected_type: u8) -> Option<u64> {
match self {
Value::Opaque(data)
if data.len() >= 4
&& data[0] == 0x9f && data[1] == expected_type =>
{
let len = data[2] as usize;
if data.len() < 3 + len || len == 0 || len > 8 {
return None;
}
let bytes = &data[3..3 + len];
let mut value: u64 = 0;
for &byte in bytes {
value = (value << 8) | (byte as u64);
}
Some(value)
}
_ => None,
}
}
pub fn as_truth_value(&self) -> Option<bool> {
match self {
Value::Integer(1) => Some(true),
Value::Integer(2) => Some(false),
_ => None,
}
}
pub fn as_row_status(&self) -> Option<RowStatus> {
match self {
Value::Integer(v) => RowStatus::from_i32(*v),
_ => None,
}
}
pub fn as_storage_type(&self) -> Option<StorageType> {
match self {
Value::Integer(v) => StorageType::from_i32(*v),
_ => None,
}
}
pub fn is_exception(&self) -> bool {
matches!(
self,
Value::NoSuchObject | Value::NoSuchInstance | Value::EndOfMibView
)
}
pub(crate) fn ber_encoded_size(&self) -> usize {
use crate::ber::{
integer_content_len, length_encoded_len, unsigned32_content_len, unsigned64_content_len,
};
match self {
Value::Integer(v) => {
let content_len = integer_content_len(*v);
1 + length_encoded_len(content_len) + content_len
}
Value::OctetString(data) => {
let content_len = data.len();
1 + length_encoded_len(content_len) + content_len
}
Value::Null => 2, Value::ObjectIdentifier(oid) => oid.ber_encoded_size(),
Value::IpAddress(_) => 6, Value::Counter32(v) | Value::Gauge32(v) | Value::TimeTicks(v) => {
let content_len = unsigned32_content_len(*v);
1 + length_encoded_len(content_len) + content_len
}
Value::Opaque(data) => {
let content_len = data.len();
1 + length_encoded_len(content_len) + content_len
}
Value::Counter64(v) => {
let content_len = unsigned64_content_len(*v);
1 + length_encoded_len(content_len) + content_len
}
Value::NoSuchObject | Value::NoSuchInstance | Value::EndOfMibView => 2, Value::Unknown { data, .. } => {
let content_len = data.len();
1 + length_encoded_len(content_len) + content_len
}
}
}
pub fn format_with_hint(&self, hint: &str) -> Option<String> {
match self {
Value::OctetString(bytes) => Some(crate::format::display_hint::apply(hint, bytes)),
Value::Opaque(bytes) => Some(crate::format::display_hint::apply(hint, bytes)),
Value::Integer(v) => crate::format::display_hint::apply_integer(hint, *v),
_ => None,
}
}
pub fn encode(&self, buf: &mut EncodeBuf) {
match self {
Value::Integer(v) => buf.push_integer(*v),
Value::OctetString(data) => buf.push_octet_string(data),
Value::Null => buf.push_null(),
Value::ObjectIdentifier(oid) => buf.push_oid(oid),
Value::IpAddress(addr) => buf.push_ip_address(*addr),
Value::Counter32(v) => buf.push_unsigned32(tag::application::COUNTER32, *v),
Value::Gauge32(v) => buf.push_unsigned32(tag::application::GAUGE32, *v),
Value::TimeTicks(v) => buf.push_unsigned32(tag::application::TIMETICKS, *v),
Value::Opaque(data) => {
buf.push_bytes(data);
buf.push_length(data.len());
buf.push_tag(tag::application::OPAQUE);
}
Value::Counter64(v) => buf.push_integer64(*v),
Value::NoSuchObject => {
buf.push_length(0);
buf.push_tag(tag::context::NO_SUCH_OBJECT);
}
Value::NoSuchInstance => {
buf.push_length(0);
buf.push_tag(tag::context::NO_SUCH_INSTANCE);
}
Value::EndOfMibView => {
buf.push_length(0);
buf.push_tag(tag::context::END_OF_MIB_VIEW);
}
Value::Unknown { tag: t, data } => {
buf.push_bytes(data);
buf.push_length(data.len());
buf.push_tag(*t);
}
}
}
pub fn decode(decoder: &mut Decoder) -> Result<Self> {
let tag = decoder.read_tag()?;
let len = decoder.read_length()?;
match tag {
tag::universal::INTEGER => {
let value = decoder.read_integer_value(len)?;
Ok(Value::Integer(value))
}
tag::universal::OCTET_STRING => {
let available = decoder.remaining();
let len = if len > available {
tracing::warn!(
target: "async_snmp::value",
{ snmp.offset = %decoder.offset(), declared = len, available },
"OctetString length exceeds varbind SEQUENCE boundary, clamping to available bytes"
);
available
} else {
len
};
let data = decoder.read_bytes(len)?;
Ok(Value::OctetString(data))
}
tag::universal::NULL => {
if len != 0 {
tracing::debug!(target: "async_snmp::value", { offset = decoder.offset(), kind = %DecodeErrorKind::InvalidNull }, "decode error");
return Err(Error::MalformedResponse {
target: UNKNOWN_TARGET,
}
.boxed());
}
Ok(Value::Null)
}
tag::universal::OBJECT_IDENTIFIER => {
let oid = decoder.read_oid_value(len)?;
Ok(Value::ObjectIdentifier(oid))
}
tag::application::IP_ADDRESS => {
if len != 4 {
tracing::debug!(target: "async_snmp::value", { offset = decoder.offset(), length = len, kind = %DecodeErrorKind::InvalidIpAddressLength { length: len } }, "decode error");
return Err(Error::MalformedResponse {
target: UNKNOWN_TARGET,
}
.boxed());
}
let data = decoder.read_bytes(4)?;
Ok(Value::IpAddress([data[0], data[1], data[2], data[3]]))
}
tag::application::COUNTER32 => {
let value = decoder.read_unsigned32_value(len)?;
Ok(Value::Counter32(value))
}
tag::application::GAUGE32 => {
let value = decoder.read_unsigned32_value(len)?;
Ok(Value::Gauge32(value))
}
tag::application::TIMETICKS => {
let value = decoder.read_unsigned32_value(len)?;
Ok(Value::TimeTicks(value))
}
tag::application::OPAQUE => {
let available = decoder.remaining();
let len = if len > available {
tracing::warn!(
target: "async_snmp::value",
{ snmp.offset = %decoder.offset(), declared = len, available },
"Opaque length exceeds varbind SEQUENCE boundary, clamping to available bytes"
);
available
} else {
len
};
let data = decoder.read_bytes(len)?;
Ok(Value::Opaque(data))
}
tag::application::NSAP => {
let data = decoder.read_bytes(len)?;
Ok(Value::OctetString(data))
}
tag::application::COUNTER64 => {
let value = decoder.read_integer64_value(len)?;
Ok(Value::Counter64(value))
}
tag::application::UINTEGER32 => {
let value = decoder.read_unsigned32_value(len)?;
Ok(Value::Gauge32(value))
}
tag::context::NO_SUCH_OBJECT => {
if len != 0 {
let _ = decoder.read_bytes(len)?;
}
Ok(Value::NoSuchObject)
}
tag::context::NO_SUCH_INSTANCE => {
if len != 0 {
let _ = decoder.read_bytes(len)?;
}
Ok(Value::NoSuchInstance)
}
tag::context::END_OF_MIB_VIEW => {
if len != 0 {
let _ = decoder.read_bytes(len)?;
}
Ok(Value::EndOfMibView)
}
tag::universal::OCTET_STRING_CONSTRUCTED => {
tracing::debug!(target: "async_snmp::value", { offset = decoder.offset(), kind = %DecodeErrorKind::ConstructedOctetString }, "decode error");
Err(Error::MalformedResponse {
target: UNKNOWN_TARGET,
}
.boxed())
}
_ => {
let data = decoder.read_bytes(len)?;
Ok(Value::Unknown { tag, data })
}
}
}
}
impl std::fmt::Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Value::Integer(v) => write!(f, "{}", v),
Value::OctetString(data) => {
if let Ok(s) = std::str::from_utf8(data) {
write!(f, "{}", s)
} else {
write!(f, "0x{}", hex::encode(data))
}
}
Value::Null => write!(f, "NULL"),
Value::ObjectIdentifier(oid) => write!(f, "{}", oid),
Value::IpAddress(addr) => {
write!(f, "{}.{}.{}.{}", addr[0], addr[1], addr[2], addr[3])
}
Value::Counter32(v) => write!(f, "{}", v),
Value::Gauge32(v) => write!(f, "{}", v),
Value::TimeTicks(v) => {
write!(f, "{}", crate::format::format_timeticks(*v))
}
Value::Opaque(data) => write!(f, "Opaque(0x{})", hex::encode(data)),
Value::Counter64(v) => write!(f, "{}", v),
Value::NoSuchObject => write!(f, "noSuchObject"),
Value::NoSuchInstance => write!(f, "noSuchInstance"),
Value::EndOfMibView => write!(f, "endOfMibView"),
Value::Unknown { tag, data } => {
write!(
f,
"Unknown(tag=0x{:02X}, data=0x{})",
tag,
hex::encode(data)
)
}
}
}
}
impl From<i32> for Value {
fn from(v: i32) -> Self {
Value::Integer(v)
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::OctetString(Bytes::copy_from_slice(s.as_bytes()))
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Value::OctetString(Bytes::from(s))
}
}
impl From<&[u8]> for Value {
fn from(data: &[u8]) -> Self {
Value::OctetString(Bytes::copy_from_slice(data))
}
}
impl From<Oid> for Value {
fn from(oid: Oid) -> Self {
Value::ObjectIdentifier(oid)
}
}
impl From<std::net::Ipv4Addr> for Value {
fn from(addr: std::net::Ipv4Addr) -> Self {
Value::IpAddress(addr.octets())
}
}
impl From<Bytes> for Value {
fn from(data: Bytes) -> Self {
Value::OctetString(data)
}
}
impl From<u64> for Value {
fn from(v: u64) -> Self {
Value::Counter64(v)
}
}
impl From<[u8; 4]> for Value {
fn from(addr: [u8; 4]) -> Self {
Value::IpAddress(addr)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reject_constructed_octet_string() {
let data = bytes::Bytes::from_static(&[0x24, 0x03, 0x04, 0x01, 0x41]);
let mut decoder = Decoder::new(data);
let result = Value::decode(&mut decoder);
assert!(
result.is_err(),
"constructed OCTET STRING (0x24) should be rejected"
);
let err = result.unwrap_err();
assert!(
matches!(&*err, crate::Error::MalformedResponse { .. }),
"expected MalformedResponse error, got: {:?}",
err
);
}
#[test]
fn test_primitive_octet_string_accepted() {
let data = bytes::Bytes::from_static(&[0x04, 0x03, 0x41, 0x42, 0x43]); let mut decoder = Decoder::new(data);
let result = Value::decode(&mut decoder);
assert!(result.is_ok(), "primitive OCTET STRING should be accepted");
let value = result.unwrap();
assert_eq!(value.as_bytes(), Some(&b"ABC"[..]));
}
fn roundtrip(value: Value) -> Value {
let mut buf = EncodeBuf::new();
value.encode(&mut buf);
let data = buf.finish();
let mut decoder = Decoder::new(data);
Value::decode(&mut decoder).unwrap()
}
#[test]
fn test_integer_positive() {
let value = Value::Integer(42);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_integer_negative() {
let value = Value::Integer(-42);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_integer_zero() {
let value = Value::Integer(0);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_integer_min() {
let value = Value::Integer(i32::MIN);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_integer_max() {
let value = Value::Integer(i32::MAX);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_octet_string_ascii() {
let value = Value::OctetString(Bytes::from_static(b"hello world"));
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_octet_string_binary() {
let value = Value::OctetString(Bytes::from_static(&[0x00, 0xFF, 0x80, 0x7F]));
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_octet_string_empty() {
let value = Value::OctetString(Bytes::new());
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_null() {
let value = Value::Null;
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_object_identifier() {
let value = Value::ObjectIdentifier(crate::oid!(1, 3, 6, 1, 2, 1, 1, 1, 0));
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_ip_address() {
let value = Value::IpAddress([192, 168, 1, 1]);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_ip_address_zero() {
let value = Value::IpAddress([0, 0, 0, 0]);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_ip_address_broadcast() {
let value = Value::IpAddress([255, 255, 255, 255]);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_counter32() {
let value = Value::Counter32(999999);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_counter32_zero() {
let value = Value::Counter32(0);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_counter32_max() {
let value = Value::Counter32(u32::MAX);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_gauge32() {
let value = Value::Gauge32(1000000000);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_gauge32_max() {
let value = Value::Gauge32(u32::MAX);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_timeticks() {
let value = Value::TimeTicks(123456);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_timeticks_max() {
let value = Value::TimeTicks(u32::MAX);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_opaque() {
let value = Value::Opaque(Bytes::from_static(&[0xDE, 0xAD, 0xBE, 0xEF]));
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_opaque_empty() {
let value = Value::Opaque(Bytes::new());
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_counter64() {
let value = Value::Counter64(123456789012345);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_counter64_zero() {
let value = Value::Counter64(0);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_counter64_max() {
let value = Value::Counter64(u64::MAX);
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_no_such_object() {
let value = Value::NoSuchObject;
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_no_such_instance() {
let value = Value::NoSuchInstance;
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_end_of_mib_view() {
let value = Value::EndOfMibView;
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_unknown_tag_preserved() {
let data = Bytes::from_static(&[0x48, 0x03, 0x01, 0x02, 0x03]);
let mut decoder = Decoder::new(data);
let value = Value::decode(&mut decoder).unwrap();
match value {
Value::Unknown { tag, ref data } => {
assert_eq!(tag, 0x48);
assert_eq!(data.as_ref(), &[0x01, 0x02, 0x03]);
}
_ => panic!("expected Unknown variant"),
}
assert_eq!(roundtrip(value.clone()), value);
}
#[test]
fn test_nsap_decodes_as_octet_string() {
let data = Bytes::from_static(&[0x45, 0x03, 0x01, 0x02, 0x03]);
let mut decoder = Decoder::new(data);
let value = Value::decode(&mut decoder).unwrap();
assert_eq!(
value,
Value::OctetString(Bytes::from_static(&[0x01, 0x02, 0x03]))
);
}
#[test]
fn test_uinteger32_decodes_as_gauge32() {
let data = Bytes::from_static(&[0x47, 0x01, 0x2a]);
let mut decoder = Decoder::new(data);
let value = Value::decode(&mut decoder).unwrap();
assert_eq!(value, Value::Gauge32(42));
}
#[test]
fn test_as_i32() {
assert_eq!(Value::Integer(42).as_i32(), Some(42));
assert_eq!(Value::Integer(-42).as_i32(), Some(-42));
assert_eq!(Value::Counter32(100).as_i32(), None);
assert_eq!(Value::Null.as_i32(), None);
}
#[test]
fn test_as_u32() {
assert_eq!(Value::Counter32(100).as_u32(), Some(100));
assert_eq!(Value::Gauge32(200).as_u32(), Some(200));
assert_eq!(Value::TimeTicks(300).as_u32(), Some(300));
assert_eq!(Value::Integer(50).as_u32(), Some(50));
assert_eq!(Value::Integer(-1).as_u32(), None);
assert_eq!(Value::Counter64(100).as_u32(), None);
}
#[test]
fn test_as_u64() {
assert_eq!(Value::Counter64(100).as_u64(), Some(100));
assert_eq!(Value::Counter32(100).as_u64(), Some(100));
assert_eq!(Value::Gauge32(200).as_u64(), Some(200));
assert_eq!(Value::TimeTicks(300).as_u64(), Some(300));
assert_eq!(Value::Integer(50).as_u64(), Some(50));
assert_eq!(Value::Integer(-1).as_u64(), None);
}
#[test]
fn test_as_bytes() {
let s = Value::OctetString(Bytes::from_static(b"test"));
assert_eq!(s.as_bytes(), Some(b"test".as_slice()));
let o = Value::Opaque(Bytes::from_static(b"data"));
assert_eq!(o.as_bytes(), Some(b"data".as_slice()));
assert_eq!(Value::Integer(1).as_bytes(), None);
}
#[test]
fn test_as_str() {
let s = Value::OctetString(Bytes::from_static(b"hello"));
assert_eq!(s.as_str(), Some("hello"));
let invalid = Value::OctetString(Bytes::from_static(&[0xFF, 0xFE]));
assert_eq!(invalid.as_str(), None);
assert_eq!(Value::Integer(1).as_str(), None);
}
#[test]
fn test_as_oid() {
let oid = crate::oid!(1, 3, 6, 1);
let v = Value::ObjectIdentifier(oid.clone());
assert_eq!(v.as_oid(), Some(&oid));
assert_eq!(Value::Integer(1).as_oid(), None);
}
#[test]
fn test_as_ip() {
let v = Value::IpAddress([192, 168, 1, 1]);
assert_eq!(v.as_ip(), Some(std::net::Ipv4Addr::new(192, 168, 1, 1)));
assert_eq!(Value::Integer(1).as_ip(), None);
}
#[test]
fn test_is_exception() {
assert!(Value::NoSuchObject.is_exception());
assert!(Value::NoSuchInstance.is_exception());
assert!(Value::EndOfMibView.is_exception());
assert!(!Value::Integer(1).is_exception());
assert!(!Value::Null.is_exception());
assert!(!Value::OctetString(Bytes::new()).is_exception());
}
#[test]
fn test_display_integer() {
assert_eq!(format!("{}", Value::Integer(42)), "42");
assert_eq!(format!("{}", Value::Integer(-42)), "-42");
}
#[test]
fn test_display_octet_string_utf8() {
let v = Value::OctetString(Bytes::from_static(b"hello"));
assert_eq!(format!("{}", v), "hello");
}
#[test]
fn test_display_octet_string_binary() {
let v = Value::OctetString(Bytes::from_static(&[0xFF, 0xFE]));
assert_eq!(format!("{}", v), "0xfffe");
}
#[test]
fn test_display_null() {
assert_eq!(format!("{}", Value::Null), "NULL");
}
#[test]
fn test_display_ip_address() {
let v = Value::IpAddress([192, 168, 1, 1]);
assert_eq!(format!("{}", v), "192.168.1.1");
}
#[test]
fn test_display_counter32() {
assert_eq!(format!("{}", Value::Counter32(999)), "999");
}
#[test]
fn test_display_gauge32() {
assert_eq!(format!("{}", Value::Gauge32(1000)), "1000");
}
#[test]
fn test_display_timeticks() {
let v = Value::TimeTicks(123456);
assert_eq!(format!("{}", v), "00:20:34.56");
}
#[test]
fn test_display_opaque() {
let v = Value::Opaque(Bytes::from_static(&[0xBE, 0xEF]));
assert_eq!(format!("{}", v), "Opaque(0xbeef)");
}
#[test]
fn test_display_counter64() {
assert_eq!(format!("{}", Value::Counter64(12345678)), "12345678");
}
#[test]
fn test_display_exceptions() {
assert_eq!(format!("{}", Value::NoSuchObject), "noSuchObject");
assert_eq!(format!("{}", Value::NoSuchInstance), "noSuchInstance");
assert_eq!(format!("{}", Value::EndOfMibView), "endOfMibView");
}
#[test]
fn test_display_unknown() {
let v = Value::Unknown {
tag: 0x99,
data: Bytes::from_static(&[0x01, 0x02]),
};
assert_eq!(format!("{}", v), "Unknown(tag=0x99, data=0x0102)");
}
#[test]
fn test_from_i32() {
let v: Value = 42i32.into();
assert_eq!(v, Value::Integer(42));
}
#[test]
fn test_from_str() {
let v: Value = "hello".into();
assert_eq!(v.as_str(), Some("hello"));
}
#[test]
fn test_from_string() {
let v: Value = String::from("hello").into();
assert_eq!(v.as_str(), Some("hello"));
}
#[test]
fn test_from_bytes_slice() {
let v: Value = (&[1u8, 2, 3][..]).into();
assert_eq!(v.as_bytes(), Some(&[1u8, 2, 3][..]));
}
#[test]
fn test_from_oid() {
let oid = crate::oid!(1, 3, 6, 1);
let v: Value = oid.clone().into();
assert_eq!(v.as_oid(), Some(&oid));
}
#[test]
fn test_from_ipv4addr() {
let addr = std::net::Ipv4Addr::new(10, 0, 0, 1);
let v: Value = addr.into();
assert_eq!(v, Value::IpAddress([10, 0, 0, 1]));
}
#[test]
fn test_from_bytes() {
let data = Bytes::from_static(b"hello");
let v: Value = data.into();
assert_eq!(v.as_bytes(), Some(b"hello".as_slice()));
}
#[test]
fn test_from_u64() {
let v: Value = 12345678901234u64.into();
assert_eq!(v, Value::Counter64(12345678901234));
}
#[test]
fn test_from_ip_array() {
let v: Value = [192u8, 168, 1, 1].into();
assert_eq!(v, Value::IpAddress([192, 168, 1, 1]));
}
#[test]
fn test_value_eq_and_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(Value::Integer(42));
set.insert(Value::Integer(42)); set.insert(Value::Integer(100));
assert_eq!(set.len(), 2);
assert!(set.contains(&Value::Integer(42)));
assert!(set.contains(&Value::Integer(100)));
}
#[test]
fn test_decode_invalid_null_length() {
let data = Bytes::from_static(&[0x05, 0x01, 0x00]); let mut decoder = Decoder::new(data);
let result = Value::decode(&mut decoder);
assert!(result.is_err());
}
#[test]
fn test_decode_invalid_ip_address_length() {
let data = Bytes::from_static(&[0x40, 0x03, 0x01, 0x02, 0x03]); let mut decoder = Decoder::new(data);
let result = Value::decode(&mut decoder);
assert!(result.is_err());
}
#[test]
fn test_decode_exception_with_content_accepted() {
let data = Bytes::from_static(&[0x80, 0x01, 0xFF]); let mut decoder = Decoder::new(data);
let result = Value::decode(&mut decoder);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::NoSuchObject);
}
#[test]
fn test_as_f64() {
assert_eq!(Value::Integer(42).as_f64(), Some(42.0));
assert_eq!(Value::Integer(-42).as_f64(), Some(-42.0));
assert_eq!(Value::Counter32(1000).as_f64(), Some(1000.0));
assert_eq!(Value::Gauge32(2000).as_f64(), Some(2000.0));
assert_eq!(Value::TimeTicks(3000).as_f64(), Some(3000.0));
assert_eq!(
Value::Counter64(10_000_000_000).as_f64(),
Some(10_000_000_000.0)
);
assert_eq!(Value::Null.as_f64(), None);
assert_eq!(
Value::OctetString(Bytes::from_static(b"test")).as_f64(),
None
);
}
#[test]
fn test_as_f64_wrapped() {
assert_eq!(Value::Counter64(1000).as_f64_wrapped(), Some(1000.0));
assert_eq!(Value::Counter32(1000).as_f64_wrapped(), Some(1000.0));
assert_eq!(Value::Integer(42).as_f64_wrapped(), Some(42.0));
let mantissa_limit = 1u64 << 53;
assert_eq!(Value::Counter64(mantissa_limit).as_f64_wrapped(), Some(0.0));
assert_eq!(
Value::Counter64(mantissa_limit + 1).as_f64_wrapped(),
Some(1.0)
);
}
#[test]
fn test_as_decimal() {
assert_eq!(Value::Integer(2350).as_decimal(2), Some(23.50));
assert_eq!(Value::Integer(9999).as_decimal(2), Some(99.99));
assert_eq!(Value::Integer(12500).as_decimal(3), Some(12.5));
assert_eq!(Value::Integer(-500).as_decimal(2), Some(-5.0));
assert_eq!(Value::Counter32(1000).as_decimal(1), Some(100.0));
assert_eq!(Value::Null.as_decimal(2), None);
}
#[test]
fn test_as_duration() {
use std::time::Duration;
assert_eq!(
Value::TimeTicks(100).as_duration(),
Some(Duration::from_secs(1))
);
assert_eq!(
Value::TimeTicks(360000).as_duration(),
Some(Duration::from_secs(3600))
);
assert_eq!(
Value::TimeTicks(1).as_duration(),
Some(Duration::from_millis(10))
);
assert_eq!(Value::Integer(100).as_duration(), None);
assert_eq!(Value::Counter32(100).as_duration(), None);
}
#[test]
fn test_as_opaque_float() {
let data = Bytes::from_static(&[0x9f, 0x78, 0x04, 0x40, 0x49, 0x0f, 0xdb]);
let value = Value::Opaque(data);
let pi = value.as_opaque_float().unwrap();
assert!((pi - std::f32::consts::PI).abs() < 0.0001);
assert_eq!(Value::Integer(42).as_opaque_float(), None);
let wrong_type = Bytes::from_static(&[0x9f, 0x79, 0x04, 0x40, 0x49, 0x0f, 0xdb]);
assert_eq!(Value::Opaque(wrong_type).as_opaque_float(), None);
let short = Bytes::from_static(&[0x9f, 0x78, 0x04, 0x40, 0x49]);
assert_eq!(Value::Opaque(short).as_opaque_float(), None);
}
#[test]
fn test_as_opaque_double() {
let data = Bytes::from_static(&[
0x9f, 0x79, 0x08, 0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18,
]);
let value = Value::Opaque(data);
let pi = value.as_opaque_double().unwrap();
assert!((pi - std::f64::consts::PI).abs() < 1e-10);
assert_eq!(Value::Integer(42).as_opaque_double(), None);
}
#[test]
fn test_as_opaque_counter64() {
let data = Bytes::from_static(&[
0x9f, 0x76, 0x08, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
]);
let value = Value::Opaque(data);
assert_eq!(value.as_opaque_counter64(), Some(0x0123456789ABCDEF));
let small = Bytes::from_static(&[0x9f, 0x76, 0x01, 0x42]);
assert_eq!(Value::Opaque(small).as_opaque_counter64(), Some(0x42));
let zero = Bytes::from_static(&[0x9f, 0x76, 0x01, 0x00]);
assert_eq!(Value::Opaque(zero).as_opaque_counter64(), Some(0));
}
#[test]
fn test_as_opaque_i64() {
let positive = Bytes::from_static(&[0x9f, 0x7a, 0x02, 0x01, 0x00]);
assert_eq!(Value::Opaque(positive).as_opaque_i64(), Some(256));
let minus_one = Bytes::from_static(&[0x9f, 0x7a, 0x01, 0xFF]);
assert_eq!(Value::Opaque(minus_one).as_opaque_i64(), Some(-1));
let full_neg = Bytes::from_static(&[
0x9f, 0x7a, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
]);
assert_eq!(Value::Opaque(full_neg).as_opaque_i64(), Some(-1));
let min = Bytes::from_static(&[
0x9f, 0x7a, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
assert_eq!(Value::Opaque(min).as_opaque_i64(), Some(i64::MIN));
}
#[test]
fn test_as_opaque_u64() {
let data = Bytes::from_static(&[
0x9f, 0x7b, 0x08, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
]);
let value = Value::Opaque(data);
assert_eq!(value.as_opaque_u64(), Some(0x0123456789ABCDEF));
let max = Bytes::from_static(&[
0x9f, 0x7b, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
]);
assert_eq!(Value::Opaque(max).as_opaque_u64(), Some(u64::MAX));
}
#[test]
fn test_format_with_hint_integer() {
assert_eq!(
Value::Integer(2350).format_with_hint("d-2"),
Some("23.50".into())
);
assert_eq!(
Value::Integer(-500).format_with_hint("d-2"),
Some("-5.00".into())
);
assert_eq!(Value::Integer(255).format_with_hint("x"), Some("ff".into()));
assert_eq!(Value::Integer(8).format_with_hint("o"), Some("10".into()));
assert_eq!(Value::Integer(5).format_with_hint("b"), Some("101".into()));
assert_eq!(Value::Integer(42).format_with_hint("d"), Some("42".into()));
assert_eq!(Value::Integer(42).format_with_hint("invalid"), None);
assert_eq!(Value::Counter32(42).format_with_hint("d-2"), None);
}
#[test]
fn test_as_truth_value() {
assert_eq!(Value::Integer(1).as_truth_value(), Some(true));
assert_eq!(Value::Integer(2).as_truth_value(), Some(false));
assert_eq!(Value::Integer(0).as_truth_value(), None);
assert_eq!(Value::Integer(3).as_truth_value(), None);
assert_eq!(Value::Integer(-1).as_truth_value(), None);
assert_eq!(Value::Null.as_truth_value(), None);
assert_eq!(Value::Counter32(1).as_truth_value(), None);
assert_eq!(Value::Gauge32(1).as_truth_value(), None);
}
#[test]
fn test_row_status_from_i32() {
assert_eq!(RowStatus::from_i32(1), Some(RowStatus::Active));
assert_eq!(RowStatus::from_i32(2), Some(RowStatus::NotInService));
assert_eq!(RowStatus::from_i32(3), Some(RowStatus::NotReady));
assert_eq!(RowStatus::from_i32(4), Some(RowStatus::CreateAndGo));
assert_eq!(RowStatus::from_i32(5), Some(RowStatus::CreateAndWait));
assert_eq!(RowStatus::from_i32(6), Some(RowStatus::Destroy));
assert_eq!(RowStatus::from_i32(0), None);
assert_eq!(RowStatus::from_i32(7), None);
assert_eq!(RowStatus::from_i32(-1), None);
}
#[test]
fn test_row_status_try_from() {
assert_eq!(RowStatus::try_from(1), Ok(RowStatus::Active));
assert_eq!(RowStatus::try_from(6), Ok(RowStatus::Destroy));
assert_eq!(RowStatus::try_from(0), Err(0));
assert_eq!(RowStatus::try_from(7), Err(7));
assert_eq!(RowStatus::try_from(-1), Err(-1));
}
#[test]
fn test_row_status_into_value() {
let v: Value = RowStatus::Active.into();
assert_eq!(v, Value::Integer(1));
let v: Value = RowStatus::Destroy.into();
assert_eq!(v, Value::Integer(6));
}
#[test]
fn test_row_status_display() {
assert_eq!(format!("{}", RowStatus::Active), "active");
assert_eq!(format!("{}", RowStatus::NotInService), "notInService");
assert_eq!(format!("{}", RowStatus::NotReady), "notReady");
assert_eq!(format!("{}", RowStatus::CreateAndGo), "createAndGo");
assert_eq!(format!("{}", RowStatus::CreateAndWait), "createAndWait");
assert_eq!(format!("{}", RowStatus::Destroy), "destroy");
}
#[test]
fn test_as_row_status() {
assert_eq!(Value::Integer(1).as_row_status(), Some(RowStatus::Active));
assert_eq!(Value::Integer(6).as_row_status(), Some(RowStatus::Destroy));
assert_eq!(Value::Integer(0).as_row_status(), None);
assert_eq!(Value::Integer(7).as_row_status(), None);
assert_eq!(Value::Null.as_row_status(), None);
assert_eq!(Value::Counter32(1).as_row_status(), None);
}
#[test]
fn test_storage_type_from_i32() {
assert_eq!(StorageType::from_i32(1), Some(StorageType::Other));
assert_eq!(StorageType::from_i32(2), Some(StorageType::Volatile));
assert_eq!(StorageType::from_i32(3), Some(StorageType::NonVolatile));
assert_eq!(StorageType::from_i32(4), Some(StorageType::Permanent));
assert_eq!(StorageType::from_i32(5), Some(StorageType::ReadOnly));
assert_eq!(StorageType::from_i32(0), None);
assert_eq!(StorageType::from_i32(6), None);
assert_eq!(StorageType::from_i32(-1), None);
}
#[test]
fn test_storage_type_try_from() {
assert_eq!(StorageType::try_from(1), Ok(StorageType::Other));
assert_eq!(StorageType::try_from(5), Ok(StorageType::ReadOnly));
assert_eq!(StorageType::try_from(0), Err(0));
assert_eq!(StorageType::try_from(6), Err(6));
assert_eq!(StorageType::try_from(-1), Err(-1));
}
#[test]
fn test_storage_type_into_value() {
let v: Value = StorageType::Volatile.into();
assert_eq!(v, Value::Integer(2));
let v: Value = StorageType::NonVolatile.into();
assert_eq!(v, Value::Integer(3));
}
#[test]
fn test_storage_type_display() {
assert_eq!(format!("{}", StorageType::Other), "other");
assert_eq!(format!("{}", StorageType::Volatile), "volatile");
assert_eq!(format!("{}", StorageType::NonVolatile), "nonVolatile");
assert_eq!(format!("{}", StorageType::Permanent), "permanent");
assert_eq!(format!("{}", StorageType::ReadOnly), "readOnly");
}
#[test]
fn test_as_storage_type() {
assert_eq!(
Value::Integer(2).as_storage_type(),
Some(StorageType::Volatile)
);
assert_eq!(
Value::Integer(3).as_storage_type(),
Some(StorageType::NonVolatile)
);
assert_eq!(Value::Integer(0).as_storage_type(), None);
assert_eq!(Value::Integer(6).as_storage_type(), None);
assert_eq!(Value::Null.as_storage_type(), None);
assert_eq!(Value::Counter32(1).as_storage_type(), None);
}
}