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