defmodule SnmpKit.SnmpLib.PDU.EncoderTest do use ExUnit.Case, async: true alias SnmpKit.SnmpLib.PDU.Encoder describe "encode_message/1" do test "encodes SNMPv1 message" do message = %{ version: 0, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 # Should start with SEQUENCE tag assert <<0x30, _::binary>> = encoded end test "encodes SNMPv2c message" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 100, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 5, 0], :null, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "delegates SNMPv3 messages to V3Encoder" do message = %{ version: 3, msgID: 1, msgMaxSize: 65_507, msgFlags: <<0x04>>, msgSecurityModel: 3, msgSecurityParameters: "", contextEngineID: "", contextName: "", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [] } } # Should delegate to V3Encoder (may fail if V3Encoder requires specific fields) result = Encoder.encode_message(message) assert is_tuple(result) and elem(result, 0) in [:ok, :error] end test "returns error for invalid message format" do assert {:error, :invalid_message_format} = Encoder.encode_message(%{invalid: :format}) assert {:error, :invalid_message_format} = Encoder.encode_message(%{version: 1}) assert {:error, :invalid_message_format} = Encoder.encode_message(%{}) end test "encodes message with multiple varbinds" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [ {[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil}, {[1, 3, 6, 1, 2, 1, 1, 3, 0], :null, nil}, {[1, 3, 6, 1, 2, 1, 1, 5, 0], :null, nil} ] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes message with empty community string" do message = %{ version: 0, community: "", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end describe "encode_message/2" do test "delegates SNMPv3 messages with user to V3Encoder" do message = %{ version: 3, msgID: 1, msgMaxSize: 65_507, msgFlags: <<0x04>>, msgSecurityModel: 3, msgSecurityParameters: "", contextEngineID: "", contextName: "", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [] } } user = %{ auth_protocol: :md5, auth_key: "authentication_key", priv_protocol: :des, priv_key: "privacy_key" } # Should delegate to V3Encoder (may fail if V3Encoder requires specific fields) result = Encoder.encode_message(message, user) assert is_tuple(result) and elem(result, 0) in [:ok, :error] end test "falls back to regular encoding for v1/v2c with user parameter" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [] } } user = %{auth_protocol: :md5} assert {:ok, encoded} = Encoder.encode_message(message, user) assert byte_size(encoded) > 0 end test "ignores user parameter for non-v3 messages" do message = %{ version: 0, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [] } } assert {:ok, encoded1} = Encoder.encode_message(message, nil) assert {:ok, encoded2} = Encoder.encode_message(message, %{some: "user"}) assert encoded1 == encoded2 end end describe "encode_pdu/1" do test "encodes get_request PDU" do pdu = %{ type: :get_request, request_id: 123, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1], :null, nil}] } assert {:ok, encoded} = Encoder.encode_pdu(pdu) assert byte_size(encoded) > 0 # Should start with get_request tag (0xA0) assert <<0xA0, _::binary>> = encoded end test "encodes get_next_request PDU" do pdu = %{ type: :get_next_request, request_id: 456, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6], :null, nil}] } assert {:ok, encoded} = Encoder.encode_pdu(pdu) assert byte_size(encoded) > 0 # Should start with getnext_request tag (0xA1) assert <<0xA1, _::binary>> = encoded end test "encodes get_response PDU" do pdu = %{ type: :get_response, request_id: 789, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, "System description"}] } assert {:ok, encoded} = Encoder.encode_pdu(pdu) assert byte_size(encoded) > 0 # Should start with get_response tag (0xA2) assert <<0xA2, _::binary>> = encoded end test "encodes set_request PDU" do pdu = %{ type: :set_request, request_id: 111, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 5, 0], :octet_string, "NewName"}] } assert {:ok, encoded} = Encoder.encode_pdu(pdu) assert byte_size(encoded) > 0 # Should start with set_request tag (0xA3) assert <<0xA3, _::binary>> = encoded end test "encodes get_bulk_request PDU" do pdu = %{ type: :get_bulk_request, request_id: 999, non_repeaters: 0, max_repetitions: 10, varbinds: [{[1, 3, 6, 1, 2, 1, 2, 2], :null, nil}] } assert {:ok, encoded} = Encoder.encode_pdu(pdu) assert byte_size(encoded) > 0 # Should start with getbulk_request tag (0xA5) assert <<0xA5, _::binary>> = encoded end test "returns error for unsupported PDU type" do pdu = %{ type: :unsupported_type, request_id: 1, error_status: 0, error_index: 0, varbinds: [] } assert {:error, :unsupported_pdu_type} = Encoder.encode_pdu(pdu) end test "encodes PDU with empty varbinds" do pdu = %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [] } assert {:ok, encoded} = Encoder.encode_pdu(pdu) assert byte_size(encoded) > 0 end test "encodes PDU with multiple varbinds" do pdu = %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [ {[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil}, {[1, 3, 6, 1, 2, 1, 1, 3, 0], :null, nil}, {[1, 3, 6, 1, 2, 1, 1, 5, 0], :null, nil} ] } assert {:ok, encoded} = Encoder.encode_pdu(pdu) assert byte_size(encoded) > 0 end test "encodes PDU with large request_id" do pdu = %{ type: :get_request, request_id: 2_147_483_647, error_status: 0, error_index: 0, varbinds: [] } assert {:ok, encoded} = Encoder.encode_pdu(pdu) assert byte_size(encoded) > 0 end end describe "encode/1 and encode_snmp_packet/1" do test "encode/1 is an alias for encode_message/1" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [] } } assert {:ok, encoded1} = Encoder.encode(message) assert {:ok, encoded2} = Encoder.encode_message(message) assert encoded1 == encoded2 end test "encode_snmp_packet/1 is an alias for encode/1" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [] } } assert {:ok, encoded1} = Encoder.encode_snmp_packet(message) assert {:ok, encoded2} = Encoder.encode(message) assert encoded1 == encoded2 end end describe "varbind encoding" do test "encodes varbind with explicit null type" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with integer value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 7, 0], :integer, 72}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with octet_string value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, "System description"}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with counter32 value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 2, 1, 10, 1], :counter32, 1_234_567}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with gauge32 value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 2, 1, 5, 1], :gauge32, 1_000_000}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with timeticks value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 3, 0], :timeticks, 12_345_678}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with counter64 value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, 1], :counter64, 9_876_543_210}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with ip_address value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 4, 20, 1, 1, 1], :ip_address, <<192, 168, 1, 1>>}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with opaque value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 4, 1, 9, 2, 1, 57, 0], :opaque, <<0x9F, 0x78, 0x04, 0x41, 0x70, 0x00, 0x00>>}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with object_identifier value (list)" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 2, 0], :object_identifier, [1, 3, 6, 1, 4, 1]}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with object_identifier value (string)" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 2, 0], :object_identifier, "1.3.6.1.4.1"}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end describe "auto type varbind encoding" do test "encodes varbind with auto type and integer value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 7, 0], :auto, 72}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with auto type and string value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 5, 0], :auto, "router1"}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with auto type and OID list" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 2, 0], :auto, [1, 3, 6, 1, 4, 1, 9]}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with auto type and OID string" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 2, 0], :auto, "1.3.6.1.4.1.9"}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with auto type and null value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :auto, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with auto type and :null atom" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :auto, :null}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with auto type and typed tuple" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [ {[1, 3, 6, 1, 2, 1, 2, 1, 10, 1], :auto, {:counter32, 1_234_567}}, {[1, 3, 6, 1, 2, 1, 2, 1, 5, 1], :auto, {:gauge32, 1_000_000}}, {[1, 3, 6, 1, 2, 1, 1, 3, 0], :auto, {:timeticks, 12_345}}, {[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, 1], :auto, {:counter64, 9_876_543_210}} ] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind with auto type and exception values" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [ {[1, 3, 6, 1, 2, 1, 99, 1], :auto, {:no_such_object, nil}}, {[1, 3, 6, 1, 2, 1, 99, 2], :auto, {:no_such_instance, nil}}, {[1, 3, 6, 1, 2, 1, 99, 3], :auto, {:end_of_mib_view, nil}} ] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end describe "exception value encoding" do test "encodes no_such_object exception" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 99, 1], :no_such_object, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes no_such_instance exception" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 99, 2], :no_such_instance, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes end_of_mib_view exception" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 99, 3], :end_of_mib_view, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end describe "integer encoding edge cases" do test "encodes zero integer" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 0, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1], :integer, 0}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes small positive integer (<128)" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1], :integer, 42}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes large positive integer" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1], :integer, 2_147_483_647}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes negative integer" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1], :integer, -100}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes large negative integer" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1], :integer, -2_147_483_648}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end describe "counter32/gauge32/timeticks encoding" do test "encodes counter32 min value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1], :counter32, 0}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes counter32 max value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1], :counter32, 4_294_967_295}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes gauge32 min value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1], :gauge32, 0}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes gauge32 max value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1], :gauge32, 4_294_967_295}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes timeticks min value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 3, 0], :timeticks, 0}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes timeticks max value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 3, 0], :timeticks, 4_294_967_295}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end describe "counter64 encoding" do test "encodes counter64 min value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, 1], :counter64, 0}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes counter64 large value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, 1], :counter64, 9_999_999_999_999}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes counter64 max value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, 1], :counter64, 18_446_744_073_709_551_615}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end describe "string encoding" do test "encodes empty string" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, ""}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes short string" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, "test"}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes long string" do long_string = String.duplicate("A", 256) message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, long_string}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes string with special characters" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, "Test\nString\twith\rspecial"}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes binary string" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, <<0x00, 0x01, 0x02, 0xFF>>}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end describe "OID encoding" do test "encodes simple OID" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1], :null, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes OID with large subidentifiers" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1000], :null, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes OID with very large subidentifiers" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 4, 1, 9, 9, 1, 1, 1, 1, 1_000_000], :null, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes long OID" do long_oid = [1, 3, 6, 1, 2, 1, 2, 2, 1, 1] ++ Enum.to_list(1..50) message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [{long_oid, :null, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes minimum OID (two components)" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[0, 0], :null, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes OID starting with 2" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[2, 5, 29, 17], :null, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end describe "bulk request encoding" do test "encodes bulk request with non_repeaters" do message = %{ version: 1, community: "public", pdu: %{ type: :get_bulk_request, request_id: 1, non_repeaters: 1, max_repetitions: 10, varbinds: [ {[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil}, {[1, 3, 6, 1, 2, 1, 2, 2, 1, 10], :null, nil} ] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes bulk request with zero non_repeaters" do message = %{ version: 1, community: "public", pdu: %{ type: :get_bulk_request, request_id: 1, non_repeaters: 0, max_repetitions: 25, varbinds: [{[1, 3, 6, 1, 2, 1, 2, 2, 1, 10], :null, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes bulk request with large max_repetitions" do message = %{ version: 1, community: "public", pdu: %{ type: :get_bulk_request, request_id: 1, non_repeaters: 0, max_repetitions: 1000, varbinds: [{[1, 3, 6, 1, 2, 1, 2, 2], :null, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end describe "error handling" do test "handles encoding errors gracefully" do # Invalid varbind format should be caught message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: ["invalid_varbind"] } } assert {:error, _reason} = Encoder.encode_message(message) end test "handles missing PDU fields" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request # Missing required fields } } assert {:error, _reason} = Encoder.encode_message(message) end end describe "PDU with error status" do test "encodes PDU with non-zero error_status" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 2, error_index: 1, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes PDU with various error statuses" do for error_status <- 0..5 do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: error_status, error_index: 0, varbinds: [] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end end describe "community string variations" do test "encodes message with short community" do message = %{ version: 1, community: "p", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes message with long community" do long_community = String.duplicate("community", 20) message = %{ version: 1, community: long_community, pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes message with special characters in community" do message = %{ version: 1, community: "test@community#123", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end describe "varbind format variations" do test "encodes varbind as two-tuple (implicit auto type)" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], nil}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes varbind as two-tuple with value" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], "Test"}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end describe "length encoding edge cases" do test "encodes value with length < 128 (short form)" do # Short string (< 128 bytes) message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1], :octet_string, "Short"}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes value with length 128-255 (long form 1 byte)" do # String of 150 bytes string = String.duplicate("A", 150) message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1], :octet_string, string}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes value with length 256-65535 (long form 2 bytes)" do # String of 300 bytes string = String.duplicate("B", 300) message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 1, error_status: 0, error_index: 0, varbinds: [{[1, 3, 6, 1], :octet_string, string}] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end describe "comprehensive integration tests" do test "encodes complete SNMP GET request" do message = %{ version: 1, community: "public", pdu: %{ type: :get_request, request_id: 12_345, error_status: 0, error_index: 0, varbinds: [ {[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil}, {[1, 3, 6, 1, 2, 1, 1, 3, 0], :null, nil}, {[1, 3, 6, 1, 2, 1, 1, 5, 0], :null, nil} ] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 assert byte_size(encoded) > 0 end test "encodes complete SNMP GET response with multiple types" do message = %{ version: 1, community: "public", pdu: %{ type: :get_response, request_id: 12_345, error_status: 0, error_index: 0, varbinds: [ {[1, 3, 6, 1, 2, 1, 1, 1, 0], :octet_string, "Linux router 4.19.0"}, {[1, 3, 6, 1, 2, 1, 1, 3, 0], :timeticks, 123_456_789}, {[1, 3, 6, 1, 2, 1, 1, 5, 0], :octet_string, "router1.example.com"}, {[1, 3, 6, 1, 2, 1, 1, 7, 0], :integer, 72}, {[1, 3, 6, 1, 2, 1, 2, 1, 10, 1], :counter32, 987_654_321} ] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 assert byte_size(encoded) > 0 end test "encodes SNMP SET request" do message = %{ version: 1, community: "private", pdu: %{ type: :set_request, request_id: 9999, error_status: 0, error_index: 0, varbinds: [ {[1, 3, 6, 1, 2, 1, 1, 5, 0], :octet_string, "NewHostName"}, {[1, 3, 6, 1, 2, 1, 1, 6, 0], :octet_string, "Data Center A"} ] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes SNMP GETNEXT request" do message = %{ version: 1, community: "public", pdu: %{ type: :get_next_request, request_id: 5555, error_status: 0, error_index: 0, varbinds: [ {[1, 3, 6, 1, 2, 1, 2, 2, 1, 1], :null, nil} ] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end test "encodes SNMP GETBULK request" do message = %{ version: 1, community: "public", pdu: %{ type: :get_bulk_request, request_id: 7777, non_repeaters: 1, max_repetitions: 10, varbinds: [ {[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, nil}, {[1, 3, 6, 1, 2, 1, 2, 2, 1, 2], :null, nil} ] } } assert {:ok, encoded} = Encoder.encode_message(message) assert byte_size(encoded) > 0 end end end