defmodule MAIL.X420.P3 do use ThousandIsland.Handler def start_link(port: port) do :logger.info(~c"Starting MAIL.X420 P3 (Thousand Island) on 0.0.0.0:~p", [port]) ThousandIsland.start_link( handler_module: __MODULE__, port: port, num_acceptors: System.schedulers_online() * 4, # масштабується з кількістю ядер num_connections: :infinity, # знімаємо обмеження (контроль через OS ulimit) num_listen_sockets: 1, read_timeout: :infinity, # persistent chat-з'єднання shutdown_timeout: 30_000, transport_module: ThousandIsland.Transports.TCP, transport_options: [ :binary, reuseaddr: true, nodelay: true, keepalive: true, backlog: 16_384, sndbuf: 1_048_576, recbuf: 1_048_576 ] ) end def child_spec(opt) do %{ id: MAIL.X420.P3, start: {MAIL.X420.P3, :start_link, [opt]}, type: :supervisor, restart: :permanent, shutdown: :infinity } end def handle_connection(socket, _state) do # Extract which protocol this connection belongs to (by transport info) {:ok, {_, port}} = :inet.port(socket.transport_socket) protocol = case port do 17001 -> "P1 (MTA transfer)" 17002 -> "P3 (UA submission/delivery)" 17003 -> "P7 (MS access)" _ -> "Unknown" end IO.puts("[#{protocol}] New connection from #{inspect(socket.remote_address)}") # Store buffer and protocol in handler state {:ok, %{buffer: <<>>, protocol: protocol, socket: socket}} end def handle_data(data, %{buffer: buffer, protocol: protocol, socket: socket} = state) do new_buffer = buffer <> data IO.puts("[#{protocol}] Received #{byte_size(data)} bytes:") IO.puts(:io_lib.format("~.16B~n", [new_buffer |> :binary.bin_to_list() |> Enum.chunk_every(16)])) # Very naive "first packet" detection – real code would parse ACSE A-ASSOCIATE.req if buffer == <<>> do # Simulate accepting the association (send a minimal A-ASSOCIATE.rsp placeholder) accept_response = <<0x61, 0x80, 0x80, 0x00>> # Dummy BER-encoded accept (not real!) ThousandIsland.Socket.send(socket, accept_response) IO.puts("[#{protocol}] Sent dummy association accept") end # Placeholder for real protocol handling # case parse_pdu(new_buffer) do # {:ok, pdu, rest} -> handle_pdu(pdu, socket, protocol); {:continue, %{state | buffer: rest}} # :incomplete -> {:continue, state} # end # For this basic version: keep connection open, continue accumulating {:continue, %{state | buffer: new_buffer}} end def handle_close(_socket, state) do IO.puts("[#{state.protocol}] Connection closed") :ok end def handle_error(reason, state) do IO.puts("[#{state.protocol}] Error: #{inspect(reason)}") :ok end end