use crate::format::hex;
use crate::{Oid, Value, VarBind};
use mib_rs::mib::display_hint::HexCase;
use smallvec::SmallVec;
pub use mib_rs::{Access, DiagnosticConfig, Kind, Loader, Mib, ResolveOidError, source};
pub fn resolve_oid(mib: &Mib, name: &str) -> Result<Oid, ResolveOidError> {
mib.resolve_oid(name).map(|oid| Oid::from(&oid))
}
pub fn format_oid(mib: &Mib, oid: &Oid) -> String {
mib.format_oid(&oid.to_mib_oid())
}
pub fn format_varbind(mib: &Mib, vb: &VarBind) -> String {
let oid_str = format_oid(mib, &vb.oid);
let value_str = format_value(mib, &vb.oid, &vb.value);
format!("{} = {}", oid_str, value_str)
}
#[derive(Debug)]
pub struct VarBindInfo<'a> {
pub object_name: &'a str,
pub module_name: &'a str,
pub suffix: SmallVec<[u32; 4]>,
pub units: &'a str,
pub access: Access,
pub kind: Kind,
pub formatted_value: String,
}
pub fn describe_varbind<'a>(mib: &'a Mib, vb: &VarBind) -> Option<VarBindInfo<'a>> {
let mib_oid = vb.oid.to_mib_oid();
let lookup = mib.lookup_instance(&mib_oid);
let node = lookup.node();
let object = node.object()?;
let module_name = object.module().map(|m| m.name()).unwrap_or("");
let formatted_value = format_object_value(mib, &object, &vb.value);
Some(VarBindInfo {
object_name: object.name(),
module_name,
suffix: SmallVec::from_slice(lookup.suffix()),
units: object.units(),
access: object.access(),
kind: object.kind(),
formatted_value,
})
}
pub fn format_value(mib: &Mib, oid: &Oid, value: &Value) -> String {
let mib_oid = oid.to_mib_oid();
let lookup = mib.lookup_instance(&mib_oid);
let node = lookup.node();
if let Some(object) = node.object() {
format_object_value(mib, &object, value)
} else {
value.to_string()
}
}
fn format_object_value(mib: &Mib, object: &mib_rs::Object<'_>, value: &Value) -> String {
match value {
Value::Integer(v) => {
let enums = object.effective_enums();
if let Some(nv) = enums.iter().find(|nv| nv.value == *v as i64) {
return format!("{}({})", nv.label, v);
}
if let Some(formatted) = object.format_integer(*v as i64, HexCase::Lower) {
return formatted;
}
format!("{}", v)
}
Value::OctetString(bytes) => {
if let Some(formatted) = object.format_octets(bytes, HexCase::Lower) {
return formatted;
}
if hex::is_printable(bytes) {
return String::from_utf8_lossy(bytes).into_owned();
}
format_hex(bytes)
}
Value::ObjectIdentifier(oid) => format_oid(mib, oid),
Value::TimeTicks(v) => {
let formatted = crate::format::format_timeticks(*v);
format!("({}) {}", v, formatted)
}
Value::Counter32(v) => format!("{}", v),
Value::Counter64(v) => format!("{}", v),
Value::Gauge32(v) => format!("{}", v),
Value::Opaque(bytes) => {
if let Some(formatted) = object.format_octets(bytes, HexCase::Lower) {
return formatted;
}
format_hex(bytes)
}
Value::IpAddress(bytes) => {
format!("{}.{}.{}.{}", bytes[0], bytes[1], bytes[2], bytes[3])
}
Value::Null => "NULL".to_string(),
Value::NoSuchObject => "noSuchObject".to_string(),
Value::NoSuchInstance => "noSuchInstance".to_string(),
Value::EndOfMibView => "endOfMibView".to_string(),
Value::Unknown { tag, data } => {
format!("Unknown(0x{:02X}): {}", tag, format_hex(data))
}
}
}
fn format_hex(bytes: &[u8]) -> String {
crate::format::format_hex_display(bytes)
}
#[cfg(feature = "cli")]
impl crate::cli::output::VarBindFormatter for Mib {
fn format_oid(&self, oid: &Oid) -> String {
format_oid(self, oid)
}
fn format_value(&self, oid: &Oid, value: &Value) -> String {
format_value(self, oid, value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::oid;
fn test_mib() -> Mib {
let source = source::memory(
"TEST-MIB",
r#"TEST-MIB DEFINITIONS ::= BEGIN
IMPORTS
MODULE-IDENTITY, OBJECT-TYPE, Integer32, enterprises
FROM SNMPv2-SMI
DisplayString
FROM SNMPv2-TC;
testMib MODULE-IDENTITY
LAST-UPDATED "202603210000Z"
ORGANIZATION "Test"
CONTACT-INFO "Test"
DESCRIPTION "Test module."
::= { enterprises 99999 }
testScalar OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS read-only
STATUS current
DESCRIPTION "A test scalar."
::= { testMib 1 }
testStatus OBJECT-TYPE
SYNTAX INTEGER { up(1), down(2), testing(3) }
MAX-ACCESS read-only
STATUS current
DESCRIPTION "A test status."
::= { testMib 2 }
END
"#,
);
Loader::new()
.source(source)
.modules(["TEST-MIB"])
.load()
.expect("test MIB should load")
}
#[test]
fn test_resolve_oid() {
let mib = test_mib();
let oid = resolve_oid(&mib, "testScalar.0").unwrap();
let expected = resolve_oid(&mib, "testScalar").unwrap().child(0);
assert_eq!(oid, expected);
}
#[test]
fn test_format_oid_symbolic() {
let mib = test_mib();
let oid = resolve_oid(&mib, "testScalar.0").unwrap();
let formatted = format_oid(&mib, &oid);
assert!(formatted.contains("testScalar"), "got: {}", formatted);
}
#[test]
fn test_format_varbind_string() {
let mib = test_mib();
let oid = resolve_oid(&mib, "testScalar.0").unwrap();
let vb = VarBind::new(oid, Value::OctetString(bytes::Bytes::from_static(b"hello")));
let formatted = format_varbind(&mib, &vb);
assert!(formatted.contains("testScalar"), "got: {}", formatted);
assert!(formatted.contains("hello"), "got: {}", formatted);
}
#[test]
fn test_format_varbind_enum() {
let mib = test_mib();
let oid = resolve_oid(&mib, "testStatus.0").unwrap();
let vb = VarBind::new(oid, Value::Integer(1));
let formatted = format_varbind(&mib, &vb);
assert!(formatted.contains("up(1)"), "got: {}", formatted);
}
#[test]
fn test_describe_varbind() {
let mib = test_mib();
let oid = resolve_oid(&mib, "testScalar.0").unwrap();
let vb = VarBind::new(oid, Value::OctetString(bytes::Bytes::from_static(b"hello")));
let info = describe_varbind(&mib, &vb).expect("should describe");
assert_eq!(info.object_name, "testScalar");
assert_eq!(info.module_name, "TEST-MIB");
assert_eq!(info.suffix.as_slice(), &[0]);
}
#[test]
fn test_oid_conversion_roundtrip() {
let snmp_oid = oid!(1, 3, 6, 1, 2, 1, 1, 1, 0);
let mib_oid = snmp_oid.to_mib_oid();
let back: Oid = Oid::from(&mib_oid);
assert_eq!(snmp_oid, back);
}
#[test]
fn test_describe_unknown_oid_returns_none() {
let mib = test_mib();
let vb = VarBind::new(oid!(1, 3, 6, 1, 99, 99, 99), Value::Integer(42));
let _ = describe_varbind(&mib, &vb);
}
}