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/// Controls diagnostic reporting verbosity.
103///
104/// See also [`ResolverStrictness`] which controls resolver fallback behavior separately.
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
106#[repr(u8)]
107pub enum ReportingLevel {
108    /// Suppress all diagnostics except fatal errors.
109    Silent = 0,
110    /// Report errors and above only.
111    Quiet = 1,
112    /// Report minor issues and above.
113    Default = 2,
114    /// Report all diagnostics including style and info.
115    Verbose = 3,
116}
117
118impl_display!(ReportingLevel {
119    Silent => "silent",
120    Quiet => "quiet",
121    Default => "default",
122    Verbose => "verbose",
123});
124
125/// Identifies what an OID node represents in the MIB tree.
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
127#[repr(u8)]
128pub enum Kind {
129    /// Kind not yet determined.
130    #[default]
131    Unknown = 0,
132    /// Synthetic internal node (e.g. root of the OID tree).
133    Internal = 1,
134    /// Plain OID registration (OBJECT IDENTIFIER value assignment).
135    Node = 2,
136    /// Scalar OBJECT-TYPE (single-instance managed object).
137    Scalar = 3,
138    /// Table OBJECT-TYPE (SEQUENCE OF).
139    Table = 4,
140    /// Row OBJECT-TYPE (conceptual row / SEQUENCE entry).
141    Row = 5,
142    /// Column OBJECT-TYPE (leaf within a row).
143    Column = 6,
144    /// NOTIFICATION-TYPE or TRAP-TYPE definition.
145    Notification = 7,
146    /// OBJECT-GROUP or NOTIFICATION-GROUP.
147    Group = 8,
148    /// MODULE-COMPLIANCE definition.
149    Compliance = 9,
150    /// AGENT-CAPABILITIES definition.
151    Capability = 10,
152    /// MODULE-IDENTITY definition.
153    ModuleIdentity = 11,
154    /// OBJECT-IDENTITY definition.
155    ObjectIdentity = 12,
156}
157
158impl Kind {
159    /// Reports whether this is a scalar/table/row/column.
160    pub fn is_object_type(self) -> bool {
161        matches!(self, Kind::Scalar | Kind::Table | Kind::Row | Kind::Column)
162    }
163
164    /// Reports whether this is a group/compliance/capabilities node.
165    pub fn is_conformance(self) -> bool {
166        matches!(self, Kind::Group | Kind::Compliance | Kind::Capability)
167    }
168
169    /// Reports whether this is a plain node-like kind (node, module-identity, object-identity).
170    pub fn is_node_like(self) -> bool {
171        matches!(
172            self,
173            Kind::Node | Kind::ModuleIdentity | Kind::ObjectIdentity
174        )
175    }
176}
177
178impl_display!(Kind {
179    Unknown => "unknown",
180    Internal => "internal",
181    Node => "node",
182    Scalar => "scalar",
183    Table => "table",
184    Row => "row",
185    Column => "column",
186    Notification => "notification",
187    Group => "group",
188    Compliance => "compliance",
189    Capability => "capabilities",
190    ModuleIdentity => "module-identity",
191    ObjectIdentity => "object-identity",
192});
193
194/// Access level for OBJECT-TYPE definitions.
195///
196/// Covers both SMIv1 ACCESS and SMIv2 MAX-ACCESS values.
197/// See [`AccessKeyword`] for which keyword was used in the source.
198#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
199#[repr(u8)]
200pub enum Access {
201    /// Object cannot be read or written.
202    #[default]
203    NotAccessible = 0,
204    /// Object is only accessible via notifications.
205    AccessibleForNotify = 1,
206    /// Object can be read but not written.
207    ReadOnly = 2,
208    /// Object can be read and written.
209    ReadWrite = 3,
210    /// Object can be read, written, and used in row creation.
211    ReadCreate = 4,
212    /// Object can only be written (SMIv1 only, deprecated in SMIv2).
213    WriteOnly = 5,
214    /// Object is not implemented (AGENT-CAPABILITIES variation).
215    NotImplemented = 6,
216}
217
218impl_display!(Access {
219    NotAccessible => "not-accessible",
220    AccessibleForNotify => "accessible-for-notify",
221    ReadOnly => "read-only",
222    ReadWrite => "read-write",
223    ReadCreate => "read-create",
224    WriteOnly => "write-only",
225    NotImplemented => "not-implemented",
226});
227
228/// Lifecycle state of a MIB definition.
229///
230/// SMIv2 uses `Current`, `Deprecated`, and `Obsolete`. SMIv1 additionally uses
231/// `Mandatory` and `Optional`. Values are not normalized across versions.
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
233#[repr(u8)]
234pub enum Status {
235    /// Active and valid (SMIv2).
236    #[default]
237    Current = 0,
238    /// Still usable but being phased out.
239    Deprecated = 1,
240    /// No longer in use.
241    Obsolete = 2,
242    /// Required for compliance (SMIv1 only).
243    Mandatory = 3,
244    /// Not required (SMIv1 only).
245    Optional = 4,
246}
247
248impl Status {
249    /// Reports whether this is an SMIv1-specific status value.
250    pub fn is_smiv1(self) -> bool {
251        matches!(self, Status::Mandatory | Status::Optional)
252    }
253}
254
255impl_display!(Status {
256    Current => "current",
257    Deprecated => "deprecated",
258    Obsolete => "obsolete",
259    Mandatory => "mandatory",
260    Optional => "optional",
261});
262
263/// SMI language version of a MIB module.
264#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
265#[repr(u8)]
266pub enum Language {
267    /// Version not yet determined.
268    #[default]
269    Unknown = 0,
270    /// RFC 1155/1212 (Structure of Management Information v1).
271    SMIv1 = 1,
272    /// RFC 2578 (Structure of Management Information v2).
273    SMIv2 = 2,
274    /// RFC 3159 (Structure of Policy Provisioning Information).
275    SPPI = 3,
276}
277
278impl_display!(Language {
279    Unknown => "unknown",
280    SMIv1 => "SMIv1",
281    SMIv2 => "SMIv2",
282    SPPI => "SPPI",
283});
284
285/// Fundamental SMI type that a textual convention or [`Kind::Scalar`]/[`Kind::Column`]
286/// object ultimately resolves to.
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
288#[repr(u8)]
289pub enum BaseType {
290    /// Base type not yet resolved.
291    #[default]
292    Unknown = 0,
293    /// 32-bit signed integer (INTEGER, Integer32).
294    Integer32 = 1,
295    /// 32-bit unsigned integer (Unsigned32).
296    Unsigned32 = 2,
297    /// 32-bit monotonically increasing counter.
298    Counter32 = 3,
299    /// 64-bit monotonically increasing counter.
300    Counter64 = 4,
301    /// 32-bit non-negative integer that can increase or decrease.
302    Gauge32 = 5,
303    /// Hundredths of a second since an epoch.
304    TimeTicks = 6,
305    /// IPv4 address (4 octets).
306    IpAddress = 7,
307    /// Arbitrary binary or text data.
308    OctetString = 8,
309    /// ASN.1 OBJECT IDENTIFIER value.
310    ObjectIdentifier = 9,
311    /// Named bit set.
312    Bits = 10,
313    /// Opaque data (wraps arbitrary ASN.1).
314    Opaque = 11,
315    /// SEQUENCE type used for table row definitions.
316    Sequence = 12,
317    /// 64-bit signed integer (SPPI).
318    Integer64 = 13,
319    /// 64-bit unsigned integer (SPPI).
320    Unsigned64 = 14,
321}
322
323impl_display!(BaseType {
324    Unknown => "unknown",
325    Integer32 => "Integer32",
326    Unsigned32 => "Unsigned32",
327    Counter32 => "Counter32",
328    Counter64 => "Counter64",
329    Gauge32 => "Gauge32",
330    TimeTicks => "TimeTicks",
331    IpAddress => "IpAddress",
332    OctetString => "OCTET STRING",
333    ObjectIdentifier => "OBJECT IDENTIFIER",
334    Bits => "BITS",
335    Opaque => "Opaque",
336    Sequence => "SEQUENCE",
337    Integer64 => "Integer64",
338    Unsigned64 => "Unsigned64",
339});
340
341/// How an INDEX component maps to instance-identifier sub-identifiers (RFC 2578, Section 7.7).
342///
343/// When a table row is identified by its index values, those values are
344/// encoded as OID sub-identifiers appended to the column OID. The
345/// encoding strategy depends on the index object's [`BaseType`] and
346/// constraints:
347///
348/// - Integer types use a single sub-identifier containing the value.
349/// - Fixed-length strings (with a single-value SIZE constraint like
350///   `SIZE (6)`) use one sub-identifier per octet, with no length prefix.
351/// - Variable-length strings are length-prefixed: one sub-identifier
352///   for the length, followed by one per octet.
353/// - The `IMPLIED` keyword omits the length prefix, but can only be
354///   used on the last index component since there is no way to tell
355///   where it ends otherwise.
356///
357/// This encoding matters when constructing or parsing instance OIDs
358/// programmatically (e.g. building an SNMP GET request for a specific
359/// table row).
360#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
361#[repr(u8)]
362pub enum IndexEncoding {
363    /// Encoding not yet determined.
364    #[default]
365    Unknown = 0,
366    /// Single sub-identifier for integer-valued indexes.
367    Integer = 1,
368    /// Fixed number of sub-identifiers (SIZE-constrained OCTET STRING).
369    /// No length prefix; the number of sub-identifiers equals the fixed SIZE.
370    FixedString = 2,
371    /// Length prefix followed by that many sub-identifiers.
372    /// Used for variable-length OCTET STRING and OBJECT IDENTIFIER indexes.
373    LengthPrefixed = 3,
374    /// No length prefix; the index value extends to the end of the OID.
375    /// Only valid for the last index component (uses the `IMPLIED` keyword).
376    Implied = 4,
377    /// Four sub-identifiers encoding an IPv4 address (one per octet).
378    IpAddress = 5,
379}
380
381impl_display!(IndexEncoding {
382    Unknown => "unknown",
383    Integer => "integer",
384    FixedString => "fixed-string",
385    LengthPrefixed => "length-prefixed",
386    Implied => "implied",
387    IpAddress => "ip-address",
388});
389
390/// Records which access keyword was used in the source MIB.
391///
392/// SMIv1 uses `ACCESS`, SMIv2 uses `MAX-ACCESS`, and compliance statements use `MIN-ACCESS`.
393/// The resolved access value is stored separately as [`Access`].
394#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
395#[repr(u8)]
396pub enum AccessKeyword {
397    /// SMIv1 `ACCESS` clause.
398    #[default]
399    Access = 0,
400    /// SMIv2 `MAX-ACCESS` clause.
401    MaxAccess = 1,
402    /// `MIN-ACCESS` clause in MODULE-COMPLIANCE refinements.
403    MinAccess = 2,
404}
405
406impl_display!(AccessKeyword {
407    Access => "ACCESS",
408    MaxAccess => "MAX-ACCESS",
409    MinAccess => "MIN-ACCESS",
410});
411
412#[cfg(test)]
413mod tests {
414    use super::*;
415
416    #[test]
417    fn severity_ordering() {
418        assert!(Severity::Fatal <= Severity::Info);
419        assert!(Severity::Fatal <= Severity::Fatal);
420        assert!(Severity::Info > Severity::Fatal);
421    }
422
423    #[test]
424    fn severity_display() {
425        assert_eq!(Severity::Fatal.to_string(), "fatal");
426        assert_eq!(Severity::Info.to_string(), "info");
427    }
428
429    #[test]
430    fn kind_classification() {
431        assert!(Kind::Scalar.is_object_type());
432        assert!(Kind::Table.is_object_type());
433        assert!(Kind::Row.is_object_type());
434        assert!(Kind::Column.is_object_type());
435        assert!(!Kind::Node.is_object_type());
436        assert!(!Kind::Notification.is_object_type());
437
438        assert!(Kind::Group.is_conformance());
439        assert!(Kind::Compliance.is_conformance());
440        assert!(Kind::Capability.is_conformance());
441        assert!(!Kind::Scalar.is_conformance());
442    }
443
444    #[test]
445    fn status_smiv1() {
446        assert!(Status::Mandatory.is_smiv1());
447        assert!(Status::Optional.is_smiv1());
448        assert!(!Status::Current.is_smiv1());
449        assert!(!Status::Deprecated.is_smiv1());
450    }
451}