defmodule SnmpKit.SnmpMgr do @moduledoc """ Lightweight SNMP client library for Elixir. This library provides a simple, stateless interface for SNMP operations without requiring heavyweight management processes or configurations. ## Core Operations - `get/3` - Single OID retrieval - `get_next/3` - Get next OID in tree - `get_bulk/3` - Bulk retrieval (SNMPv2c) - `walk/3` - Walk subtree - `set/4` - Set OID value """ alias SnmpKit.SnmpLib.OID alias SnmpKit.SnmpMgr.Bulk alias SnmpKit.SnmpMgr.Config alias SnmpKit.SnmpMgr.Core alias SnmpKit.SnmpMgr.Format alias SnmpKit.SnmpMgr.MIB alias SnmpKit.SnmpMgr.Walk @type target :: binary() | tuple() | map() @type oid :: binary() | list() @type opts :: keyword() @doc """ Performs an SNMP GET request. ## Parameters - `target` - The target device (e.g., "192.168.1.1:161" or "device.local") - `oid` - The OID to retrieve (string or list format) - `opts` - Options including :community, :timeout, :retries ## Examples {:ok, value} = SnmpMgr.get("device.local:161", "sysDescr.0", community: "public") """ def get(target, oid, opts \\ []) do merged_opts = Config.merge_opts(opts) case Core.send_get_request_with_type(target, oid, merged_opts) do {:ok, {oid_str, type, value}} -> {:ok, Format.enrich_varbind({oid_str, type, value}, merged_opts)} {:error, reason} -> {:error, reason} end end @doc """ Performs an SNMP GETNEXT request. ## Parameters - `target` - The target device - `oid` - The starting OID - `opts` - Options including :community, :timeout, :retries """ def get_next(target, oid, opts \\ []) do merged_opts = Config.merge_opts(opts) case Core.send_get_next_request(target, oid, merged_opts) do {:ok, {oid_string, type, value}} -> {:ok, Format.enrich_varbind({oid_string, type, value}, merged_opts)} {:error, reason} -> {:error, reason} end end @doc """ Performs an SNMP SET request. ## Parameters - `target` - The target device - `oid` - The OID to set - `value` - The value to set - `opts` - Options including :community, :timeout, :retries """ def set(target, oid, value, opts \\ []) do merged_opts = Config.merge_opts(opts) Core.send_set_request(target, oid, value, merged_opts) end @doc """ Performs an asynchronous SNMP GET request. Returns immediately with a reference. The caller will receive a message with the result. """ def get_async(target, oid, opts \\ []) do merged_opts = Config.merge_opts(opts) Core.send_get_request_async(target, oid, merged_opts) end @doc """ Performs an SNMP GETBULK request (SNMPv2c only). GETBULK is more efficient than multiple GETNEXT requests for retrieving large amounts of data. ## Parameters - `target` - The target device - `oid` - The starting OID - `opts` - Options including :non_repeaters, :max_repetitions, :community, :timeout """ @spec get_bulk(target(), oid(), opts()) :: {:ok, [{list(), atom(), any()}]} | {:error, any()} def get_bulk(target, oid, opts \\ []) do case Keyword.get(opts, :version) do :v1 -> {:error, {:unsupported_operation, :get_bulk_requires_v2c}} :v3 -> {:error, {:unsupported_operation, :get_bulk_requires_v2c}} _ -> merged_opts = opts |> Keyword.put(:version, :v2c) |> then(&Config.merge_opts/1) case Core.send_get_bulk_request(target, oid, merged_opts) do {:ok, results} -> {:ok, Format.enrich_varbinds(results, merged_opts)} {:error, reason} -> {:error, reason} end end end @doc """ Performs an asynchronous SNMP GETBULK request. Returns immediately with a reference. The caller will receive a message with the result. """ def get_bulk_async(target, oid, opts \\ []) do case Keyword.get(opts, :version) do :v1 -> {:error, {:unsupported_operation, :get_bulk_requires_v2c}} :v3 -> {:error, {:unsupported_operation, :get_bulk_requires_v2c}} _ -> merged_opts = opts |> Keyword.put(:version, :v2c) |> then(&Config.merge_opts/1) Core.send_get_bulk_request_async(target, oid, merged_opts) end end @doc """ Performs an SNMP walk operation using iterative GETNEXT requests. Walks the SNMP tree starting from the given OID and returns all OID/value pairs found under that subtree. ## Parameters - `target` - The target device - `root_oid` - The starting OID for the walk - `opts` - Options including :community, :timeout, :max_repetitions """ @spec walk(target(), oid(), opts()) :: {:ok, [{list(), atom(), any()}]} | {:error, any()} def walk(target, root_oid, opts \\ []) do merged_opts = Config.merge_opts(opts) case Walk.walk(target, root_oid, merged_opts) do {:ok, results} -> {:ok, Format.enrich_varbinds(results, merged_opts)} {:error, reason} -> {:error, reason} end end @doc """ Walks an SNMP table and returns all entries. ## Parameters - `target` - The target device - `table_oid` - The table OID to walk - `opts` - Options including :community, :timeout """ @spec walk_table(target(), oid(), opts()) :: {:ok, [{list(), atom(), any()}]} | {:error, any()} def walk_table(target, table_oid, opts \\ []) do merged_opts = Config.merge_opts(opts) case Walk.walk_table(target, table_oid, merged_opts) do {:ok, results} -> {:ok, Format.enrich_varbinds(results, merged_opts)} {:error, reason} -> {:error, reason} end end @doc """ Gets a specific column from an SNMP table. ## Parameters - `target` - The target device - `table_oid` - The table OID - `column` - The column number or name - `opts` - Options including :community, :timeout """ def get_column(target, table_oid, column, opts \\ []) do with {:ok, resolved_table_oid} <- resolve_oid_if_needed(table_oid), {:ok, column_oid} <- build_column_oid(resolved_table_oid, column) do walk(target, column_oid, opts) end end defp build_column_oid(table_oid, column) when is_integer(column) do {:ok, table_oid ++ [1, column]} end defp build_column_oid(_table_oid, column) do case MIB.resolve(column) do {:ok, oid} -> {:ok, oid} error -> error end end @doc """ Performs an SNMP GET operation and returns a formatted value. """ @spec get_pretty(target(), oid(), opts()) :: {:ok, String.t()} | {:error, any()} def get_pretty(target, oid, opts \\ []) do merged_opts = opts |> Keyword.put(:include_formatted, true) |> Config.merge_opts() case get(target, oid, merged_opts) do {:ok, enriched} -> {:ok, enriched} {:error, reason} -> {:error, reason} end end @doc """ Performs an SNMP WALK operation and returns formatted results. """ @spec walk_pretty(target(), oid(), opts()) :: {:ok, [{String.t(), String.t()}]} | {:error, any()} def walk_pretty(target, oid, opts \\ []) do merged_opts = opts |> Keyword.put(:include_formatted, true) |> Config.merge_opts() case Walk.walk(target, oid, merged_opts) do {:ok, results} -> {:ok, Format.enrich_varbinds(results, merged_opts)} {:error, reason} -> {:error, reason} end end @doc """ Performs an SNMP BULK operation and returns formatted results. """ @spec bulk_pretty(target(), oid(), opts()) :: {:ok, [{String.t(), String.t()}]} | {:error, any()} def bulk_pretty(target, oid, opts \\ []) do merged_opts = opts |> Keyword.put(:include_formatted, true) |> Config.merge_opts() case Bulk.get_bulk(target, oid, merged_opts) do {:ok, results} -> {:ok, Format.enrich_varbinds(results, merged_opts)} {:error, reason} -> {:error, reason} end end @doc """ Performs an SNMP BULK WALK operation. Uses iterative GETBULK requests to efficiently walk the subtree. """ @spec bulk_walk(target(), oid(), opts()) :: {:ok, [{String.t(), atom(), any()}]} | {:error, any()} def bulk_walk(target, oid, opts \\ []) do merged_opts = opts |> Keyword.put(:version, :v2c) |> Config.merge_opts() case Bulk.walk_bulk(target, oid, merged_opts) do {:ok, results} -> {:ok, results} {:error, reason} -> {:error, reason} end end @doc """ Performs an SNMP BULK WALK operation and returns formatted results. """ @spec bulk_walk_pretty(target(), oid(), opts()) :: {:ok, [{String.t(), atom(), String.t()}]} | {:error, any()} def bulk_walk_pretty(target, oid, opts \\ []) do merged_opts = opts |> Keyword.put(:include_formatted, true) |> Config.merge_opts() case bulk_walk(target, oid, merged_opts) do {:ok, results} -> {:ok, results} {:error, reason} -> {:error, reason} end end # Private helper function defp resolve_oid_if_needed(oid) when is_binary(oid) do case OID.string_to_list(oid) do {:ok, oid_list} -> {:ok, oid_list} {:error, _} -> # Try resolving as symbolic name MIB.resolve(oid) end end defp resolve_oid_if_needed(oid) when is_list(oid), do: {:ok, oid} defp resolve_oid_if_needed(_), do: {:error, :invalid_oid_format} end