defmodule RNS.Interface.TCPServer do @moduledoc """ TCP server interface. This is a simple tcp server, which will spawn a process for each connection. """ @type params() :: map() require Logger @spec start(params(), RNS.Config.transport()) :: pid() def start(params, transport) do spawn(__MODULE__, :init, [params, transport]) end # Callbacks @spec init(params(), RNS.Config.transport()) :: no_return() def init(%{"port" => port} = params, transport) do opts = opts(params) case :gen_tcp.listen(port, [:binary, {:reuseaddr, true}]) do {:ok, listen_socket} -> Logger.debug("TCP Server now listening on port #{port}...") loop(listen_socket, transport, opts) {:error, error} -> Logger.warning("TCP server on port #{port} failed to start with reason #{inspect(error)}") exit(error) end end @spec loop(:inet.socket(), RNS.Config.transport(), Keyword.t()) :: no_return() def loop(listen_socket, transport, opts) do {:ok, client_socket} = :gen_tcp.accept(listen_socket) pid = RNS.Interface.TCPServer.Client.start(client_socket, transport, opts) :gen_tcp.controlling_process(client_socket, pid) loop(listen_socket, transport, opts) end @spec opts(map()) :: Keyword.t() defp opts(params) do mode = case params["mode"] do nil -> :full "full" -> :full "access_point" -> :access_point "gateway" -> :gateway "roaming" -> :roaming "boundary" -> :boundary mode -> Logger.warning(""" Unknown interface mode '#{mode}' on TCP server interface, 'full' will be used instead! The interface mode can be 'full', 'access_point', 'gateway', 'roaming' or 'boundary'. You can read more about interface modes at https://reticulum.network/manual/interfaces.html#interface-modes. """) :full end [mode: mode] end end defmodule RNS.Interface.TCPServer.Client do @moduledoc """ TCP server client handler interface. This genserver is spawned for each connection to the TCP server. """ @type state() :: {RNS.Interface.t(), :inet.socket(), RNS.Config.transport()} use GenServer @spec start(:inet.socket(), RNS.Config.transport(), Keyword.t()) :: pid() def start(socket, transport, opts) do {:ok, pid} = GenServer.start(__MODULE__, {socket, transport, opts}) pid end # Callbacks @impl true @spec init({:inet.socket(), RNS.Config.transport(), Keyword.t()}) :: {:ok, state()} def init({socket, transport, opts}) do {:ok, {host, port}} = :inet.peername(socket) interface = RNS.Interface.new("TCPServerClient@#{host}:#{port}", self(), opts) # Tell the interface manager the interface has started and send the pid and interface struct. RNS.InterfaceManager.interface_started(interface) {:ok, {interface, socket, transport}} end @impl true @spec handle_info({:inbound, RNS.Packet.t(), RNS.Interface.t()}, state()) :: {:noreply, state()} def handle_info({:inbound, packet, _}, state) do socket = elem(state, 1) framed_raw = RNS.Packet.raw(packet) |> RNS.HDLC.frame() :gen_tcp.send(socket, framed_raw) {:noreply, state} end @impl true @spec handle_info({:tcp, :inet.socket(), binary()}, state()) :: {:noreply, state()} def handle_info({:tcp, _socket, packet}, {interface, _, transport} = state) do RNS.PacketHandler.start(fn -> RNS.HDLC.unframe(packet) end, interface, transport) {:noreply, state} end @impl true @spec handle_info({:tcp_closed, :inet.socket()}, state()) :: {:stop, :tcp_closed} def handle_info({:tcp_closed, _socket}, _state) do {:stop, :tcp_closed} end @impl true @spec handle_info({:tcp_closed, :inet.socket()}, state()) :: {:stop, atom()} def handle_info({:tcp_error, _socket, error}, _state) do {:stop, error} end @impl true @spec handle_info({:transport_change, RNS.Config.transport()}, state()) :: {:noreply, state()} def handle_info({:transport_change, transport}, {interface, socket, _old_transport}) do new_state = {interface, socket, transport} {:noreply, new_state} end end