Skip to main content

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}