mib_rs/types/enums.rs
1//! Enumerations for SMI concepts.
2//!
3//! Defines the core enums used throughout the MIB parsing and resolution pipeline:
4//! severity levels, node kinds, access levels, status values, base types, and
5//! configuration knobs for resolver strictness and diagnostic reporting.
6
7use std::fmt;
8
9macro_rules! impl_display {
10 ($ty:ident { $($variant:ident => $s:literal),* $(,)? }) => {
11 impl fmt::Display for $ty {
12 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13 f.write_str(match self {
14 $($ty::$variant => $s),*
15 })
16 }
17 }
18 }
19}
20
21/// Severity indicates how serious a diagnostic issue is (libsmi-compatible).
22/// Lower values are more severe.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
24#[repr(u8)]
25pub enum Severity {
26 /// Unrecoverable failure that halts processing.
27 Fatal = 0,
28 /// Serious issue that likely produces incorrect results.
29 Severe = 1,
30 /// Standard error in the MIB definition.
31 Error = 2,
32 /// Minor issue that may indicate a problem.
33 Minor = 3,
34 /// Stylistic deviation from best practice.
35 Style = 4,
36 /// Potential issue worth noting.
37 Warning = 5,
38 /// Informational message.
39 Info = 6,
40}
41
42impl Severity {
43 /// Reports whether this severity is at least as severe as `threshold`.
44 pub fn at_least(self, threshold: Severity) -> bool {
45 self <= threshold
46 }
47}
48
49impl_display!(Severity {
50 Fatal => "fatal",
51 Severe => "severe",
52 Error => "error",
53 Minor => "minor",
54 Style => "style",
55 Warning => "warning",
56 Info => "info",
57});
58
59/// Controls resolver fallback behavior when resolving cross-module references.
60///
61/// Ordered from strictest (fewest fallbacks) to most permissive.
62/// See also [`ReportingLevel`] which controls diagnostic output separately.
63///
64/// All levels support direct import resolution, import forwarding (following
65/// re-exports declared in the source module's own IMPORTS), partial import
66/// resolution, ASN.1 primitive type fallback, and well-known OID roots.
67///
68/// See the crate-level docs for a detailed breakdown of behaviors per level.
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70#[repr(u8)]
71pub enum ResolverStrictness {
72 /// Minimal fallbacks. Only deterministic strategies that don't guess
73 /// the source module (direct imports, import forwarding, ASN.1
74 /// primitives, well-known OID roots).
75 Strict = 0,
76 /// Constrained fallbacks: module name aliases, unimported SMI/TC type
77 /// lookup, SMI global OID root fallback, TRAP-TYPE enterprise lookup.
78 Normal = 1,
79 /// All fallbacks, including global symbol search across all loaded
80 /// modules for objects, group members, and compliance targets.
81 Permissive = 2,
82}
83
84impl ResolverStrictness {
85 /// Reports whether tier-2 constrained fallbacks are enabled (Normal+).
86 pub fn allow_constrained_fallbacks(self) -> bool {
87 self != ResolverStrictness::Strict
88 }
89
90 /// Reports whether tier-3 global fallbacks are enabled (Permissive only).
91 pub fn allow_global_fallbacks(self) -> bool {
92 self == ResolverStrictness::Permissive
93 }
94}
95
96impl_display!(ResolverStrictness {
97 Strict => "strict",
98 Normal => "normal",
99 Permissive => "permissive",
100});
101
102/// Resolver lookup context used when explaining a symbol reference.
103///
104/// Different SMI reference sites intentionally use different fallback rules.
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
106pub enum ResolutionDomain {
107 /// A type reference in SYNTAX or a type assignment.
108 Type,
109 /// A symbolic OID component.
110 Oid,
111 /// The object-only lookup used by AUGMENTS and non-bare INDEX targets.
112 Object,
113 /// An OBJECT-GROUP or NOTIFICATION-GROUP member node reference.
114 GroupMember,
115 /// An INDEX component, including the permitted bare type names.
116 Index,
117 /// A NOTIFICATION-TYPE or TRAP-TYPE OBJECTS reference.
118 NotificationObject,
119 /// A conformance target without an explicit SUPPORTS module qualifier.
120 Conformance,
121}
122
123impl_display!(ResolutionDomain {
124 Type => "type",
125 Oid => "oid",
126 Object => "object",
127 GroupMember => "group-member",
128 Index => "index",
129 NotificationObject => "notification-object",
130 Conformance => "conformance",
131});
132
133/// Controls diagnostic reporting verbosity.
134///
135/// See also [`ResolverStrictness`] which controls resolver fallback behavior separately.
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
137#[repr(u8)]
138pub enum ReportingLevel {
139 /// Suppress all diagnostics except fatal errors.
140 Silent = 0,
141 /// Report errors and above only.
142 Quiet = 1,
143 /// Report minor issues and above.
144 Default = 2,
145 /// Report all diagnostics including style and info.
146 Verbose = 3,
147}
148
149impl_display!(ReportingLevel {
150 Silent => "silent",
151 Quiet => "quiet",
152 Default => "default",
153 Verbose => "verbose",
154});
155
156/// Identifies what an OID node represents in the MIB tree.
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
158#[repr(u8)]
159pub enum Kind {
160 /// Kind not yet determined.
161 #[default]
162 Unknown = 0,
163 /// Synthetic internal node (e.g. root of the OID tree).
164 Internal = 1,
165 /// Plain OID registration (OBJECT IDENTIFIER value assignment).
166 Node = 2,
167 /// Scalar OBJECT-TYPE (single-instance managed object).
168 Scalar = 3,
169 /// Table OBJECT-TYPE (SEQUENCE OF).
170 Table = 4,
171 /// Row OBJECT-TYPE (conceptual row / SEQUENCE entry).
172 Row = 5,
173 /// Column OBJECT-TYPE (leaf within a row).
174 Column = 6,
175 /// NOTIFICATION-TYPE or TRAP-TYPE definition.
176 Notification = 7,
177 /// OBJECT-GROUP or NOTIFICATION-GROUP.
178 Group = 8,
179 /// MODULE-COMPLIANCE definition.
180 Compliance = 9,
181 /// AGENT-CAPABILITIES definition.
182 Capability = 10,
183 /// MODULE-IDENTITY definition.
184 ModuleIdentity = 11,
185 /// OBJECT-IDENTITY definition.
186 ObjectIdentity = 12,
187}
188
189impl Kind {
190 /// Reports whether this is a scalar/table/row/column.
191 pub fn is_object_type(self) -> bool {
192 matches!(self, Kind::Scalar | Kind::Table | Kind::Row | Kind::Column)
193 }
194
195 /// Reports whether this is a group/compliance/capabilities node.
196 pub fn is_conformance(self) -> bool {
197 matches!(self, Kind::Group | Kind::Compliance | Kind::Capability)
198 }
199
200 /// Reports whether this is a plain node-like kind (node, module-identity, object-identity).
201 pub fn is_node_like(self) -> bool {
202 matches!(
203 self,
204 Kind::Node | Kind::ModuleIdentity | Kind::ObjectIdentity
205 )
206 }
207}
208
209impl_display!(Kind {
210 Unknown => "unknown",
211 Internal => "internal",
212 Node => "node",
213 Scalar => "scalar",
214 Table => "table",
215 Row => "row",
216 Column => "column",
217 Notification => "notification",
218 Group => "group",
219 Compliance => "compliance",
220 Capability => "capabilities",
221 ModuleIdentity => "module-identity",
222 ObjectIdentity => "object-identity",
223});
224
225/// Access level for OBJECT-TYPE definitions.
226///
227/// Covers both SMIv1 ACCESS and SMIv2 MAX-ACCESS values.
228/// See [`AccessKeyword`] for which keyword was used in the source.
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
230#[repr(u8)]
231pub enum Access {
232 /// Object cannot be read or written.
233 #[default]
234 NotAccessible = 0,
235 /// Object is only accessible via notifications.
236 AccessibleForNotify = 1,
237 /// Object can be read but not written.
238 ReadOnly = 2,
239 /// Object can be read and written.
240 ReadWrite = 3,
241 /// Object can be read, written, and used in row creation.
242 ReadCreate = 4,
243 /// Object can only be written (SMIv1 only, deprecated in SMIv2).
244 WriteOnly = 5,
245 /// Object is not implemented (AGENT-CAPABILITIES variation).
246 NotImplemented = 6,
247}
248
249impl_display!(Access {
250 NotAccessible => "not-accessible",
251 AccessibleForNotify => "accessible-for-notify",
252 ReadOnly => "read-only",
253 ReadWrite => "read-write",
254 ReadCreate => "read-create",
255 WriteOnly => "write-only",
256 NotImplemented => "not-implemented",
257});
258
259/// Lifecycle state of a MIB definition.
260///
261/// SMIv2 uses `Current`, `Deprecated`, and `Obsolete`. SMIv1 additionally uses
262/// `Mandatory` and `Optional`. Values are not normalized across versions.
263#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
264#[repr(u8)]
265pub enum Status {
266 /// Active and valid (SMIv2).
267 #[default]
268 Current = 0,
269 /// Still usable but being phased out.
270 Deprecated = 1,
271 /// No longer in use.
272 Obsolete = 2,
273 /// Required for compliance (SMIv1 only).
274 Mandatory = 3,
275 /// Not required (SMIv1 only).
276 Optional = 4,
277}
278
279impl Status {
280 /// Reports whether this is an SMIv1-specific status value.
281 pub fn is_smiv1(self) -> bool {
282 matches!(self, Status::Mandatory | Status::Optional)
283 }
284}
285
286impl_display!(Status {
287 Current => "current",
288 Deprecated => "deprecated",
289 Obsolete => "obsolete",
290 Mandatory => "mandatory",
291 Optional => "optional",
292});
293
294/// SMI language version of a MIB module.
295#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
296#[repr(u8)]
297pub enum Language {
298 /// Version not determined, including when evidence is insufficient or conflicting.
299 #[default]
300 Unknown = 0,
301 /// RFC 1155/1212 (Structure of Management Information v1).
302 SMIv1 = 1,
303 /// RFC 2578 (Structure of Management Information v2).
304 SMIv2 = 2,
305 /// RFC 3159 (Structure of Policy Provisioning Information).
306 SPPI = 3,
307}
308
309impl_display!(Language {
310 Unknown => "unknown",
311 SMIv1 => "SMIv1",
312 SMIv2 => "SMIv2",
313 SPPI => "SPPI",
314});
315
316/// Fundamental SMI type that a textual convention or [`Kind::Scalar`]/[`Kind::Column`]
317/// object ultimately resolves to.
318#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
319#[repr(u8)]
320pub enum BaseType {
321 /// Base type not yet resolved.
322 #[default]
323 Unknown = 0,
324 /// 32-bit signed integer (INTEGER, Integer32).
325 Integer32 = 1,
326 /// 32-bit unsigned integer (Unsigned32).
327 Unsigned32 = 2,
328 /// 32-bit monotonically increasing counter.
329 Counter32 = 3,
330 /// 64-bit monotonically increasing counter.
331 Counter64 = 4,
332 /// 32-bit non-negative integer that can increase or decrease.
333 Gauge32 = 5,
334 /// Hundredths of a second since an epoch.
335 TimeTicks = 6,
336 /// IPv4 address (4 octets).
337 IpAddress = 7,
338 /// Arbitrary binary or text data.
339 OctetString = 8,
340 /// ASN.1 OBJECT IDENTIFIER value.
341 ObjectIdentifier = 9,
342 /// Named bit set.
343 Bits = 10,
344 /// Opaque data (wraps arbitrary ASN.1).
345 Opaque = 11,
346 /// SEQUENCE type used for table row definitions.
347 Sequence = 12,
348 /// 64-bit signed integer (SPPI).
349 Integer64 = 13,
350 /// 64-bit unsigned integer (SPPI).
351 Unsigned64 = 14,
352}
353
354impl_display!(BaseType {
355 Unknown => "unknown",
356 Integer32 => "Integer32",
357 Unsigned32 => "Unsigned32",
358 Counter32 => "Counter32",
359 Counter64 => "Counter64",
360 Gauge32 => "Gauge32",
361 TimeTicks => "TimeTicks",
362 IpAddress => "IpAddress",
363 OctetString => "OCTET STRING",
364 ObjectIdentifier => "OBJECT IDENTIFIER",
365 Bits => "BITS",
366 Opaque => "Opaque",
367 Sequence => "SEQUENCE",
368 Integer64 => "Integer64",
369 Unsigned64 => "Unsigned64",
370});
371
372/// How an INDEX component maps to instance-identifier sub-identifiers (RFC 2578, Section 7.7).
373///
374/// When a table row is identified by its index values, those values are
375/// encoded as OID sub-identifiers appended to the column OID. The
376/// encoding strategy depends on the index object's [`BaseType`] and
377/// constraints:
378///
379/// - Integer types use a single sub-identifier containing the value.
380/// - Fixed-length strings (with a single-value SIZE constraint like
381/// `SIZE (6)`) use one sub-identifier per octet, with no length prefix.
382/// - Variable-length strings are length-prefixed: one sub-identifier
383/// for the length, followed by one per octet.
384/// - The `IMPLIED` keyword omits the length prefix, but can only be
385/// used on the last index component since there is no way to tell
386/// where it ends otherwise.
387///
388/// This encoding matters when constructing or parsing instance OIDs
389/// programmatically (e.g. building an SNMP GET request for a specific
390/// table row).
391#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
392#[repr(u8)]
393pub enum IndexEncoding {
394 /// Encoding not yet determined.
395 #[default]
396 Unknown = 0,
397 /// Single sub-identifier for integer-valued indexes.
398 Integer = 1,
399 /// Fixed number of sub-identifiers (SIZE-constrained OCTET STRING).
400 /// No length prefix; the number of sub-identifiers equals the fixed SIZE.
401 FixedString = 2,
402 /// Length prefix followed by that many sub-identifiers.
403 /// Used for variable-length OCTET STRING and OBJECT IDENTIFIER indexes.
404 LengthPrefixed = 3,
405 /// No length prefix; the index value extends to the end of the OID.
406 /// Only valid for the last index component (uses the `IMPLIED` keyword).
407 Implied = 4,
408 /// Four sub-identifiers encoding an IPv4 address (one per octet).
409 IpAddress = 5,
410}
411
412impl_display!(IndexEncoding {
413 Unknown => "unknown",
414 Integer => "integer",
415 FixedString => "fixed-string",
416 LengthPrefixed => "length-prefixed",
417 Implied => "implied",
418 IpAddress => "ip-address",
419});
420
421/// Records which access keyword was used in the source MIB.
422///
423/// SMIv1 uses `ACCESS`, SMIv2 uses `MAX-ACCESS`, and compliance statements use `MIN-ACCESS`.
424/// The resolved access value is stored separately as [`Access`].
425#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
426#[repr(u8)]
427pub enum AccessKeyword {
428 /// SMIv1 `ACCESS` clause.
429 #[default]
430 Access = 0,
431 /// SMIv2 `MAX-ACCESS` clause.
432 MaxAccess = 1,
433 /// `MIN-ACCESS` clause in MODULE-COMPLIANCE refinements.
434 MinAccess = 2,
435}
436
437impl_display!(AccessKeyword {
438 Access => "ACCESS",
439 MaxAccess => "MAX-ACCESS",
440 MinAccess => "MIN-ACCESS",
441});
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446
447 #[test]
448 fn severity_ordering() {
449 assert!(Severity::Fatal <= Severity::Info);
450 assert!(Severity::Fatal <= Severity::Fatal);
451 assert!(Severity::Info > Severity::Fatal);
452 }
453
454 #[test]
455 fn severity_display() {
456 assert_eq!(Severity::Fatal.to_string(), "fatal");
457 assert_eq!(Severity::Info.to_string(), "info");
458 }
459
460 #[test]
461 fn kind_classification() {
462 assert!(Kind::Scalar.is_object_type());
463 assert!(Kind::Table.is_object_type());
464 assert!(Kind::Row.is_object_type());
465 assert!(Kind::Column.is_object_type());
466 assert!(!Kind::Node.is_object_type());
467 assert!(!Kind::Notification.is_object_type());
468
469 assert!(Kind::Group.is_conformance());
470 assert!(Kind::Compliance.is_conformance());
471 assert!(Kind::Capability.is_conformance());
472 assert!(!Kind::Scalar.is_conformance());
473 }
474
475 #[test]
476 fn status_smiv1() {
477 assert!(Status::Mandatory.is_smiv1());
478 assert!(Status::Optional.is_smiv1());
479 assert!(!Status::Current.is_smiv1());
480 assert!(!Status::Deprecated.is_smiv1());
481 }
482}