1use crate::{Oid, Value, VarBind};
14use mib_rs::mib::display_hint::HexCase;
15use smallvec::SmallVec;
16
17pub use mib_rs::{Access, DiagnosticConfig, Kind, Loader, Mib, ResolveOidError, source};
19
20pub fn resolve_oid(mib: &Mib, name: &str) -> Result<Oid, ResolveOidError> {
26 mib.resolve_oid(name).map(|oid| Oid::from(&oid))
27}
28
29pub fn format_oid(mib: &Mib, oid: &Oid) -> String {
34 mib.format_oid(&oid.to_mib_oid())
35}
36
37pub fn format_varbind(mib: &Mib, vb: &VarBind) -> String {
46 let oid_str = format_oid(mib, &vb.oid);
47 let value_str = format_value(mib, &vb.oid, &vb.value);
48 format!("{} = {}", oid_str, value_str)
49}
50
51pub struct VarBindInfo<'a> {
57 pub object_name: &'a str,
59 pub module_name: &'a str,
61 pub suffix: SmallVec<[u32; 4]>,
63 pub units: &'a str,
65 pub access: Access,
67 pub kind: Kind,
69 pub formatted_value: String,
71}
72
73pub fn describe_varbind<'a>(mib: &'a Mib, vb: &VarBind) -> Option<VarBindInfo<'a>> {
78 let mib_oid = vb.oid.to_mib_oid();
79 let lookup = mib.lookup_instance(&mib_oid);
80 let node = lookup.node();
81 let object = node.object()?;
82
83 let module_name = object.module().map(|m| m.name()).unwrap_or("");
84
85 let formatted_value = format_object_value(mib, &object, &vb.value);
86
87 Some(VarBindInfo {
88 object_name: object.name(),
89 module_name,
90 suffix: SmallVec::from_slice(lookup.suffix()),
91 units: object.units(),
92 access: object.access(),
93 kind: object.kind(),
94 formatted_value,
95 })
96}
97
98pub fn format_value(mib: &Mib, oid: &Oid, value: &Value) -> String {
104 let mib_oid = oid.to_mib_oid();
105 let lookup = mib.lookup_instance(&mib_oid);
106 let node = lookup.node();
107
108 if let Some(object) = node.object() {
109 format_object_value(mib, &object, value)
110 } else {
111 value.to_string()
112 }
113}
114
115fn format_object_value(mib: &Mib, object: &mib_rs::Object<'_>, value: &Value) -> String {
117 match value {
118 Value::Integer(v) => {
119 let enums = object.effective_enums();
121 if let Some(nv) = enums.iter().find(|nv| nv.value == *v as i64) {
122 return format!("{}({})", nv.label, v);
123 }
124 if let Some(formatted) = object.format_integer(*v as i64, HexCase::Lower) {
126 return formatted;
127 }
128 format!("{}", v)
129 }
130
131 Value::OctetString(bytes) => {
132 if let Some(formatted) = object.format_octets(bytes, HexCase::Lower) {
134 return formatted;
135 }
136 if let Ok(s) = std::str::from_utf8(bytes)
138 && s.chars()
139 .all(|c| c.is_ascii_graphic() || c.is_ascii_whitespace())
140 {
141 return s.to_string();
142 }
143 format_hex(bytes)
144 }
145
146 Value::ObjectIdentifier(oid) => format_oid(mib, oid),
147
148 Value::TimeTicks(v) => {
149 let formatted = crate::fmt::format_timeticks(*v);
150 format!("({}) {}", v, formatted)
151 }
152
153 Value::Counter32(v) => format!("{}", v),
154 Value::Counter64(v) => format!("{}", v),
155 Value::Gauge32(v) => format!("{}", v),
156
157 Value::Opaque(bytes) => {
158 if let Some(formatted) = object.format_octets(bytes, HexCase::Lower) {
160 return formatted;
161 }
162 format_hex(bytes)
163 }
164
165 Value::IpAddress(bytes) => {
166 format!("{}.{}.{}.{}", bytes[0], bytes[1], bytes[2], bytes[3])
167 }
168
169 Value::Null => "NULL".to_string(),
170
171 Value::NoSuchObject => "noSuchObject".to_string(),
173 Value::NoSuchInstance => "noSuchInstance".to_string(),
174 Value::EndOfMibView => "endOfMibView".to_string(),
175
176 Value::Unknown { tag, data } => {
177 format!("Unknown(0x{:02X}): {}", tag, format_hex(data))
178 }
179 }
180}
181
182fn format_hex(bytes: &[u8]) -> String {
184 crate::fmt::format_hex_display(bytes)
185}
186
187#[cfg(feature = "cli")]
188impl crate::cli::output::VarBindFormatter for Mib {
189 fn format_oid(&self, oid: &Oid) -> String {
190 format_oid(self, oid)
191 }
192
193 fn format_value(&self, oid: &Oid, value: &Value) -> String {
194 format_value(self, oid, value)
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use crate::oid;
202
203 fn test_mib() -> Mib {
204 let source = source::memory(
205 "TEST-MIB",
206 r#"TEST-MIB DEFINITIONS ::= BEGIN
207IMPORTS
208 MODULE-IDENTITY, OBJECT-TYPE, Integer32, enterprises
209 FROM SNMPv2-SMI
210 DisplayString
211 FROM SNMPv2-TC;
212
213testMib MODULE-IDENTITY
214 LAST-UPDATED "202603210000Z"
215 ORGANIZATION "Test"
216 CONTACT-INFO "Test"
217 DESCRIPTION "Test module."
218 ::= { enterprises 99999 }
219
220testScalar OBJECT-TYPE
221 SYNTAX DisplayString
222 MAX-ACCESS read-only
223 STATUS current
224 DESCRIPTION "A test scalar."
225 ::= { testMib 1 }
226
227testStatus OBJECT-TYPE
228 SYNTAX INTEGER { up(1), down(2), testing(3) }
229 MAX-ACCESS read-only
230 STATUS current
231 DESCRIPTION "A test status."
232 ::= { testMib 2 }
233
234END
235"#,
236 );
237
238 Loader::new()
239 .source(source)
240 .modules(["TEST-MIB"])
241 .load()
242 .expect("test MIB should load")
243 }
244
245 #[test]
246 fn test_resolve_oid() {
247 let mib = test_mib();
248 let oid = resolve_oid(&mib, "testScalar.0").unwrap();
249 let expected = resolve_oid(&mib, "testScalar").unwrap().child(0);
250 assert_eq!(oid, expected);
251 }
252
253 #[test]
254 fn test_format_oid_symbolic() {
255 let mib = test_mib();
256 let oid = resolve_oid(&mib, "testScalar.0").unwrap();
257 let formatted = format_oid(&mib, &oid);
258 assert!(formatted.contains("testScalar"), "got: {}", formatted);
259 }
260
261 #[test]
262 fn test_format_varbind_string() {
263 let mib = test_mib();
264 let oid = resolve_oid(&mib, "testScalar.0").unwrap();
265 let vb = VarBind::new(oid, Value::OctetString(bytes::Bytes::from_static(b"hello")));
266 let formatted = format_varbind(&mib, &vb);
267 assert!(formatted.contains("testScalar"), "got: {}", formatted);
268 assert!(formatted.contains("hello"), "got: {}", formatted);
269 }
270
271 #[test]
272 fn test_format_varbind_enum() {
273 let mib = test_mib();
274 let oid = resolve_oid(&mib, "testStatus.0").unwrap();
275 let vb = VarBind::new(oid, Value::Integer(1));
276 let formatted = format_varbind(&mib, &vb);
277 assert!(formatted.contains("up(1)"), "got: {}", formatted);
278 }
279
280 #[test]
281 fn test_describe_varbind() {
282 let mib = test_mib();
283 let oid = resolve_oid(&mib, "testScalar.0").unwrap();
284 let vb = VarBind::new(oid, Value::OctetString(bytes::Bytes::from_static(b"hello")));
285 let info = describe_varbind(&mib, &vb).expect("should describe");
286 assert_eq!(info.object_name, "testScalar");
287 assert_eq!(info.module_name, "TEST-MIB");
288 assert_eq!(info.suffix.as_slice(), &[0]);
289 }
290
291 #[test]
292 fn test_oid_conversion_roundtrip() {
293 let snmp_oid = oid!(1, 3, 6, 1, 2, 1, 1, 1, 0);
294 let mib_oid = snmp_oid.to_mib_oid();
295 let back: Oid = Oid::from(&mib_oid);
296 assert_eq!(snmp_oid, back);
297 }
298
299 #[test]
300 fn test_describe_unknown_oid_returns_none() {
301 let mib = test_mib();
302 let vb = VarBind::new(oid!(1, 3, 6, 1, 99, 99, 99), Value::Integer(42));
304 let _ = describe_varbind(&mib, &vb);
307 }
308}