diff --git a/.credo.exs b/.credo.exs index 96e67300..ac6338c2 100644 --- a/.credo.exs +++ b/.credo.exs @@ -144,7 +144,8 @@ {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, {Credo.Check.Warning.IExPry, []}, {Credo.Check.Warning.IoInspect, []}, - {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, + # Disabled: We use structured logging with dynamic metadata keys + # {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, {Credo.Check.Warning.OperationOnSameValues, []}, {Credo.Check.Warning.OperationWithConstantResult, []}, {Credo.Check.Warning.RaiseInsideRescue, []}, diff --git a/lib/snmpkit/snmp_lib/error.ex b/lib/snmpkit/snmp_lib/error.ex index 1d8da8b2..f3e278b3 100644 --- a/lib/snmpkit/snmp_lib/error.ex +++ b/lib/snmpkit/snmp_lib/error.ex @@ -76,6 +76,9 @@ defmodule SnmpKit.SnmpLib.Error do @not_writable 17 @inconsistent_name 18 + # Errors that are typically transient and worth retrying + @retriable_errors MapSet.new([:too_big, :gen_err, :resource_unavailable]) + ## Standard Error Codes @doc """ @@ -351,28 +354,9 @@ defmodule SnmpKit.SnmpLib.Error do """ @spec retriable_error?(error_status()) :: boolean() def retriable_error?(error_status) do - case error_atom(error_status) do - :no_error -> false - :too_big -> true - :no_such_name -> false - :bad_value -> false - :read_only -> false - :gen_err -> true - :no_access -> false - :wrong_type -> false - :wrong_length -> false - :wrong_encoding -> false - :wrong_value -> false - :no_creation -> false - :inconsistent_value -> false - :resource_unavailable -> true - :commit_failed -> false - :undo_failed -> false - :authorization_error -> false - :not_writable -> false - :inconsistent_name -> false - _ -> false - end + error_status + |> error_atom() + |> then(&MapSet.member?(@retriable_errors, &1)) end @doc """ diff --git a/lib/snmpkit/snmp_lib/error_handler.ex b/lib/snmpkit/snmp_lib/error_handler.ex index b1c204f8..3b71f680 100644 --- a/lib/snmpkit/snmp_lib/error_handler.ex +++ b/lib/snmpkit/snmp_lib/error_handler.ex @@ -111,6 +111,30 @@ defmodule SnmpKit.SnmpLib.ErrorHandler do quarantine_until: integer() | nil } + # Error classification map for O(1) lookup + @error_classifications %{ + # Network-related transient errors + :timeout => :transient, + :nxdomain => :transient, + :network_unreachable => :transient, + :connection_refused => :transient, + # Device overload (transient) + :device_busy => :transient, + :too_big => :transient, + :resource_unavailable => :transient, + # Permanent configuration errors + :authentication_failed => :permanent, + :community_mismatch => :permanent, + :unsupported_version => :permanent, + :no_such_name => :permanent, + :bad_value => :permanent, + :read_only => :permanent, + # Performance degradation + :slow_response => :degraded, + :partial_failure => :degraded, + :high_error_rate => :degraded + } + defstruct device_stats: %{}, global_stats: %{ total_operations: 0, @@ -351,33 +375,8 @@ defmodule SnmpKit.SnmpLib.ErrorHandler do :degraded = SnmpKit.SnmpLib.ErrorHandler.classify_error(:slow_response) """ @spec classify_error(any()) :: error_class() - def classify_error(error) do - case error do - # Network-related transient errors - :timeout -> :transient - :nxdomain -> :transient - :network_unreachable -> :transient - :connection_refused -> :transient - {:network_error, _} -> :transient - # Device overload (transient) - :device_busy -> :transient - :too_big -> :transient - :resource_unavailable -> :transient - # Permanent configuration errors - :authentication_failed -> :permanent - :community_mismatch -> :permanent - :unsupported_version -> :permanent - :no_such_name -> :permanent - :bad_value -> :permanent - :read_only -> :permanent - # Performance degradation - :slow_response -> :degraded - :partial_failure -> :degraded - :high_error_rate -> :degraded - # Default to unknown for unclassified errors - _ -> :unknown - end - end + def classify_error({:network_error, _}), do: :transient + def classify_error(error), do: Map.get(@error_classifications, error, :unknown) @doc """ Puts a device into quarantine for a specified duration. diff --git a/lib/snmpkit/snmp_lib/mib/snmp_tokenizer.ex b/lib/snmpkit/snmp_lib/mib/snmp_tokenizer.ex index ea8f698d..497fd96c 100644 --- a/lib/snmpkit/snmp_lib/mib/snmp_tokenizer.ex +++ b/lib/snmpkit/snmp_lib/mib/snmp_tokenizer.ex @@ -13,6 +13,94 @@ defmodule SnmpKit.SnmpLib.MIB.SnmpTokenizer do # State record equivalent defstruct line: 1, chars: [], get_line_fun: nil + # Reserved words from SNMP/SMI - complete list from Erlang tokenizer + # Using a Map for O(1) lookup and reduced cyclomatic complexity + @reserved_words %{ + "DEFINITIONS" => :DEFINITIONS, + "BEGIN" => :BEGIN, + "END" => :END, + "IMPORTS" => :IMPORTS, + "FROM" => :FROM, + "EXPORTS" => :EXPORTS, + "OBJECT" => :OBJECT, + "IDENTIFIER" => :IDENTIFIER, + "OBJECT-TYPE" => :"OBJECT-TYPE", + "SYNTAX" => :SYNTAX, + "ACCESS" => :ACCESS, + "MAX-ACCESS" => :"MAX-ACCESS", + "STATUS" => :STATUS, + "DESCRIPTION" => :DESCRIPTION, + "REFERENCE" => :REFERENCE, + "INDEX" => :INDEX, + "AUGMENTS" => :AUGMENTS, + "DEFVAL" => :DEFVAL, + "UNITS" => :UNITS, + "SEQUENCE" => :SEQUENCE, + "OF" => :OF, + "CHOICE" => :CHOICE, + "SIZE" => :SIZE, + "INTEGER" => :INTEGER, + "OCTET" => :OCTET, + "STRING" => :STRING, + "NULL" => :NULL, + "IpAddress" => :IpAddress, + "Counter" => :Counter, + "Counter32" => :Counter32, + "Counter64" => :Counter64, + "Gauge" => :Gauge, + "Gauge32" => :Gauge32, + "TimeTicks" => :TimeTicks, + "Unsigned32" => :Unsigned32, + "Integer32" => :Integer32, + "Opaque" => :Opaque, + "BITS" => :BITS, + "MODULE-IDENTITY" => :"MODULE-IDENTITY", + "OBJECT-IDENTITY" => :"OBJECT-IDENTITY", + "TEXTUAL-CONVENTION" => :"TEXTUAL-CONVENTION", + "OBJECT-GROUP" => :"OBJECT-GROUP", + "NOTIFICATION-GROUP" => :"NOTIFICATION-GROUP", + "MODULE-COMPLIANCE" => :"MODULE-COMPLIANCE", + "AGENT-CAPABILITIES" => :"AGENT-CAPABILITIES", + "NOTIFICATION-TYPE" => :"NOTIFICATION-TYPE", + "TRAP-TYPE" => :"TRAP-TYPE", + "LAST-UPDATED" => :"LAST-UPDATED", + "ORGANIZATION" => :ORGANIZATION, + "CONTACT-INFO" => :"CONTACT-INFO", + "REVISION" => :REVISION, + "DISPLAY-HINT" => :"DISPLAY-HINT", + "IMPLIED" => :IMPLIED, + "OBJECTS" => :OBJECTS, + "NOTIFICATIONS" => :NOTIFICATIONS, + "MANDATORY-GROUPS" => :"MANDATORY-GROUPS", + "GROUP" => :GROUP, + "MODULE" => :MODULE, + "WRITE-SYNTAX" => :"WRITE-SYNTAX", + "MIN-ACCESS" => :"MIN-ACCESS", + "PRODUCT-RELEASE" => :"PRODUCT-RELEASE", + "SUPPORTS" => :SUPPORTS, + "INCLUDES" => :INCLUDES, + "VARIATION" => :VARIATION, + "CREATION-REQUIRES" => :"CREATION-REQUIRES", + "ENTERPRISE" => :ENTERPRISE, + "VARIABLES" => :VARIABLES, + "APPLICATION" => :APPLICATION, + "IMPLICIT" => :IMPLICIT, + "EXPLICIT" => :EXPLICIT, + "UNIVERSAL" => :UNIVERSAL, + "PRIVATE" => :PRIVATE, + "MACRO" => :MACRO, + "TYPE" => :TYPE, + "NOTATION" => :NOTATION, + "VALUE" => :VALUE, + # Access values + "read-only" => :"read-only", + "read-write" => :"read-write", + "write-only" => :"write-only", + "not-accessible" => :"not-accessible", + "accessible-for-notify" => :"accessible-for-notify", + "read-create" => :"read-create" + } + @type state() :: %__MODULE__{ line: pos_integer(), chars: charlist(), @@ -590,100 +678,5 @@ defmodule SnmpKit.SnmpLib.MIB.SnmpTokenizer do end end - # Reserved words from SNMP/SMI - complete list from Erlang tokenizer - defp reserved_word(word) do - case word do - "DEFINITIONS" -> :DEFINITIONS - "BEGIN" -> :BEGIN - "END" -> :END - "IMPORTS" -> :IMPORTS - "FROM" -> :FROM - "EXPORTS" -> :EXPORTS - "OBJECT" -> :OBJECT - "IDENTIFIER" -> :IDENTIFIER - "OBJECT-TYPE" -> :"OBJECT-TYPE" - "SYNTAX" -> :SYNTAX - "ACCESS" -> :ACCESS - "MAX-ACCESS" -> :"MAX-ACCESS" - "STATUS" -> :STATUS - "DESCRIPTION" -> :DESCRIPTION - "REFERENCE" -> :REFERENCE - "INDEX" -> :INDEX - "AUGMENTS" -> :AUGMENTS - "DEFVAL" -> :DEFVAL - "UNITS" -> :UNITS - "SEQUENCE" -> :SEQUENCE - "OF" -> :OF - "CHOICE" -> :CHOICE - "SIZE" -> :SIZE - "INTEGER" -> :INTEGER - "OCTET" -> :OCTET - "STRING" -> :STRING - "NULL" -> :NULL - "IpAddress" -> :IpAddress - "Counter" -> :Counter - "Counter32" -> :Counter32 - "Counter64" -> :Counter64 - "Gauge" -> :Gauge - "Gauge32" -> :Gauge32 - "TimeTicks" -> :TimeTicks - "Unsigned32" -> :Unsigned32 - "Integer32" -> :Integer32 - "Opaque" -> :Opaque - "BITS" -> :BITS - "MODULE-IDENTITY" -> :"MODULE-IDENTITY" - "OBJECT-IDENTITY" -> :"OBJECT-IDENTITY" - "TEXTUAL-CONVENTION" -> :"TEXTUAL-CONVENTION" - "OBJECT-GROUP" -> :"OBJECT-GROUP" - "NOTIFICATION-GROUP" -> :"NOTIFICATION-GROUP" - "MODULE-COMPLIANCE" -> :"MODULE-COMPLIANCE" - "AGENT-CAPABILITIES" -> :"AGENT-CAPABILITIES" - "NOTIFICATION-TYPE" -> :"NOTIFICATION-TYPE" - "TRAP-TYPE" -> :"TRAP-TYPE" - "LAST-UPDATED" -> :"LAST-UPDATED" - "ORGANIZATION" -> :ORGANIZATION - "CONTACT-INFO" -> :"CONTACT-INFO" - "REVISION" -> :REVISION - "DISPLAY-HINT" -> :"DISPLAY-HINT" - "IMPLIED" -> :IMPLIED - "OBJECTS" -> :OBJECTS - "NOTIFICATIONS" -> :NOTIFICATIONS - "MANDATORY-GROUPS" -> :"MANDATORY-GROUPS" - "GROUP" -> :GROUP - "MODULE" -> :MODULE - "WRITE-SYNTAX" -> :"WRITE-SYNTAX" - "MIN-ACCESS" -> :"MIN-ACCESS" - "PRODUCT-RELEASE" -> :"PRODUCT-RELEASE" - "SUPPORTS" -> :SUPPORTS - "INCLUDES" -> :INCLUDES - "VARIATION" -> :VARIATION - "CREATION-REQUIRES" -> :"CREATION-REQUIRES" - "ENTERPRISE" -> :ENTERPRISE - "VARIABLES" -> :VARIABLES - "APPLICATION" -> :APPLICATION - "IMPLICIT" -> :IMPLICIT - "EXPLICIT" -> :EXPLICIT - "UNIVERSAL" -> :UNIVERSAL - "PRIVATE" -> :PRIVATE - "MACRO" -> :MACRO - "TYPE" -> :TYPE - "NOTATION" -> :NOTATION - "VALUE" -> :VALUE - # Status values - removed to let grammar handle context - # "current" -> :'current' - # "deprecated" -> :'deprecated' - # "obsolete" -> :'obsolete' - # Access values - "read-only" -> :"read-only" - "read-write" -> :"read-write" - "write-only" -> :"write-only" - "not-accessible" -> :"not-accessible" - "accessible-for-notify" -> :"accessible-for-notify" - "read-create" -> :"read-create" - # Special values - removed to let grammar handle context - # "mandatory" -> :'mandatory' - # "optional" -> :'optional' - _ -> nil - end - end + defp reserved_word(word), do: Map.get(@reserved_words, word) end diff --git a/lib/snmpkit/snmp_lib/security.ex b/lib/snmpkit/snmp_lib/security.ex index eecf0b69..34f18c88 100644 --- a/lib/snmpkit/snmp_lib/security.ex +++ b/lib/snmpkit/snmp_lib/security.ex @@ -344,42 +344,39 @@ defmodule SnmpKit.SnmpLib.Security do ## Private Implementation defp validate_user_config(config) do - # Validate authentication protocol auth_protocol = config[:auth_protocol] || :none + priv_protocol = config[:priv_protocol] || :none - if auth_protocol in [:none, :md5, :sha1, :sha256, :sha384, :sha512] do - # Validate privacy protocol - priv_protocol = config[:priv_protocol] || :none - - if priv_protocol in [:none, :des, :aes128, :aes192, :aes256] do - # Validate protocol compatibility - if priv_protocol != :none and auth_protocol == :none do - {:error, :priv_requires_auth} - else - # Validate required passwords - if auth_protocol != :none and is_nil(config[:auth_password]) do - {:error, :missing_auth_password} - else - if priv_protocol != :none and is_nil(config[:priv_password]) do - {:error, :missing_priv_password} - else - # Validate engine ID - if is_nil(config[:engine_id]) do - {:error, :missing_engine_id} - else - {:ok, config} - end - end - end - end - else - {:error, :invalid_priv_protocol} - end - else - {:error, :invalid_auth_protocol} + with :ok <- validate_auth_protocol(auth_protocol), + :ok <- validate_priv_protocol(priv_protocol), + :ok <- validate_protocol_compatibility(auth_protocol, priv_protocol), + :ok <- validate_required_passwords(config, auth_protocol, priv_protocol), + :ok <- validate_engine_id(config) do + {:ok, config} end end + defp validate_auth_protocol(protocol) when protocol in [:none, :md5, :sha1, :sha256, :sha384, :sha512], do: :ok + defp validate_auth_protocol(_), do: {:error, :invalid_auth_protocol} + + defp validate_priv_protocol(protocol) when protocol in [:none, :des, :aes128, :aes192, :aes256], do: :ok + defp validate_priv_protocol(_), do: {:error, :invalid_priv_protocol} + + defp validate_protocol_compatibility(:none, priv) when priv != :none, do: {:error, :priv_requires_auth} + defp validate_protocol_compatibility(_, _), do: :ok + + defp validate_required_passwords(config, auth_protocol, priv_protocol) do + cond do + auth_protocol != :none and is_nil(config[:auth_password]) -> {:error, :missing_auth_password} + priv_protocol != :none and is_nil(config[:priv_password]) -> {:error, :missing_priv_password} + true -> :ok + end + end + + defp validate_engine_id(config) do + if is_nil(config[:engine_id]), do: {:error, :missing_engine_id}, else: :ok + end + defp derive_auth_key(config) do case config[:auth_protocol] do :none -> diff --git a/lib/snmpkit/snmp_lib/utils.ex b/lib/snmpkit/snmp_lib/utils.ex index 0184348d..6811bc99 100644 --- a/lib/snmpkit/snmp_lib/utils.ex +++ b/lib/snmpkit/snmp_lib/utils.ex @@ -583,18 +583,22 @@ defmodule SnmpKit.SnmpLib.Utils do # Standard host:port parsing for IPv4 and simple hostnames true -> - case String.split(target_str, ":", parts: 2) do - [host_str] -> - {host_str, nil} + parse_standard_host_port(target_str) + end + end - [host_str, port_str] -> - # Check if host part looks like IPv4 or simple hostname - if ipv4_or_simple_hostname?(host_str) do - {host_str, port_str} - else - # Complex hostname with colons, treat as hostname without port - {target_str, nil} - end + defp parse_standard_host_port(target_str) do + case String.split(target_str, ":", parts: 2) do + [host_str] -> + {host_str, nil} + + [host_str, port_str] -> + # Check if host part looks like IPv4 or simple hostname + if ipv4_or_simple_hostname?(host_str) do + {host_str, port_str} + else + # Complex hostname with colons, treat as hostname without port + {target_str, nil} end end end @@ -667,58 +671,46 @@ defmodule SnmpKit.SnmpLib.Utils do end end - defp valid_ip_tuple?({a, b, c, d}) when is_integer(a) and is_integer(b) and is_integer(c) and is_integer(d) do - a >= 0 and a <= 255 and - b >= 0 and b <= 255 and - c >= 0 and c <= 255 and - d >= 0 and d <= 255 + defp valid_ip_tuple?({a, b, c, d}) do + Enum.all?([a, b, c, d], &valid_ipv4_octet?/1) end defp valid_ip_tuple?(_), do: false - defp valid_ipv6_tuple?({a, b, c, d, e, f, g, h}) - when is_integer(a) and is_integer(b) and is_integer(c) and is_integer(d) and is_integer(e) and is_integer(f) and - is_integer(g) and is_integer(h) do - a >= 0 and a <= 65_535 and - b >= 0 and b <= 65_535 and - c >= 0 and c <= 65_535 and - d >= 0 and d <= 65_535 and - e >= 0 and e <= 65_535 and - f >= 0 and f <= 65_535 and - g >= 0 and g <= 65_535 and - h >= 0 and h <= 65_535 + defp valid_ipv4_octet?(n), do: is_integer(n) and n >= 0 and n <= 255 + + defp valid_ipv6_tuple?({a, b, c, d, e, f, g, h}) do + Enum.all?([a, b, c, d, e, f, g, h], &valid_ipv6_segment?/1) end defp valid_ipv6_tuple?(_), do: false + defp valid_ipv6_segment?(n), do: is_integer(n) and n >= 0 and n <= 65_535 + defp ipv4_or_simple_hostname?(host_str) do # Check if it looks like IPv4 (has 3 dots and only digits/dots) case String.split(host_str, ".") do - [a, b, c, d] -> - # Looks like IPv4, check if all parts are numeric - Enum.all?([a, b, c, d], fn part -> - case Integer.parse(part) do - {num, ""} when num >= 0 and num <= 255 -> true - _ -> false - end - end) - - _ -> - # Not IPv4 format, check if it's a simple hostname - # Exclude anything that looks like IPv6 (contains :: or multiple colons) - cond do - # IPv6 - String.contains?(host_str, "::") -> false - # IPv6 or complex - host_str |> String.graphemes() |> Enum.count(&(&1 == ":")) > 1 -> false - # Contains colon, not simple - String.contains?(host_str, ":") -> false - # Simple hostname pattern - true -> String.match?(host_str, ~r/^[a-zA-Z0-9\-\.\_]+$/) - end + [a, b, c, d] -> Enum.all?([a, b, c, d], &valid_ipv4_octet_string?/1) + _ -> simple_hostname?(host_str) end end + defp valid_ipv4_octet_string?(part) do + case Integer.parse(part) do + {num, ""} when num >= 0 and num <= 255 -> true + _ -> false + end + end + + defp simple_hostname?(host_str) do + # Not IPv4 format, check if it's a simple hostname + # Exclude anything that looks like IPv6 (contains :: or multiple colons) + not String.contains?(host_str, "::") and + count_colons(host_str) <= 1 and + not String.contains?(host_str, ":") and + String.match?(host_str, ~r/^[a-zA-Z0-9\-\.\_]+$/) + end + defp format_pdu_type(:get_request), do: "GET Request" defp format_pdu_type(:get_next_request), do: "GET-NEXT Request" defp format_pdu_type(:get_bulk_request), do: "GET-BULK Request" diff --git a/lib/snmpkit/snmp_mgr/core.ex b/lib/snmpkit/snmp_mgr/core.ex index 64ce3d11..45e52fdf 100644 --- a/lib/snmpkit/snmp_mgr/core.ex +++ b/lib/snmpkit/snmp_mgr/core.ex @@ -29,6 +29,27 @@ defmodule SnmpKit.SnmpMgr.Core do @type oid :: binary() | list(non_neg_integer()) @type opts :: keyword() + # Type normalization map for manager-expected atoms + @type_normalization %{ + :string => :string, + :octet_string => :string, + :octetString => :string, + :integer => :integer, + :unsigned32 => :integer, + :gauge32 => :gauge32, + :counter32 => :counter32, + :counter64 => :counter64, + :timeticks => :timeticks, + :timeTicks => :timeticks, + :ip_address => :ip_address, + :ipAddress => :ip_address, + :object_identifier => :object_identifier, + :objectId => :object_identifier, + :oid => :object_identifier, + :null => :null, + :opaque => :opaque + } + @doc """ Sends an SNMP GET request and returns the response. @@ -266,28 +287,7 @@ defmodule SnmpKit.SnmpMgr.Core do # Normalize typed values to the atoms that SnmpKit.SnmpLib.Manager expects defp normalize_manager_typed({type, val}) when is_atom(type) do - mapped_type = - case type do - :string -> :string - :octet_string -> :string - :octetString -> :string - :integer -> :integer - :unsigned32 -> :integer - :gauge32 -> :gauge32 - :counter32 -> :counter32 - :counter64 -> :counter64 - :timeticks -> :timeticks - :timeTicks -> :timeticks - :ip_address -> :ip_address - :ipAddress -> :ip_address - :object_identifier -> :object_identifier - :objectId -> :object_identifier - :oid -> :object_identifier - :null -> :null - :opaque -> :opaque - other -> other - end - + mapped_type = Map.get(@type_normalization, type, type) {mapped_type, val} end @@ -539,16 +539,7 @@ defmodule SnmpKit.SnmpMgr.Core do cond do # RFC 3986 bracket notation: [IPv6]:port String.starts_with?(target, "[") and String.contains?(target, "]:") -> - case String.split(target, "]:", parts: 2) do - [_ipv6_part, port_part] -> - case Integer.parse(port_part) do - {port, ""} when port > 0 and port <= 65_535 -> true - _ -> false - end - - _ -> - false - end + target |> extract_bracket_port() |> valid_port_string?() # Plain IPv6 addresses (contain :: or multiple colons) - no port embedded String.contains?(target, "::") -> @@ -559,16 +550,7 @@ defmodule SnmpKit.SnmpMgr.Core do # IPv4 or simple hostname with port String.contains?(target, ":") -> - case String.split(target, ":", parts: 2) do - [_host_part, port_part] -> - case Integer.parse(port_part) do - {port, ""} when port > 0 and port <= 65_535 -> true - _ -> false - end - - _ -> - false - end + target |> extract_simple_port() |> valid_port_string?() # No colon at all true -> @@ -577,4 +559,27 @@ defmodule SnmpKit.SnmpMgr.Core do end defp target_contains_port?(_), do: false + + defp extract_bracket_port(target) do + case String.split(target, "]:", parts: 2) do + [_ipv6_part, port_part] -> port_part + _ -> nil + end + end + + defp extract_simple_port(target) do + case String.split(target, ":", parts: 2) do + [_host_part, port_part] -> port_part + _ -> nil + end + end + + defp valid_port_string?(nil), do: false + + defp valid_port_string?(port_str) do + case Integer.parse(port_str) do + {port, ""} when port > 0 and port <= 65_535 -> true + _ -> false + end + end end diff --git a/test/snmpkit/snmp_lib/mib/compiler_test.exs b/test/snmpkit/snmp_lib/mib/compiler_test.exs index c6c839de..f4c07464 100644 --- a/test/snmpkit/snmp_lib/mib/compiler_test.exs +++ b/test/snmpkit/snmp_lib/mib/compiler_test.exs @@ -74,7 +74,7 @@ defmodule SnmpKit.SnmpLib.MIB.CompilerTest do assert {:error, errors} = Compiler.compile_string(mib_content) assert is_list(errors) - assert length(errors) > 0 + assert errors != [] end test "respects compile options" do diff --git a/test/snmpkit/snmp_lib/mib/comprehensive_mib_test.exs b/test/snmpkit/snmp_lib/mib/comprehensive_mib_test.exs index c35ac233..677c0b8f 100644 --- a/test/snmpkit/snmp_lib/mib/comprehensive_mib_test.exs +++ b/test/snmpkit/snmp_lib/mib/comprehensive_mib_test.exs @@ -26,7 +26,7 @@ defmodule SnmpKit.SnmpLib.MIB.ComprehensiveMibTest do {:ok, files} -> mib_files = filter_mib_files(files) - assert length(mib_files) > 0, "No MIB files found in #{unquote(dir_name)} directory" + assert mib_files != [], "No MIB files found in #{unquote(dir_name)} directory" results = test_mib_files(dir_path, mib_files) @@ -34,9 +34,9 @@ defmodule SnmpKit.SnmpLib.MIB.ComprehensiveMibTest do failed = Enum.filter(results, fn {status, _} -> status == :error end) # Log results for visibility - IO.puts("\n#{String.upcase(unquote(dir_name))} MIBs: #{successful}/#{length(mib_files)} successful") + IO.puts("\n#{String.upcase(unquote(dir_name))} MIBs: #{successful}/#{Enum.count(mib_files)} successful") - if length(failed) > 0 do + if failed != [] do IO.puts("Failed files:") for {:error, {file, reason}} <- failed do diff --git a/test/snmpkit/snmp_lib/mib/docsis_mib_test.exs b/test/snmpkit/snmp_lib/mib/docsis_mib_test.exs index 8fcfb878..37f9dab6 100644 --- a/test/snmpkit/snmp_lib/mib/docsis_mib_test.exs +++ b/test/snmpkit/snmp_lib/mib/docsis_mib_test.exs @@ -52,7 +52,7 @@ defmodule SnmpKit.SnmpLib.MIB.DocsisMibTest do not match?({:ok, _}, result) and not match?({:warning, _, _}, result) end) - if length(failed_mibs) > 0 do + if failed_mibs != [] do error_details = Enum.map(failed_mibs, fn {name, {:error, errors}} when is_list(errors) -> @@ -72,13 +72,13 @@ defmodule SnmpKit.SnmpLib.MIB.DocsisMibTest do {:ok, mib} -> assert mib.name != nil, "#{name} should have a valid MIB name" assert is_list(mib.definitions), "#{name} should have definitions list" - assert length(mib.definitions) > 0, "#{name} should have at least one definition" + assert mib.definitions != [], "#{name} should have at least one definition" {:warning, mib, warnings} -> assert mib.name != nil, "#{name} should have a valid MIB name despite warnings" assert is_list(mib.definitions), "#{name} should have definitions list" - assert length(warnings) > 0, + assert warnings != [], "#{name} should have warnings if returning warning result" {:error, _} -> @@ -136,7 +136,7 @@ defmodule SnmpKit.SnmpLib.MIB.DocsisMibTest do # Should contain MODULE-COMPLIANCE for QoS conformance # Note: Some MIBs might not have module compliance definitions # Just verify parsing succeeded - assert length(mib.definitions) > 0, "Should have parsed at least some definitions" + assert mib.definitions != [], "Should have parsed at least some definitions" end end diff --git a/test/snmpkit/snmp_lib/mib/parser_test.exs b/test/snmpkit/snmp_lib/mib/parser_test.exs index fc6922e8..96e5ba86 100644 --- a/test/snmpkit/snmp_lib/mib/parser_test.exs +++ b/test/snmpkit/snmp_lib/mib/parser_test.exs @@ -39,8 +39,8 @@ defmodule SnmpKit.SnmpLib.MIB.ParserTest do assert {:ok, mib} = Parser.parse(mib_content) assert %{__type__: :mib, name: "TEST-MIB"} = mib - assert length(mib.imports) == 1 - assert length(mib.definitions) >= 1 + assert match?([_], mib.imports) + assert mib.definitions != [] end test "parses simple object identifier assignment" do @@ -145,8 +145,8 @@ defmodule SnmpKit.SnmpLib.MIB.ParserTest do assert {:ok, mib} = Parser.parse(mib_content) assert %{__type__: :mib, name: "TEST-MIB"} = mib - assert length(mib.imports) >= 1 - assert length(mib.definitions) >= 1 + assert mib.imports != [] + assert mib.definitions != [] end test "handles comments in MIB content" do diff --git a/test/snmpkit/snmp_lib/snmpv3_edge_cases_test.exs b/test/snmpkit/snmp_lib/snmpv3_edge_cases_test.exs index 95067e67..f51f4bd2 100644 --- a/test/snmpkit/snmp_lib/snmpv3_edge_cases_test.exs +++ b/test/snmpkit/snmp_lib/snmpv3_edge_cases_test.exs @@ -365,7 +365,7 @@ defmodule SnmpKit.SnmpLib.SNMPv3EdgeCasesTest do case V3Encoder.decode_message(encoded, user) do {:ok, decoded} -> # Verify we have at least one varbind and it's reasonably long - assert length(decoded.msg_data.pdu.varbinds) >= 1 + assert decoded.msg_data.pdu.varbinds != [] [{decoded_oid, _, _} | _] = decoded.msg_data.pdu.varbinds # Allow some tolerance for encoding limits assert length(decoded_oid) >= 50 diff --git a/test/snmpkit/snmp_lib/snmpv3_integration_test.exs b/test/snmpkit/snmp_lib/snmpv3_integration_test.exs index 4295816e..c0f1b539 100644 --- a/test/snmpkit/snmp_lib/snmpv3_integration_test.exs +++ b/test/snmpkit/snmp_lib/snmpv3_integration_test.exs @@ -2,10 +2,6 @@ defmodule SnmpKit.SnmpLib.SNMPv3IntegrationTest do use ExUnit.Case, async: false alias SnmpKit.SnmpLib.PDU.V3Encoder - alias SnmpKit.SnmpLib.Security - alias SnmpKit.SnmpLib.Security.Keys - alias SnmpKit.SnmpLib.Security.Priv - alias SnmpKit.SnmpLib.Security.USM @moduletag :snmpv3