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);