Skip to main content

mib_rs/mib/
types.rs

1//! Shared types used across the resolved MIB model.
2//!
3//! Contains arena id newtypes ([`NodeId`], [`ObjectId`], [`TypeId`], etc.),
4//! supporting data structures ([`Range`], [`NamedValue`], [`DefVal`],
5//! [`IndexEntry`]), and SMI clause representations used by compliance and
6//! capability definitions.
7
8use std::fmt;
9
10use crate::mib::Oid;
11use crate::types::{Access, BaseType, IndexEncoding, Span};
12
13/// A single imported symbol with its source location.
14///
15/// Part of an [`Import`] group. The `name` is the symbol as written in the
16/// MIB's IMPORTS clause (e.g. `"ifIndex"`, `"DisplayString"`).
17#[derive(Debug, Clone)]
18pub struct ImportSymbol {
19    /// The symbol name as it appears in the IMPORTS clause.
20    pub name: String,
21    /// Source location of this symbol reference.
22    pub span: Span,
23}
24
25/// A group of symbols imported from a single source module.
26///
27/// Each MIB module's IMPORTS section is represented as a list of `Import`
28/// entries, one per source module.
29#[derive(Debug, Clone)]
30pub struct Import {
31    /// Name of the module being imported from.
32    pub module: String,
33    /// Symbols imported from this module.
34    pub symbols: Vec<ImportSymbol>,
35}
36
37/// An endpoint in a resolved SIZE or value range constraint.
38///
39/// Signed and unsigned literals remain distinct so values above [`i64::MAX`]
40/// are represented exactly. `MIN` and `MAX` are retained when no parent or
41/// base-type bound is available to give them a concrete value. Malformed or
42/// unsupported literals retain their source text in [`Raw`](Self::Raw).
43///
44/// The raw representation means this enum is no longer `Copy`; clone endpoints
45/// when an owned value is required.
46#[derive(Debug, Clone, PartialEq, Eq, Hash)]
47pub enum RangeBound {
48    /// A signed integer literal.
49    Signed(i64),
50    /// An unsigned integer literal.
51    Unsigned(u64),
52    /// The `MIN` keyword.
53    Min,
54    /// The `MAX` keyword.
55    Max,
56    /// An unresolved endpoint preserving its source text.
57    Raw(String),
58}
59
60impl RangeBound {
61    /// Return the endpoint as `i64` when it is concrete and representable.
62    pub fn as_i64(&self) -> Option<i64> {
63        match self {
64            Self::Signed(value) => Some(*value),
65            Self::Unsigned(value) => i64::try_from(*value).ok(),
66            Self::Min | Self::Max | Self::Raw(_) => None,
67        }
68    }
69
70    /// Return the endpoint as `u64` when it is concrete and non-negative.
71    pub fn as_u64(&self) -> Option<u64> {
72        match self {
73            Self::Signed(value) => u64::try_from(*value).ok(),
74            Self::Unsigned(value) => Some(*value),
75            Self::Min | Self::Max | Self::Raw(_) => None,
76        }
77    }
78
79    /// Return whether the endpoint is a signed or unsigned number.
80    pub fn is_concrete(&self) -> bool {
81        matches!(self, Self::Signed(_) | Self::Unsigned(_))
82    }
83
84    pub(crate) fn cmp_value(&self, other: &Self) -> Option<std::cmp::Ordering> {
85        use std::cmp::Ordering;
86        match (self, other) {
87            (Self::Raw(_), _) | (_, Self::Raw(_)) => None,
88            (Self::Min, Self::Min) | (Self::Max, Self::Max) => Some(Ordering::Equal),
89            (Self::Min, _) | (_, Self::Max) => Some(Ordering::Less),
90            (Self::Max, _) | (_, Self::Min) => Some(Ordering::Greater),
91            (Self::Signed(left), Self::Signed(right)) => Some(left.cmp(right)),
92            (Self::Unsigned(left), Self::Unsigned(right)) => Some(left.cmp(right)),
93            (Self::Signed(left), Self::Unsigned(right)) => Some(if *left < 0 {
94                Ordering::Less
95            } else {
96                (*left as u64).cmp(right)
97            }),
98            (Self::Unsigned(left), Self::Signed(right)) => Some(if *right < 0 {
99                Ordering::Greater
100            } else {
101                left.cmp(&(*right as u64))
102            }),
103        }
104    }
105}
106
107impl fmt::Display for RangeBound {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        match self {
110            Self::Signed(value) => write!(f, "{value}"),
111            Self::Unsigned(value) => write!(f, "{value}"),
112            Self::Min => f.write_str("MIN"),
113            Self::Max => f.write_str("MAX"),
114            Self::Raw(value) => f.write_str(value),
115        }
116    }
117}
118
119/// A min..max constraint range, used for both SIZE and value constraints.
120///
121/// For single-value constraints (e.g. `SIZE (6)`), `min` equals `max`.
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct Range {
124    /// Lower bound (inclusive).
125    pub min: RangeBound,
126    /// Upper bound (inclusive). Equal to `min` for single-value ranges.
127    pub max: RangeBound,
128    /// Source location of this constraint.
129    pub span: Span,
130}
131
132impl Range {
133    /// Return whether both endpoints are concrete numeric values.
134    pub fn is_resolved(&self) -> bool {
135        self.min.is_concrete() && self.max.is_concrete()
136    }
137
138    pub(crate) fn contains_i64(&self, value: i64) -> bool {
139        let value = RangeBound::Signed(value);
140        self.min
141            .cmp_value(&value)
142            .is_some_and(|ordering| ordering.is_le())
143            && self
144                .max
145                .cmp_value(&value)
146                .is_some_and(|ordering| ordering.is_ge())
147    }
148
149    pub(crate) fn contains_u64(&self, value: u64) -> bool {
150        let value = RangeBound::Unsigned(value);
151        self.min
152            .cmp_value(&value)
153            .is_some_and(|ordering| ordering.is_le())
154            && self
155                .max
156                .cmp_value(&value)
157                .is_some_and(|ordering| ordering.is_ge())
158    }
159}
160
161impl fmt::Display for Range {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        if self.min == self.max {
164            write!(f, "{}", self.min)
165        } else {
166            write!(f, "{}..{}", self.min, self.max)
167        }
168    }
169}
170
171/// A labeled integer from an enumeration or BITS definition.
172///
173/// Used in OBJECT-TYPE SYNTAX enumerations, BITS definitions, and
174/// refinement clauses in compliance and capability statements.
175#[derive(Debug, Clone)]
176pub struct NamedValue {
177    /// The textual label.
178    pub label: String,
179    /// The integer value associated with this label.
180    pub value: i64,
181    /// Source location of this named value.
182    pub span: Span,
183}
184
185/// Finds a named value by label in a slice.
186pub(crate) fn find_named_value<'a>(
187    values: &'a [NamedValue],
188    label: &str,
189) -> Option<&'a NamedValue> {
190    values.iter().find(|nv| nv.label == label)
191}
192
193/// A module revision entry from a MODULE-IDENTITY REVISION clause.
194#[derive(Debug, Clone)]
195pub struct Revision {
196    /// Revision timestamp string.
197    pub date: String,
198    /// Free-text description of what changed.
199    pub description: String,
200    /// Source location of this revision clause.
201    pub span: Span,
202}
203
204/// An index component from a table row's INDEX clause.
205///
206/// Indexes can be object-backed (referencing a column like `ifIndex`) or
207/// bare-type indexes (using a type name directly). The
208/// [`encoding`](Self::encoding) field indicates how this index component
209/// is encoded on the wire (see [`IndexEncoding`]).
210#[derive(Debug, Clone)]
211pub struct IndexEntry {
212    /// Name of the index object.
213    pub name: String,
214    /// Resolved object id, if found.
215    pub object: Option<ObjectId>,
216    /// Resolved type of the index object, if found.
217    pub type_id: Option<TypeId>,
218    /// True if this index uses the IMPLIED keyword.
219    pub implied: bool,
220    /// Wire encoding inferred from the index object's type.
221    pub encoding: IndexEncoding,
222    /// Source location of this index entry.
223    pub span: Span,
224}
225
226/// Classify the index encoding from the object's resolved base type and size constraints.
227pub(crate) fn classify_index_encoding(
228    base: BaseType,
229    implied: bool,
230    sizes: &[Range],
231) -> IndexEncoding {
232    match base {
233        BaseType::Integer32
234        | BaseType::Unsigned32
235        | BaseType::Gauge32
236        | BaseType::TimeTicks
237        | BaseType::Counter32
238        | BaseType::Counter64 => IndexEncoding::Integer,
239        BaseType::IpAddress => IndexEncoding::IpAddress,
240        BaseType::OctetString | BaseType::Opaque | BaseType::Bits => {
241            if implied {
242                IndexEncoding::Implied
243            } else if is_fixed_size(sizes) {
244                IndexEncoding::FixedString
245            } else {
246                IndexEncoding::LengthPrefixed
247            }
248        }
249        BaseType::ObjectIdentifier => {
250            if implied {
251                IndexEncoding::Implied
252            } else {
253                IndexEncoding::LengthPrefixed
254            }
255        }
256        _ => IndexEncoding::Unknown,
257    }
258}
259
260pub(crate) fn is_fixed_size(sizes: &[Range]) -> bool {
261    sizes.len() == 1
262        && sizes[0].min == sizes[0].max
263        && sizes[0].min.as_u64().is_some_and(|value| value > 0)
264}
265
266/// Discriminant for the kind of value in a [`DefVal`].
267///
268/// Mirrors the [`DefValValue`] variants but as a simple `Copy` enum,
269/// useful for matching or display without borrowing the value.
270#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
271#[repr(u8)]
272pub enum DefValKind {
273    /// No default value specified.
274    Unset = 0,
275    /// Signed integer value.
276    Int = 1,
277    /// Unsigned integer value.
278    Uint = 2,
279    /// Quoted string value.
280    String = 3,
281    /// Raw byte sequence (hex string).
282    Bytes = 4,
283    /// Enumeration label.
284    Enum = 5,
285    /// Set of BITS labels.
286    Bits = 6,
287    /// Object identifier value.
288    Oid = 7,
289}
290
291impl fmt::Display for DefValKind {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        f.write_str(match self {
294            DefValKind::Unset => "unset",
295            DefValKind::Int => "int",
296            DefValKind::Uint => "uint",
297            DefValKind::String => "string",
298            DefValKind::Bytes => "bytes",
299            DefValKind::Enum => "enum",
300            DefValKind::Bits => "bits",
301            DefValKind::Oid => "oid",
302        })
303    }
304}
305
306/// A DEFVAL clause value with both the interpreted value and the raw MIB syntax string.
307///
308/// The [`kind`](DefVal::kind) method returns the discriminant, [`value`](DefVal::value)
309/// returns the interpreted value, and [`raw`](DefVal::raw) returns the original
310/// syntax as written in the MIB source.
311///
312/// Constructed via the named constructors ([`DefVal::int`], [`DefVal::string`], etc.).
313#[derive(Debug, Clone)]
314pub struct DefVal {
315    pub(crate) kind: DefValKind,
316    pub(crate) value: DefValValue,
317    pub(crate) raw: String,
318}
319
320/// The interpreted value of a DEFVAL clause.
321///
322/// Each variant corresponds to a [`DefValKind`] discriminant.
323#[derive(Debug, Clone)]
324pub enum DefValValue {
325    /// No value (corresponds to `DefValKind::Unset`).
326    None,
327    /// Signed integer.
328    Int(i64),
329    /// Unsigned integer.
330    Uint(u64),
331    /// Quoted string.
332    String(String),
333    /// Raw byte sequence.
334    Bytes(Vec<u8>),
335    /// Enumeration label.
336    Enum(String),
337    /// Set of BITS labels.
338    Bits(Vec<String>),
339    /// Object identifier.
340    Oid(Oid),
341}
342
343impl DefVal {
344    /// Create a default value indicating no value was specified.
345    pub fn unset() -> Self {
346        DefVal {
347            kind: DefValKind::Unset,
348            value: DefValValue::None,
349            raw: String::new(),
350        }
351    }
352
353    /// Create a signed integer default value.
354    pub fn int(v: i64, raw: String) -> Self {
355        DefVal {
356            kind: DefValKind::Int,
357            value: DefValValue::Int(v),
358            raw,
359        }
360    }
361
362    /// Create an unsigned integer default value.
363    pub fn uint(v: u64, raw: String) -> Self {
364        DefVal {
365            kind: DefValKind::Uint,
366            value: DefValValue::Uint(v),
367            raw,
368        }
369    }
370
371    /// Create a quoted string default value.
372    pub fn string(v: String, raw: String) -> Self {
373        DefVal {
374            kind: DefValKind::String,
375            value: DefValValue::String(v),
376            raw,
377        }
378    }
379
380    /// Create a raw byte sequence default value (from a hex string).
381    pub fn bytes(v: Vec<u8>, raw: String) -> Self {
382        DefVal {
383            kind: DefValKind::Bytes,
384            value: DefValValue::Bytes(v),
385            raw,
386        }
387    }
388
389    /// Create an enumeration label default value.
390    pub fn enumeration(label: String, raw: String) -> Self {
391        DefVal {
392            kind: DefValKind::Enum,
393            value: DefValValue::Enum(label),
394            raw,
395        }
396    }
397
398    /// Create a BITS set default value.
399    pub fn bits(labels: Vec<String>, raw: String) -> Self {
400        DefVal {
401            kind: DefValKind::Bits,
402            value: DefValValue::Bits(labels),
403            raw,
404        }
405    }
406
407    /// Create an OID default value.
408    pub fn oid(oid: Oid, raw: String) -> Self {
409        DefVal {
410            kind: DefValKind::Oid,
411            value: DefValValue::Oid(oid),
412            raw,
413        }
414    }
415
416    /// Return the [`DefValKind`] discriminant.
417    pub fn kind(&self) -> DefValKind {
418        self.kind
419    }
420
421    /// Return the raw MIB syntax string as written in the source.
422    pub fn raw(&self) -> &str {
423        &self.raw
424    }
425
426    /// Return the interpreted [`DefValValue`].
427    pub fn value(&self) -> &DefValValue {
428        &self.value
429    }
430
431    /// Return `true` if no default value was specified.
432    pub fn is_unset(&self) -> bool {
433        self.kind == DefValKind::Unset
434    }
435}
436
437impl fmt::Display for DefVal {
438    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439        match &self.value {
440            DefValValue::None => Ok(()),
441            DefValValue::Int(v) => write!(f, "{v}"),
442            DefValValue::Uint(v) => write!(f, "{v}"),
443            DefValValue::String(v) => {
444                write!(f, "\"{}\"", v.replace('"', "\\\""))
445            }
446            DefValValue::Bytes(b) => {
447                write!(f, "0x")?;
448                for byte in b {
449                    write!(f, "{byte:02X}")?;
450                }
451                Ok(())
452            }
453            DefValValue::Enum(label) => f.write_str(label),
454            DefValValue::Bits(labels) => {
455                if labels.is_empty() {
456                    write!(f, "{{ }}")
457                } else {
458                    write!(f, "{{ {} }}", labels.join(", "))
459                }
460            }
461            DefValValue::Oid(_) => f.write_str(&self.raw),
462        }
463    }
464}
465
466/// A MODULE clause within a [`ComplianceData`](super::compliance::ComplianceData) definition.
467///
468/// Specifies the mandatory groups and optional object refinements required
469/// for conformance to a particular module.
470#[derive(Debug, Clone)]
471pub struct ComplianceModule {
472    /// Name of the module this clause applies to.
473    pub module_name: String,
474    /// Groups required for conformance.
475    pub mandatory_groups: Vec<String>,
476    /// Optional GROUP refinements.
477    pub groups: Vec<ComplianceGroup>,
478    /// Optional OBJECT refinements.
479    pub objects: Vec<ComplianceObject>,
480    /// Source location of this MODULE clause.
481    pub span: Span,
482}
483
484/// A GROUP clause within a [`ComplianceModule`].
485///
486/// Represents a conditionally required group, with a description of the
487/// conditions under which it is required.
488#[derive(Debug, Clone)]
489pub struct ComplianceGroup {
490    /// Name of the conditionally required group.
491    pub group: String,
492    /// Description of when this group is required.
493    pub description: String,
494    /// Source location of this GROUP clause.
495    pub span: Span,
496}
497
498/// An OBJECT refinement within a [`ComplianceModule`].
499///
500/// May narrow the syntax, write-syntax, or minimum access level for an
501/// object beyond what the base OBJECT-TYPE definition requires.
502#[derive(Debug, Clone)]
503pub struct ComplianceObject {
504    /// Name of the refined object.
505    pub object: String,
506    /// Restricted SYNTAX, if any.
507    pub syntax: Option<SyntaxConstraints>,
508    /// Restricted WRITE-SYNTAX, if any.
509    pub write_syntax: Option<SyntaxConstraints>,
510    /// Minimum required access level, if specified.
511    pub min_access: Option<Access>,
512    /// Description of the refinement.
513    pub description: String,
514    /// Source location of this OBJECT clause.
515    pub span: Span,
516}
517
518/// A SUPPORTS clause within a [`CapabilityData`](super::capability::CapabilityData) definition.
519///
520/// Lists the included groups from a supported module and any object or
521/// notification variations the agent implements.
522#[derive(Debug, Clone)]
523pub struct CapabilitiesModule {
524    /// Name of the supported module.
525    pub module_name: String,
526    /// Groups included from this module.
527    pub includes: Vec<String>,
528    /// Object VARIATION clauses.
529    pub object_variations: Vec<ObjectVariation>,
530    /// Notification VARIATION clauses.
531    pub notification_variations: Vec<NotificationVariation>,
532    /// Source location of this SUPPORTS clause.
533    pub span: Span,
534}
535
536/// An object VARIATION within a [`CapabilitiesModule`].
537///
538/// Describes implementation-specific deviations for a single object,
539/// including restricted syntax, access overrides, and default values.
540#[derive(Debug, Clone)]
541pub struct ObjectVariation {
542    /// Name of the varied object.
543    pub object: String,
544    /// Restricted SYNTAX, if any.
545    pub syntax: Option<SyntaxConstraints>,
546    /// Restricted WRITE-SYNTAX, if any.
547    pub write_syntax: Option<SyntaxConstraints>,
548    /// Overridden access level, if any.
549    pub access: Option<Access>,
550    /// Objects required for row creation.
551    pub creation_requires: Vec<String>,
552    /// Implementation-specific default value, if any.
553    pub def_val: Option<DefVal>,
554    /// Description of this variation.
555    pub description: String,
556    /// Source location of this VARIATION clause.
557    pub span: Span,
558}
559
560/// A notification VARIATION within a [`CapabilitiesModule`].
561///
562/// Describes implementation-specific deviations for a single notification.
563#[derive(Debug, Clone)]
564pub struct NotificationVariation {
565    /// Name of the varied notification.
566    pub notification: String,
567    /// Overridden access level, if any.
568    pub access: Option<Access>,
569    /// Description of this variation.
570    pub description: String,
571    /// Source location of this VARIATION clause.
572    pub span: Span,
573}
574
575/// Inline syntax constraints from a VARIATION SYNTAX/WRITE-SYNTAX clause
576/// or a MODULE-COMPLIANCE OBJECT refinement.
577///
578/// Represents a restricted view of a type with narrowed ranges, enums, or
579/// BITS values.
580#[derive(Debug, Clone)]
581pub struct SyntaxConstraints {
582    /// Resolved type, if any.
583    pub type_id: Option<TypeId>,
584    /// Effective SIZE constraints after intersection with the resolved type.
585    pub sizes: Vec<Range>,
586    /// SIZE constraints declared directly in this syntax clause.
587    pub declared_sizes: Vec<Range>,
588    /// Whether a SIZE constraint was explicitly declared.
589    ///
590    /// When this is true and `sizes` is empty, the declared constraint has an
591    /// empty intersection with the inherited or base-type constraint.
592    pub sizes_constrained: bool,
593    /// Effective value range constraints after intersection with the resolved type.
594    pub ranges: Vec<Range>,
595    /// Value range constraints declared directly in this syntax clause.
596    pub declared_ranges: Vec<Range>,
597    /// Whether a value range constraint was explicitly declared.
598    ///
599    /// When this is true and `ranges` is empty, the declared constraint has an
600    /// empty intersection with the inherited or base-type constraint.
601    pub ranges_constrained: bool,
602    /// Restricted enumeration values.
603    pub enums: Vec<NamedValue>,
604    /// Restricted BITS values.
605    pub bits: Vec<NamedValue>,
606}
607
608/// SMIv1 TRAP-TYPE specific fields.
609///
610/// Present on [`NotificationData`](super::notification::NotificationData)
611/// instances that originate from TRAP-TYPE definitions.
612#[derive(Debug, Clone)]
613pub struct TrapInfo {
614    /// ENTERPRISE OID name from the TRAP-TYPE definition.
615    pub enterprise: String,
616    /// Numeric trap identifier (the specific-trap number).
617    pub trap_number: u32,
618}
619
620/// Identifies the category of an unresolved reference.
621#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
622#[repr(u8)]
623pub enum UnresolvedKind {
624    /// An unresolved IMPORTS symbol.
625    Import = 0,
626    /// An unresolved type reference.
627    Type = 1,
628    /// An unresolved OID component.
629    Oid = 2,
630    /// An unresolved INDEX object.
631    Index = 3,
632    /// An unresolved OBJECTS member of a notification.
633    NotificationObject = 4,
634}
635
636impl fmt::Display for UnresolvedKind {
637    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
638        f.write_str(match self {
639            UnresolvedKind::Import => "import",
640            UnresolvedKind::Type => "type",
641            UnresolvedKind::Oid => "oid",
642            UnresolvedKind::Index => "index",
643            UnresolvedKind::NotificationObject => "notification-object",
644        })
645    }
646}
647
648/// An unresolved symbol reference collected during resolution.
649///
650/// Available via [`Mib::unresolved`](super::mib::Mib::unresolved).
651#[derive(Debug, Clone)]
652pub struct UnresolvedRef {
653    /// What kind of reference failed to resolve.
654    pub kind: UnresolvedKind,
655    /// The symbol name that could not be resolved.
656    pub symbol: String,
657    /// The module where the reference was used.
658    pub module: String,
659    /// Human-readable explanation of why resolution failed.
660    pub reason: String,
661}
662
663/// A symbolic name referenced in an OID value assignment (e.g. `enterprises` in
664/// `{ enterprises 9 }`).
665#[derive(Debug, Clone)]
666pub struct OidRef {
667    /// The symbolic name referenced in the OID assignment.
668    pub name: String,
669    /// Source location of this reference.
670    pub span: Span,
671}
672
673// Arena index types for the resolved model.
674macro_rules! define_id {
675    ($(#[$attr:meta])* $name:ident) => {
676        $(#[$attr])*
677        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
678        pub struct $name(pub(crate) u32);
679
680        impl $name {
681            pub(crate) fn new(index: u32) -> Self {
682                Self(index)
683            }
684
685            /// Return the raw arena index as a `u32`.
686            pub fn index(self) -> u32 {
687                self.0
688            }
689        }
690    };
691}
692
693define_id!(
694    /// Index into the OidTree's node arena.
695    NodeId
696);
697define_id!(
698    /// Index into the Mib's object arena.
699    ObjectId
700);
701define_id!(
702    /// Index into the Mib's type arena.
703    TypeId
704);
705define_id!(
706    /// Index into the Mib's module arena.
707    ModuleId
708);
709define_id!(
710    /// Index into the Mib's notification arena.
711    NotificationId
712);
713define_id!(
714    /// Index into the Mib's group arena.
715    GroupId
716);
717define_id!(
718    /// Index into the Mib's compliance arena.
719    ComplianceId
720);
721define_id!(
722    /// Index into the Mib's capability arena.
723    CapabilityId
724);