use crate::ber::{Decoder, EncodeBuf, tag};
use crate::error::internal::DecodeErrorKind;
use crate::error::{Error, Result};
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 {
#[must_use]
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 {
#[must_use]
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, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum ValueKind {
Integer,
OctetString,
Null,
ObjectIdentifier,
IpAddress,
Counter32,
Gauge32,
UInteger32,
TimeTicks,
Opaque,
Nsap,
Counter64,
NoSuchObject,
NoSuchInstance,
EndOfMibView,
Unknown,
}
impl ValueKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Integer => "integer",
Self::OctetString => "octet_string",
Self::Null => "null",
Self::ObjectIdentifier => "object_identifier",
Self::IpAddress => "ip_address",
Self::Counter32 => "counter32",
Self::Gauge32 => "gauge32",
Self::UInteger32 => "uinteger32",
Self::TimeTicks => "time_ticks",
Self::Opaque => "opaque",
Self::Nsap => "nsap",
Self::Counter64 => "counter64",
Self::NoSuchObject => "no_such_object",
Self::NoSuchInstance => "no_such_instance",
Self::EndOfMibView => "end_of_mib_view",
Self::Unknown => "unknown",
}
}
}
impl std::fmt::Display for ValueKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[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),
UInteger32(u32),
TimeTicks(u32),
Opaque(Bytes),
Nsap(Bytes),
Counter64(u64),
NoSuchObject,
NoSuchInstance,
EndOfMibView,
Unknown { tag: u8, data: Bytes },
}
impl Value {
#[must_use]
pub fn kind(&self) -> ValueKind {
match self {
Self::Integer(_) => ValueKind::Integer,
Self::OctetString(_) => ValueKind::OctetString,
Self::Null => ValueKind::Null,
Self::ObjectIdentifier(_) => ValueKind::ObjectIdentifier,
Self::IpAddress(_) => ValueKind::IpAddress,
Self::Counter32(_) => ValueKind::Counter32,
Self::Gauge32(_) => ValueKind::Gauge32,
Self::UInteger32(_) => ValueKind::UInteger32,
Self::TimeTicks(_) => ValueKind::TimeTicks,
Self::Opaque(_) => ValueKind::Opaque,
Self::Nsap(_) => ValueKind::Nsap,
Self::Counter64(_) => ValueKind::Counter64,
Self::NoSuchObject => ValueKind::NoSuchObject,
Self::NoSuchInstance => ValueKind::NoSuchInstance,
Self::EndOfMibView => ValueKind::EndOfMibView,
Self::Unknown { .. } => ValueKind::Unknown,
}
}
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::UInteger32(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::UInteger32(v)
| Value::TimeTicks(v) => Some(u64::from(*v)),
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) | Value::Nsap(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,
}
}
#[expect(
clippy::cast_precision_loss,
reason = "we intentionally bend over backwards to make an f64 available even for 64-bit ints"
)]
pub fn as_f64(&self) -> Option<f64> {
match self {
Value::Integer(v) => Some(f64::from(*v)),
Value::Counter32(v)
| Value::Gauge32(v)
| Value::UInteger32(v)
| Value::TimeTicks(v) => Some(f64::from(*v)),
Value::Counter64(v) => Some(*v as f64),
_ => None,
}
}
#[expect(
clippy::cast_precision_loss,
reason = "we intentionally bend over backwards to make an f64 available even for 64-bit ints"
)]
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(i32::from(places));
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(u64::from(*v) * 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) | i64::from(byte);
}
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) | u64::from(byte);
}
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)
| Value::Opaque(data)
| Value::Nsap(data)
| Value::Unknown { data, .. } => {
let content_len = data.len();
1 + length_encoded_len(content_len) + content_len
}
Value::Null | Value::NoSuchObject | Value::NoSuchInstance | Value::EndOfMibView => 2, Value::ObjectIdentifier(oid) => oid.ber_encoded_size(),
Value::IpAddress(_) => 6, Value::Counter32(v)
| Value::Gauge32(v)
| Value::UInteger32(v)
| Value::TimeTicks(v) => {
let content_len = unsigned32_content_len(*v);
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
}
}
}
pub fn format_with_hint(&self, hint: &str) -> Option<String> {
match self {
Value::OctetString(bytes) | Value::Opaque(bytes) | Value::Nsap(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) -> Result<()> {
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::UInteger32(_) => {
return Err(Error::InvalidMessage(
"UInteger32 is a receive-only value type".into(),
)
.boxed());
}
Value::TimeTicks(v) => buf.push_unsigned32(tag::application::TIMETICKS, *v),
Value::Opaque(data) => {
crate::ber::encode_length(data.len())?;
buf.push_bytes(data);
buf.push_length(data.len())?;
buf.push_tag(tag::application::OPAQUE);
}
Value::Nsap(_) => {
return Err(
Error::InvalidMessage("Nsap is a receive-only value type".into()).boxed(),
);
}
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 { .. } => {
return Err(Error::InvalidMessage(
"Value::Unknown cannot be encoded by structured encoders".into(),
)
.boxed());
}
}
Ok(())
}
fn decode_exception(
decoder: &mut Decoder,
len: usize,
exception_type: &'static str,
exception_kind: crate::compatibility::ExceptionKind,
value: Self,
) -> Result<Self> {
if len == 0 {
return Ok(value);
}
if !decoder.decode_config().malformed_exception_payloads {
return Err(decoder.malformed(DecodeErrorKind::InvalidValue));
}
let _ = decoder.read_bytes(len)?;
tracing::warn!(target: "async_snmp::value", anomaly = "malformed_exception_payload", exception_type, payload_length = len, "discarded non-empty exception payload");
decoder.record_anomaly(
crate::compatibility::DecodeAnomaly::MalformedExceptionPayload {
kind: exception_kind,
original_length: len,
canonical_length: 0,
},
);
Ok(value)
}
pub fn decode(decoder: &mut Decoder) -> Result<Self> {
let tag_offset = decoder.local_offset();
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 {
if !decoder.decode_config().clamp_bounded_strings {
return Err(decoder.malformed(DecodeErrorKind::InsufficientData {
needed: len,
available,
}));
}
tracing::warn!(target: "async_snmp::value", anomaly = "bounded_string_clamp", value_type = "octet_string", snmp.offset = decoder.offset(), declared_length = len, available_length = available, "clamped value length to enclosing varbind boundary");
decoder.record_anomaly(
crate::compatibility::DecodeAnomaly::BoundedStringClamp {
kind: crate::compatibility::BoundedStringKind::OctetString,
declared_length: len,
canonical_length: available,
},
);
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(decoder.malformed(DecodeErrorKind::InvalidNull));
}
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(
decoder.malformed(DecodeErrorKind::InvalidIpAddressLength { length: len })
);
}
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 {
if !decoder.decode_config().clamp_bounded_strings {
return Err(decoder.malformed(DecodeErrorKind::InsufficientData {
needed: len,
available,
}));
}
tracing::warn!(target: "async_snmp::value", anomaly = "bounded_string_clamp", value_type = "opaque", snmp.offset = decoder.offset(), declared_length = len, available_length = available, "clamped value length to enclosing varbind boundary");
decoder.record_anomaly(
crate::compatibility::DecodeAnomaly::BoundedStringClamp {
kind: crate::compatibility::BoundedStringKind::Opaque,
declared_length: len,
canonical_length: available,
},
);
available
} else {
len
};
let data = decoder.read_bytes(len)?;
Ok(Value::Opaque(data))
}
tag::application::NSAP => {
let data = decoder.read_bytes(len)?;
Ok(Value::Nsap(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::UInteger32(value))
}
tag::context::NO_SUCH_OBJECT => Self::decode_exception(
decoder,
len,
"no_such_object",
crate::compatibility::ExceptionKind::NoSuchObject,
Value::NoSuchObject,
),
tag::context::NO_SUCH_INSTANCE => Self::decode_exception(
decoder,
len,
"no_such_instance",
crate::compatibility::ExceptionKind::NoSuchInstance,
Value::NoSuchInstance,
),
tag::context::END_OF_MIB_VIEW => Self::decode_exception(
decoder,
len,
"end_of_mib_view",
crate::compatibility::ExceptionKind::EndOfMibView,
Value::EndOfMibView,
),
tag::universal::OCTET_STRING_CONSTRUCTED => {
tracing::debug!(target: "async_snmp::value", { offset = decoder.offset(), kind = %DecodeErrorKind::ConstructedOctetString }, "decode error");
Err(decoder.malformed_at(tag_offset, DecodeErrorKind::ConstructedOctetString))
}
_ => {
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::UInteger32(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::Nsap(data) => write!(f, "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 value_kind_exhaustively_maps_variants_and_canonical_names() {
let cases = [
(Value::Integer(-1), ValueKind::Integer, "integer"),
(
Value::OctetString(Bytes::from_static(b"text")),
ValueKind::OctetString,
"octet_string",
),
(Value::Null, ValueKind::Null, "null"),
(
Value::ObjectIdentifier(Oid::from_slice(&[1, 3, 6, 1])),
ValueKind::ObjectIdentifier,
"object_identifier",
),
(
Value::IpAddress([192, 0, 2, 1]),
ValueKind::IpAddress,
"ip_address",
),
(Value::Counter32(1), ValueKind::Counter32, "counter32"),
(Value::Gauge32(2), ValueKind::Gauge32, "gauge32"),
(Value::UInteger32(3), ValueKind::UInteger32, "uinteger32"),
(Value::TimeTicks(4), ValueKind::TimeTicks, "time_ticks"),
(
Value::Opaque(Bytes::from_static(b"opaque")),
ValueKind::Opaque,
"opaque",
),
(
Value::Nsap(Bytes::from_static(b"nsap")),
ValueKind::Nsap,
"nsap",
),
(Value::Counter64(5), ValueKind::Counter64, "counter64"),
(
Value::NoSuchObject,
ValueKind::NoSuchObject,
"no_such_object",
),
(
Value::NoSuchInstance,
ValueKind::NoSuchInstance,
"no_such_instance",
),
(
Value::EndOfMibView,
ValueKind::EndOfMibView,
"end_of_mib_view",
),
(
Value::Unknown {
tag: 0x1f,
data: Bytes::from_static(b"unknown"),
},
ValueKind::Unknown,
"unknown",
),
];
for (value, expected_kind, expected_name) in cases {
let kind = value.kind();
assert_eq!(kind, expected_kind);
assert_eq!(kind.as_str(), expected_name);
assert_eq!(kind.to_string(), expected_name);
}
}
#[test]
fn unknown_value_kind_does_not_depend_on_tag_or_data() {
let first = Value::Unknown {
tag: 0,
data: Bytes::new(),
};
let second = Value::Unknown {
tag: u8::MAX,
data: Bytes::from_static(b"payload"),
};
assert_eq!(first.kind(), ValueKind::Unknown);
assert_eq!(second.kind(), ValueKind::Unknown);
}
#[test]
fn value_kind_names_are_const() {
const NAME: &str = ValueKind::Null.as_str();
assert_eq!(NAME, "null");
}
#[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::Decode(error) if error.kind == DecodeErrorKind::ConstructedOctetString),
"expected Decode error, got: {err:?}"
);
}
#[test]
fn malformed_value_errors_retain_decoder_target() {
let peer = "192.0.2.44:161".parse().unwrap();
let malformed_values = [
Bytes::from_static(&[0x05, 0x01, 0x00]),
Bytes::from_static(&[0x40, 0x03, 0x01, 0x02, 0x03]),
Bytes::from_static(&[0x24, 0x00]),
];
for value in malformed_values {
let mut decoder = Decoder::with_target(value, peer);
let error = Value::decode(&mut decoder).unwrap_err();
assert!(matches!(&*error, crate::Error::Decode(error) if error.peer == Some(peer)));
}
}
#[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).unwrap();
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), value);
}
#[test]
fn test_integer_negative() {
let value = Value::Integer(-42);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_integer_zero() {
let value = Value::Integer(0);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_integer_min() {
let value = Value::Integer(i32::MIN);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_integer_max() {
let value = Value::Integer(i32::MAX);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_octet_string_ascii() {
let value = Value::OctetString(Bytes::from_static(b"hello world"));
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_octet_string_binary() {
let value = Value::OctetString(Bytes::from_static(&[0x00, 0xFF, 0x80, 0x7F]));
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_octet_string_empty() {
let value = Value::OctetString(Bytes::new());
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_null() {
let value = Value::Null;
assert_eq!(roundtrip(&value), 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), value);
}
#[test]
fn test_ip_address() {
let value = Value::IpAddress([192, 168, 1, 1]);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_ip_address_zero() {
let value = Value::IpAddress([0, 0, 0, 0]);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_ip_address_broadcast() {
let value = Value::IpAddress([255, 255, 255, 255]);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_counter32() {
let value = Value::Counter32(999_999);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_counter32_zero() {
let value = Value::Counter32(0);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_counter32_max() {
let value = Value::Counter32(u32::MAX);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_gauge32() {
let value = Value::Gauge32(1_000_000_000);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_gauge32_max() {
let value = Value::Gauge32(u32::MAX);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_timeticks() {
let value = Value::TimeTicks(123_456);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_timeticks_max() {
let value = Value::TimeTicks(u32::MAX);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_opaque() {
let value = Value::Opaque(Bytes::from_static(&[0xDE, 0xAD, 0xBE, 0xEF]));
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_opaque_empty() {
let value = Value::Opaque(Bytes::new());
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_counter64() {
let value = Value::Counter64(123_456_789_012_345);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_counter64_zero() {
let value = Value::Counter64(0);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_counter64_max() {
let value = Value::Counter64(u64::MAX);
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_no_such_object() {
let value = Value::NoSuchObject;
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_no_such_instance() {
let value = Value::NoSuchInstance;
assert_eq!(roundtrip(&value), value);
}
#[test]
fn test_end_of_mib_view() {
let value = Value::EndOfMibView;
assert_eq!(roundtrip(&value), 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"),
}
let mut buf = EncodeBuf::new();
assert!(matches!(
&*value.encode(&mut buf).unwrap_err(),
Error::InvalidMessage(_)
));
assert!(buf.is_empty());
}
#[test]
fn test_nsap_decodes_as_nsap() {
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::Nsap(Bytes::from_static(&[0x01, 0x02, 0x03])));
}
#[test]
fn test_uinteger32_decodes_as_uinteger32() {
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::UInteger32(42));
}
#[test]
fn historic_values_decode_but_structured_encode_rejects_them() {
for bytes in [
Bytes::from_static(&[0x45, 0x03, 0x01, 0x02, 0x03]),
Bytes::from_static(&[0x47, 0x01, 0x2a]),
] {
let mut decoder = Decoder::new(bytes);
let value = Value::decode(&mut decoder).unwrap();
let original = value.clone();
let mut buf = EncodeBuf::new();
assert!(value.encode(&mut buf).is_err());
assert!(buf.is_empty());
assert_eq!(value, original);
}
}
#[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(123_456);
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_nsap_always_hex() {
let v = Value::Nsap(Bytes::from_static(b"90abc"));
assert_eq!(format!("{v}"), "0x3930616263");
}
#[test]
fn test_display_counter64() {
assert_eq!(format!("{}", Value::Counter64(1234_5678)), "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 = 12_345_678_901_234_u64.into();
assert_eq!(v, Value::Counter64(12_345_678_901_234));
}
#[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 malformed_exception_payload_policy_is_independent_and_defaults_strict() {
let cases = [
(0x80, Value::NoSuchObject),
(0x81, Value::NoSuchInstance),
(0x82, Value::EndOfMibView),
];
for (tag, expected) in cases {
let malformed = Bytes::from(vec![tag, 0x01, 0xff]);
let mut default_decoder = Decoder::new(malformed.clone());
assert!(Value::decode(&mut default_decoder).is_err());
let mut strict_decoder =
Decoder::new(malformed.clone()).with_decode_config(crate::DecodeConfig::STRICT);
assert!(Value::decode(&mut strict_decoder).is_err());
let config = crate::DecodeConfig {
malformed_exception_payloads: true,
..crate::DecodeConfig::STRICT
};
let mut compatible_decoder = Decoder::new(malformed).with_decode_config(config);
assert_eq!(Value::decode(&mut compatible_decoder).unwrap(), expected);
let mut canonical =
Decoder::from_slice(&[tag, 0x00]).with_decode_config(crate::DecodeConfig::STRICT);
assert_eq!(Value::decode(&mut canonical).unwrap(), expected);
}
}
#[test]
fn compatibility_policy_controls_bounded_string_clamping() {
let malformed = Bytes::from_static(&[0x04, 0x03, b'a', b'b']);
let mut compatible = Decoder::new(malformed.clone());
assert_eq!(
Value::decode(&mut compatible).unwrap(),
Value::OctetString(Bytes::from_static(b"ab"))
);
let config = crate::DecodeConfig {
clamp_bounded_strings: false,
..crate::DecodeConfig::DEFAULT
};
let mut strict = Decoder::new(malformed).with_decode_config(config);
assert!(Value::decode(&mut strict).is_err());
}
#[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(360_000).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(0x0123_4567_89AB_CDEF));
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(0x0123_4567_89AB_CDEF));
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 opaque_typed_accessors_reject_trailing_bytes_without_altering_raw_access() {
let cases = [
(
Bytes::from_static(&[0x9f, 0x78, 0x04, 0x40, 0x49, 0x0f, 0xdb, 0xff]),
"float",
),
(
Bytes::from_static(&[
0x9f, 0x79, 0x08, 0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18, 0xff,
]),
"double",
),
(
Bytes::from_static(&[0x9f, 0x76, 0x01, 0x2a, 0xff]),
"counter64",
),
(Bytes::from_static(&[0x9f, 0x7a, 0x01, 0xff, 0x00]), "i64"),
(Bytes::from_static(&[0x9f, 0x7b, 0x01, 0x2a, 0xff]), "u64"),
];
for (data, kind) in cases {
let value = Value::Opaque(data.clone());
match kind {
"float" => assert_eq!(value.as_opaque_float(), None),
"double" => assert_eq!(value.as_opaque_double(), None),
"counter64" => assert_eq!(value.as_opaque_counter64(), None),
"i64" => assert_eq!(value.as_opaque_i64(), None),
"u64" => assert_eq!(value.as_opaque_u64(), None),
_ => unreachable!(),
}
assert_eq!(value.as_bytes(), Some(data.as_ref()));
}
}
#[test]
fn opaque_integer_accessors_retain_non_minimal_payload_compatibility() {
let signed = Value::Opaque(Bytes::from_static(&[0x9f, 0x7a, 0x02, 0xff, 0xff]));
assert_eq!(signed.as_opaque_i64(), Some(-1));
let unsigned = Value::Opaque(Bytes::from_static(&[0x9f, 0x7b, 0x02, 0x00, 0x2a]));
assert_eq!(unsigned.as_opaque_u64(), Some(42));
let counter = Value::Opaque(Bytes::from_static(&[0x9f, 0x76, 0x02, 0x00, 0x2a]));
assert_eq!(counter.as_opaque_counter64(), Some(42));
}
#[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);
}
}