towerops/lib/snmpkit/snmp_lib/mib/ast.ex
2026-01-23 12:52:17 -06:00

496 lines
13 KiB
Elixir

defmodule SnmpKit.SnmpLib.MIB.AST do
@moduledoc """
Abstract Syntax Tree definitions for MIB compilation.
Faithfully ported from Erlang OTP snmpc_mib_gram.yrl and related structures.
Provides the complete AST representation needed for SNMP MIB compilation.
"""
@type line_number :: integer()
# Top-level MIB structure
@type mib :: %{
__type__: :mib,
name: binary(),
last_updated: binary() | nil,
organization: binary() | nil,
contact_info: binary() | nil,
description: binary() | nil,
revision_history: [revision()],
imports: [import()],
definitions: [definition()],
oid_tree: oid_tree(),
metadata: metadata()
}
@type revision :: %{
__type__: :revision,
date: binary(),
description: binary(),
line: line_number()
}
# Import statement from other MIBs
@type import :: %{
__type__: :import,
symbols: [binary()],
from_module: binary(),
line: line_number()
}
# MIB definitions (various types)
@type definition ::
object_type()
| object_identity()
| object_group()
| notification_type()
| notification_group()
| module_identity()
| module_compliance()
| agent_capabilities()
| textual_convention()
| trap_type()
| object_identifier_assignment()
# Object Type Definition (OBJECT-TYPE)
@type object_type :: %{
__type__: :object_type,
name: binary(),
syntax: syntax(),
units: binary() | nil,
max_access: access_level(),
status: status(),
description: binary(),
reference: binary() | nil,
index: index_spec() | nil,
augments: binary() | nil,
defval: default_value() | nil,
oid: oid(),
line: line_number()
}
# Object Identity Definition (OBJECT-IDENTITY)
@type object_identity :: %{
__type__: :object_identity,
name: binary(),
status: status(),
description: binary(),
reference: binary() | nil,
oid: oid(),
line: line_number()
}
# Module Identity Definition (MODULE-IDENTITY) - SNMPv2 only
@type module_identity :: %{
__type__: :module_identity,
name: binary(),
last_updated: binary(),
organization: binary(),
contact_info: binary(),
description: binary(),
revision_history: [revision()],
oid: oid(),
line: line_number()
}
# Object Group Definition (OBJECT-GROUP)
@type object_group :: %{
__type__: :object_group,
name: binary(),
objects: [binary()],
status: status(),
description: binary(),
reference: binary() | nil,
oid: oid(),
line: line_number()
}
# Notification Type Definition (NOTIFICATION-TYPE)
@type notification_type :: %{
__type__: :notification_type,
name: binary(),
objects: [binary()],
status: status(),
description: binary(),
reference: binary() | nil,
oid: oid(),
line: line_number()
}
# Notification Group Definition (NOTIFICATION-GROUP)
@type notification_group :: %{
__type__: :notification_group,
name: binary(),
notifications: [binary()],
status: status(),
description: binary(),
reference: binary() | nil,
oid: oid(),
line: line_number()
}
# Module Compliance Definition (MODULE-COMPLIANCE)
@type module_compliance :: %{
__type__: :module_compliance,
name: binary(),
status: status(),
description: binary(),
reference: binary() | nil,
modules: [compliance_module()],
oid: oid(),
line: line_number()
}
@type compliance_module :: %{
module_name: binary(),
mandatory_groups: [binary()],
compliance_objects: [compliance_object()]
}
@type compliance_object :: %{
object: binary(),
syntax: syntax() | nil,
write_syntax: syntax() | nil,
access: access_level() | nil,
description: binary() | nil
}
# Agent Capabilities Definition (AGENT-CAPABILITIES)
@type agent_capabilities :: %{
__type__: :agent_capabilities,
name: binary(),
product_release: binary(),
status: status(),
description: binary(),
reference: binary() | nil,
modules: [capability_module()],
oid: oid(),
line: line_number()
}
@type capability_module :: %{
module_name: binary(),
includes: [binary()],
variations: [object_variation()]
}
@type object_variation :: %{
object: binary(),
syntax: syntax() | nil,
write_syntax: syntax() | nil,
access: access_level() | nil,
creation: boolean(),
defval: default_value() | nil,
description: binary()
}
# Textual Convention Definition (TEXTUAL-CONVENTION)
@type textual_convention :: %{
__type__: :textual_convention,
name: binary(),
display_hint: binary() | nil,
status: status(),
description: binary(),
reference: binary() | nil,
syntax: syntax(),
line: line_number()
}
# Trap Type Definition (TRAP-TYPE) - SNMPv1 only
@type trap_type :: %{
__type__: :trap_type,
name: binary(),
enterprise: oid(),
variables: [binary()],
description: binary() | nil,
reference: binary() | nil,
trap_number: integer(),
line: line_number()
}
# Object Identifier Assignment
@type object_identifier_assignment :: %{
__type__: :object_identifier_assignment,
name: binary(),
oid: oid(),
line: line_number()
}
# Syntax Definitions
@type syntax ::
primitive_syntax() | constructed_syntax() | named_syntax()
@type primitive_syntax ::
:integer
| :octet_string
| :object_identifier
| :null
| :real
| {:integer, constraints()}
| {:octet_string, constraints()}
| {:object_identifier, constraints()}
@type constructed_syntax ::
{:sequence, [sequence_element()]}
| {:sequence_of, syntax()}
| {:choice, [choice_element()]}
| {:bit_string, [named_bit()]}
@type named_syntax ::
{:named_type, binary()}
| {:application_type, tag(), syntax()}
| {:context_type, tag(), syntax()}
@type sequence_element :: %{
name: binary(),
syntax: syntax(),
optional: boolean(),
default: default_value() | nil
}
@type choice_element :: %{
name: binary(),
syntax: syntax()
}
@type named_bit :: %{
name: binary(),
bit_number: integer()
}
@type tag :: integer()
# Constraint Definitions
@type constraints :: [constraint()]
@type constraint ::
{:size, size_constraint()}
| {:range, range_constraint()}
| {:named_values, [named_value()]}
| {:contained_subtype, syntax()}
@type size_constraint ::
integer() | {integer(), integer()} | [size_range()]
@type range_constraint ::
{integer(), integer()} | [range_spec()]
@type size_range :: integer() | {integer(), integer()}
@type range_spec :: integer() | {integer(), integer()}
@type named_value :: %{
name: binary(),
value: integer()
}
# Index Specifications
@type index_spec ::
{:index, [index_element()]}
| {:implied, [index_element()]}
@type index_element ::
binary() | {:implied, binary()}
# Access Levels
@type access_level ::
:not_accessible
| :accessible_for_notify
| :read_only
| :read_write
| :read_create
# Legacy SNMPv1 access levels
| :write_only
# Status Values
@type status :: :current | :deprecated | :obsolete | :mandatory
# Default Values
@type default_value ::
nil
| integer()
| binary()
| atom()
| [default_value()]
| {:named_value, binary()}
| {:bit_string, [binary()]}
# OID Representation
@type oid :: [integer()] | [oid_element()]
@type oid_element :: integer() | {binary(), integer()}
# Performance: Use ETS for large OID trees in production
@type oid_tree :: :ets.tid() | map()
# Compilation Metadata
@type metadata :: %{
compile_time: DateTime.t(),
compiler_version: binary(),
source_file: Path.t(),
snmp_version: :v1 | :v2c,
dependencies: [binary()],
warnings: [binary()],
line_count: integer()
}
# Helper Functions for AST Construction
@doc """
Create a new MIB AST node.
"""
@spec new_mib(binary(), keyword()) :: mib()
def new_mib(name, opts \\ []) do
%{
__type__: :mib,
name: name,
last_updated: opts[:last_updated],
organization: opts[:organization],
contact_info: opts[:contact_info],
description: opts[:description],
revision_history: opts[:revision_history] || [],
imports: opts[:imports] || [],
definitions: opts[:definitions] || [],
oid_tree: opts[:oid_tree] || %{},
metadata: opts[:metadata] || %{}
}
end
@doc """
Create a new object type definition.
"""
@spec new_object_type(binary(), keyword()) :: object_type()
def new_object_type(name, opts) do
%{
__type__: :object_type,
name: name,
syntax: opts[:syntax],
units: opts[:units],
max_access: opts[:max_access],
status: opts[:status],
description: opts[:description],
reference: opts[:reference],
index: opts[:index],
augments: opts[:augments],
defval: opts[:defval],
oid: opts[:oid],
line: opts[:line]
}
end
@doc """
Create a new object identity definition.
"""
@spec new_object_identity(binary(), keyword()) :: object_identity()
def new_object_identity(name, opts) do
%{
__type__: :object_identity,
name: name,
status: opts[:status],
description: opts[:description],
reference: opts[:reference],
oid: opts[:oid],
line: opts[:line]
}
end
@doc """
Create a new import statement.
"""
@spec new_import([binary()], binary(), line_number()) :: import()
def new_import(symbols, from_module, line) do
%{
__type__: :import,
symbols: symbols,
from_module: from_module,
line: line
}
end
@doc """
Determine SNMP version based on MIB content.
Per Erlang implementation: presence of MODULE-IDENTITY indicates SNMPv2.
"""
@spec determine_snmp_version([definition()]) :: :v1 | :v2c
def determine_snmp_version(definitions) do
has_module_identity =
Enum.any?(definitions, fn
%{__type__: :module_identity} -> true
_ -> false
end)
if has_module_identity, do: :v2c, else: :v1
end
@doc """
Build an OID tree from definitions for fast lookups.
"""
@spec build_oid_tree([definition()]) :: :ets.tid()
def build_oid_tree(definitions) do
# Use ETS for performance with large MIBs
tid = :ets.new(:oid_tree, [:set, :protected])
Enum.each(definitions, fn definition ->
case extract_oid_mapping(definition) do
{name, oid} when is_list(oid) ->
:ets.insert(tid, {oid, name})
:ets.insert(tid, {name, oid})
nil ->
:ok
end
end)
tid
end
# Extract OID mapping from definition
defp extract_oid_mapping(%{name: name, oid: oid}) when is_list(oid) do
{name, oid}
end
defp extract_oid_mapping(_), do: nil
@doc """
Validate AST node structure.
"""
@spec validate_node(term()) :: {:ok, term()} | {:error, binary()}
def validate_node(%{__type__: type} = node)
when type in [
:mib,
:object_type,
:object_identity,
:module_identity,
:object_group,
:notification_type,
:notification_group,
:module_compliance,
:agent_capabilities,
:textual_convention,
:trap_type,
:object_identifier_assignment,
:import,
:revision
] do
{:ok, node}
end
def validate_node(node) do
{:error, "Invalid AST node: #{inspect(node)}"}
end
@doc """
Pretty print AST node for debugging.
"""
@spec pretty_print(term()) :: binary()
def pretty_print(%{__type__: type, name: name}) do
"#{type}: #{name}"
end
def pretty_print(%{__type__: type}) do
"#{type}"
end
def pretty_print(other) do
inspect(other)
end
end