Packages

A comprehensive SNMP toolkit for Elixir featuring a unified API, pure Elixir implementation, and powerful device simulation. Perfect for network monitoring, testing, and development with support for SNMP operations, MIB management, and realistic device simulation.

Current section

Files

Jump to
snmpkit lib snmpkit snmp_lib mib ast.ex
Raw

lib/snmpkit/snmp_lib/mib/ast.ex

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