defmodule SnmpKit.SnmpMgr.Errors do @moduledoc """ SNMP error handling and error code translation. Provides functions to handle both SNMPv1 and SNMPv2c error conditions and translate error codes to human-readable messages. """ alias SnmpKit.SnmpLib.Error # SNMPv1 and SNMPv2c error codes @error_codes %{ 0 => :no_error, 1 => :too_big, 2 => :no_such_name, 3 => :bad_value, 4 => :read_only, 5 => :gen_err, # SNMPv2c additional error codes 6 => :no_access, 7 => :wrong_type, 8 => :wrong_length, 9 => :wrong_encoding, 10 => :wrong_value, 11 => :no_creation, 12 => :inconsistent_value, 13 => :resource_unavailable, 14 => :commit_failed, 15 => :undo_failed, 16 => :authorization_error, 17 => :not_writable, 18 => :inconsistent_name } @error_descriptions %{ :no_error => "No error occurred", :too_big => "Response too big to fit in message", :no_such_name => "Variable name not found", :bad_value => "Invalid value for variable", :read_only => "Variable is read-only", :gen_err => "General error", # SNMPv2c additional errors :no_access => "Access denied", :wrong_type => "Wrong data type", :wrong_length => "Wrong data length", :wrong_encoding => "Wrong encoding", :wrong_value => "Wrong value", :no_creation => "Cannot create variable", :inconsistent_value => "Inconsistent value", :resource_unavailable => "Resource unavailable", :commit_failed => "Commit failed", :undo_failed => "Undo failed", :authorization_error => "Authorization failed", :not_writable => "Variable not writable", :inconsistent_name => "Inconsistent name" } @doc """ Translates an SNMP error code to an atom. Uses SnmpKit.SnmpLib.Error for validation and standardization of RFC-compliant error codes. ## Examples iex> SnmpKit.SnmpMgr.Errors.code_to_atom(2) :no_such_name iex> SnmpKit.SnmpMgr.Errors.code_to_atom(0) :no_error iex> SnmpKit.SnmpMgr.Errors.code_to_atom(999) :unknown_error """ def code_to_atom(error_code) when is_integer(error_code) do # Use SnmpKit.SnmpLib.Error for validation and RFC compliance, fall back to our mapping if Error.valid_error_status?(error_code) do Error.error_atom(error_code) else Map.get(@error_codes, error_code, :unknown_error) end end @doc """ Translates an SNMP error atom to a human-readable description. ## Examples iex> SnmpKit.SnmpMgr.Errors.description(:no_such_name) "Variable name not found" iex> SnmpKit.SnmpMgr.Errors.description(:too_big) "Response too big to fit in message" iex> SnmpKit.SnmpMgr.Errors.description(:unknown_error) "Unknown error" """ def description(error_atom) when is_atom(error_atom) do Map.get(@error_descriptions, error_atom, "Unknown error") end @doc """ Translates an error code directly to a description. ## Examples iex> SnmpKit.SnmpMgr.Errors.code_to_description(2) "Variable name not found" iex> SnmpKit.SnmpMgr.Errors.code_to_description(18) "Inconsistent name" """ def code_to_description(error_code) when is_integer(error_code) do error_code |> code_to_atom() |> description() end @doc """ Determines if an error is version-specific. ## Examples iex> SnmpKit.SnmpMgr.Errors.v2c_error?(:no_access) true iex> SnmpKit.SnmpMgr.Errors.v2c_error?(:no_such_name) false """ def v2c_error?(error_atom) do v2c_errors = [ :no_access, :wrong_type, :wrong_length, :wrong_encoding, :wrong_value, :no_creation, :inconsistent_value, :resource_unavailable, :commit_failed, :undo_failed, :authorization_error, :not_writable, :inconsistent_name ] error_atom in v2c_errors end @doc """ Formats an SNMP error for display. ## Examples iex> SnmpKit.SnmpMgr.Errors.format_error({:snmp_error, 2}) "SNMP Error (2): Variable name not found" iex> SnmpKit.SnmpMgr.Errors.format_error({:snmp_error, :no_such_name}) "SNMP Error: Variable name not found" iex> SnmpKit.SnmpMgr.Errors.format_error({:v2c_error, :no_access, oid: "1.2.3.4"}) "SNMPv2c Error: Access denied (OID: 1.2.3.4)" """ def format_error({:snmp_error, error_code}) when is_integer(error_code) do error_atom = code_to_atom(error_code) desc = description(error_atom) "SNMP Error (#{error_code}): #{desc}" end def format_error({:snmp_error, error_atom}) when is_atom(error_atom) do desc = description(error_atom) "SNMP Error: #{desc}" end def format_error({:v2c_error, error_atom}) when is_atom(error_atom) do desc = description(error_atom) "SNMPv2c Error: #{desc}" end def format_error({:v2c_error, error_atom, details}) when is_atom(error_atom) do desc = description(error_atom) detail_str = format_error_details(details) "SNMPv2c Error: #{desc}#{detail_str}" end def format_error({:network_error, reason}) do "Network Error: #{inspect(reason)}" end def format_error({:timeout, _}) do "Timeout Error: Request timed out" end def format_error({:encoding_error, reason}) do "Encoding Error: #{inspect(reason)}" end def format_error({:decoding_error, reason}) do "Decoding Error: #{inspect(reason)}" end def format_error(error) do "Unknown Error: #{inspect(error)}" end @doc """ Checks if an error is recoverable (can be retried). ## Examples iex> SnmpKit.SnmpMgr.Errors.recoverable?({:network_error, :host_unreachable}) false iex> SnmpKit.SnmpMgr.Errors.recoverable?({:snmp_error, :too_big}) true iex> SnmpKit.SnmpMgr.Errors.recoverable?(:timeout) true """ def recoverable?({:network_error, :host_unreachable}), do: false def recoverable?({:network_error, :network_unreachable}), do: false def recoverable?({:snmp_error, :no_such_name}), do: false def recoverable?({:snmp_error, :bad_value}), do: false def recoverable?({:snmp_error, :read_only}), do: false def recoverable?({:v2c_error, :no_access}), do: false def recoverable?({:v2c_error, :not_writable}), do: false def recoverable?({:v2c_error, :wrong_type}), do: false def recoverable?(:timeout), do: true def recoverable?({:snmp_error, :too_big}), do: true def recoverable?({:snmp_error, :gen_err}), do: true def recoverable?(_), do: false @doc """ Enhanced error analysis using SnmpKit.SnmpLib.Error for SNMP protocol errors. Provides detailed error information including severity and RFC compliance for SNMP protocol errors while preserving our comprehensive network error handling. ## Examples iex> SnmpKit.SnmpMgr.Errors.analyze_error({:snmp_error, 2}) %{ type: :snmp_protocol, atom: :no_such_name, code: 2, severity: :error, retriable: false, rfc_compliant: true, category: :user_error, description: "Variable name not found" } iex> SnmpKit.SnmpMgr.Errors.analyze_error(:timeout) %{ type: :network, atom: :timeout, retriable: true, category: :transient_error, description: "Operation timed out" } """ def analyze_error(error) do case error do # SNMP protocol errors - use SnmpKit.SnmpLib.Error for detailed analysis {:snmp_error, code} when is_integer(code) -> if Error.valid_error_status?(code) do atom = Error.error_atom(code) %{ type: :snmp_protocol, atom: atom, code: code, severity: Error.error_severity(atom), retriable: Error.retriable_error?(atom), rfc_compliant: true, category: classify_snmp_error(atom), description: description(atom) } else %{ type: :snmp_protocol, atom: code_to_atom(code), code: code, retriable: false, rfc_compliant: false, category: :unknown_error, description: "Unknown SNMP error code" } end {:snmp_error, atom} when is_atom(atom) -> %{ type: :snmp_protocol, atom: atom, code: Error.error_code(atom), severity: Error.error_severity(atom), retriable: Error.retriable_error?(atom), rfc_compliant: true, category: classify_snmp_error(atom), description: description(atom) } # Network errors - our superior handling error_atom when error_atom in [:timeout, :host_unreachable, :network_unreachable, :connection_refused] -> %{ type: :network, atom: error_atom, retriable: recoverable?(error_atom), category: classify_network_error(error_atom), description: get_network_error_description(error_atom) } # Other errors _ -> {category, subcategory} = classify_error(error) %{ type: :other, atom: error, retriable: recoverable?(error), category: category, subcategory: subcategory, description: format_error(error) } end end defp classify_snmp_error(atom) do case atom do atom when atom in [:no_such_name, :bad_value, :wrong_type, :wrong_value] -> :user_error atom when atom in [:read_only, :not_writable, :no_access, :authorization_error] -> :security_error atom when atom in [:too_big, :resource_unavailable] -> :resource_error atom when atom in [:gen_err, :commit_failed, :undo_failed] -> :device_error _ -> :protocol_error end end defp classify_network_error(atom) do case atom do :timeout -> :transient_error :host_unreachable -> :configuration_error :network_unreachable -> :configuration_error :connection_refused -> :service_error end end defp get_network_error_description(atom) do case atom do :timeout -> "Operation timed out" :host_unreachable -> "Host is unreachable" :network_unreachable -> "Network is unreachable" :connection_refused -> "Connection refused by target" end end # Private functions defp format_error_details(details) when is_list(details) do formatted = Enum.map_join(details, ", ", fn {:oid, oid} -> "OID: #{oid}" {:index, index} -> "Index: #{index}" {:value, value} -> "Value: #{inspect(value)}" {key, value} -> "#{key}: #{value}" end) if formatted == "", do: "", else: " (#{formatted})" end defp format_error_details(_), do: "" @doc """ Classifies an error into a category for better handling. ## Examples iex> SnmpKit.SnmpMgr.Errors.classify_error({:snmp_error, :no_such_name}, "Get request") {:user_error, :invalid_oid} iex> SnmpKit.SnmpMgr.Errors.classify_error({:network_error, :timeout}, "Network operation") {:transient_error, :network_timeout} iex> SnmpKit.SnmpMgr.Errors.classify_error({:snmp_error, :too_big}, "Bulk request") {:recoverable_error, :response_too_large} """ def classify_error(error, context \\ nil) def classify_error({:snmp_error, :no_such_name}, _context) do {:user_error, :invalid_oid} end def classify_error({:snmp_error, :bad_value}, _context) do {:user_error, :invalid_value} end def classify_error({:snmp_error, :read_only}, _context) do {:user_error, :write_to_readonly} end def classify_error({:snmp_error, :too_big}, _context) do {:recoverable_error, :response_too_large} end def classify_error({:snmp_error, :gen_err}, _context) do {:device_error, :general_failure} end def classify_error({:v2c_error, :no_access}, _context) do {:security_error, :access_denied} end def classify_error({:v2c_error, :authorization_error}, _context) do {:security_error, :authorization_failed} end def classify_error({:v2c_error, :wrong_type}, _context) do {:user_error, :type_mismatch} end def classify_error({:v2c_error, :resource_unavailable}, _context) do {:device_error, :resource_exhausted} end def classify_error({:network_error, :timeout}, _context) do {:transient_error, :network_timeout} end def classify_error({:network_error, :host_unreachable}, _context) do {:configuration_error, :unreachable_host} end def classify_error({:network_error, :network_unreachable}, _context) do {:configuration_error, :network_unavailable} end def classify_error({:encoding_error, _reason}, _context) do {:protocol_error, :message_encoding_failed} end def classify_error({:decoding_error, _reason}, _context) do {:protocol_error, :message_decoding_failed} end def classify_error(:timeout, _context) do {:transient_error, :operation_timeout} end def classify_error(:invalid_oid_values, _context) do :validation_error end def classify_error({:snmp_encoding_error, _reason}, _context) do :validation_error end def classify_error(:encoding_failed, _context) do :validation_error end def classify_error(:authentication_error, _context) do :authentication_error end def classify_error(:invalid_community, _context) do :invalid_community end def classify_error(:bad_community, _context) do :invalid_community end def classify_error(:snmp_modules_not_available, _context) do :system_error end def classify_error(error, _context) do {:unknown_error, error} end @doc """ Formats an error message in a user-friendly way with context. ## Examples iex> SnmpKit.SnmpMgr.Errors.format_user_friendly_error({:snmp_error, :no_such_name}, "Getting system description") "Getting system description failed: The requested OID does not exist on the device" iex> SnmpKit.SnmpMgr.Errors.format_user_friendly_error({:network_error, :timeout}, "Contacting device") "Contacting device failed: The device did not respond within the timeout period" """ def format_user_friendly_error(error, context \\ "Operation") def format_user_friendly_error({:snmp_error, :no_such_name}, context) do "#{context} failed: The requested OID does not exist on the device" end def format_user_friendly_error({:snmp_error, :bad_value}, context) do "#{context} failed: The provided value is invalid for this OID" end def format_user_friendly_error({:snmp_error, :read_only}, context) do "#{context} failed: This OID is read-only and cannot be modified" end def format_user_friendly_error({:snmp_error, :too_big}, context) do "#{context} failed: The response is too large. Try requesting fewer OIDs or use BULK operations" end def format_user_friendly_error({:snmp_error, :gen_err}, context) do "#{context} failed: The device reported a general error" end def format_user_friendly_error({:v2c_error, :no_access}, context) do "#{context} failed: Access denied. Check your community string and device configuration" end def format_user_friendly_error({:v2c_error, :authorization_error}, context) do "#{context} failed: Authorization failed. Verify your credentials" end def format_user_friendly_error({:v2c_error, :wrong_type}, context) do "#{context} failed: The value type does not match the expected type for this OID" end def format_user_friendly_error({:network_error, :timeout}, context) do "#{context} failed: The device did not respond within the timeout period" end def format_user_friendly_error({:network_error, :host_unreachable}, context) do "#{context} failed: Cannot reach the device. Check the IP address and network connectivity" end def format_user_friendly_error({:network_error, :network_unreachable}, context) do "#{context} failed: Network is unreachable. Check your network configuration" end def format_user_friendly_error(:timeout, context) do "#{context} timed out: The operation took too long to complete" end def format_user_friendly_error({:encoding_error, _reason}, context) do "#{context} failed: Unable to encode the SNMP message" end def format_user_friendly_error({:decoding_error, _reason}, context) do "#{context} failed: Unable to decode the SNMP response" end def format_user_friendly_error(error, context) do "#{context} failed: #{inspect(error)}" end @doc """ Provides recovery suggestions for common errors. ## Examples iex> SnmpKit.SnmpMgr.Errors.get_recovery_suggestions({:snmp_error, :no_such_name}) ["Verify the OID is correct", "Check if the OID is supported by this device", "Try using MIB browser to explore available OIDs", "Ensure you're using the correct SNMP version"] iex> SnmpKit.SnmpMgr.Errors.get_recovery_suggestions({:network_error, :timeout}) ["Increase timeout value", "Check network connectivity to the device", "Verify device is responding to ping", "Check for network congestion or packet loss", "Ensure device SNMP service is running"] """ def get_recovery_suggestions({:snmp_error, :no_such_name}) do [ "Verify the OID is correct", "Check if the OID is supported by this device", "Try using MIB browser to explore available OIDs", "Ensure you're using the correct SNMP version" ] end def get_recovery_suggestions({:snmp_error, :bad_value}) do [ "Check the value format and type", "Verify the value is within acceptable range", "Ensure the value matches the OID's expected data type", "Check device documentation for valid values" ] end def get_recovery_suggestions({:snmp_error, :read_only}) do [ "This OID cannot be modified", "Use GET operation instead of SET", "Check device documentation for writable OIDs", "Verify you have the correct OID for writing" ] end def get_recovery_suggestions({:snmp_error, :too_big}) do [ "Reduce the number of OIDs in the request", "Use GETBULK operation with smaller max-repetitions", "Split the request into multiple smaller requests", "Check device's maximum PDU size settings" ] end def get_recovery_suggestions({:v2c_error, :no_access}) do [ "Verify the community string is correct", "Check device SNMP access configuration", "Ensure the device allows SNMP access from your IP", "Verify SNMP version compatibility (v1/v2c)" ] end def get_recovery_suggestions({:network_error, :timeout}) do [ "Increase timeout value", "Check network connectivity to the device", "Verify device is responding to ping", "Check for network congestion or packet loss", "Ensure device SNMP service is running" ] end def get_recovery_suggestions({:network_error, :host_unreachable}) do [ "Verify the IP address is correct", "Check network routing", "Ensure device is powered on and connected", "Test basic connectivity with ping", "Check firewall rules" ] end def get_recovery_suggestions({:encoding_error, _reason}) do [ "Check OID format", "Verify value types are supported", "Ensure SNMP version compatibility", "Check for invalid characters in community string" ] end def get_recovery_suggestions({:decoding_error, _reason}) do [ "Device may not support SNMP", "Check SNMP version compatibility", "Verify device is not sending corrupted responses", "Try different SNMP version (v1/v2c)" ] end def get_recovery_suggestions(:timeout) do [ "Increase operation timeout", "Check if operation is too complex", "Verify device resources are available", "Try simpler operations first" ] end def get_recovery_suggestions(_error) do [ "Check device documentation", "Verify SNMP configuration", "Try basic connectivity tests", "Contact device vendor support" ] end end