lib/kalevala/telnet/listener.ex

defmodule Kalevala.Telnet.Listener do
  @moduledoc """
  Process that starts the `ranch` listener
  """

  use GenServer

  require Logger

  alias Kalevala.Telnet.Protocol

  def start_link(opts \\ []) do
    opts = Enum.into(opts, %{})
    GenServer.start_link(__MODULE__, opts, Map.get(opts, :otp, []))
  end

  def init(config) do
    {:ok, %{config: config}, {:continue, :listen_tcp}}
  end

  def handle_continue(:listen_tcp, state) do
    telnet_config = Map.get(state.config, :telnet, [])
    port = Keyword.get(telnet_config, :port, 4444)

    opts = %{
      socket_opts: [{:port, port}],
      max_connections: 102_400
    }

    options = Map.take(state.config, [:foreman, :protocol])

    case :ranch.start_listener({__MODULE__, :tcp}, :ranch_tcp, opts, Protocol, options) do
      {:ok, listener} ->
        set_listener(state, listener)

      {:error, {:already_started, listener}} ->
        set_listener(state, listener)
    end
  end

  def handle_continue(:listen_tls, state) do
    telnet_config = Map.get(state.config, :tls, [])
    port = Keyword.get(telnet_config, :port, 4444)

    opts = %{
      socket_opts: [
        {:port, port},
        {:keyfile, keyfile(state.config.tls)},
        {:certfile, certfile(state.config.tls)}
      ],
      max_connections: 4096
    }

    options = Map.take(state.config, [:foreman, :protocol])

    case :ranch.start_listener({__MODULE__, :tls}, :ranch_ssl, opts, Protocol, options) do
      {:ok, listener} ->
        set_tls_listener(state, listener)

      {:error, {:already_started, listener}} ->
        set_tls_listener(state, listener)
    end
  end

  defp keyfile(config) do
    case config[:keyfile] do
      nil ->
        raise "The `keyfile` config must be set if tls is true!"

      keyfile ->
        keyfile
    end
  end

  defp certfile(config) do
    case config[:certfile] do
      nil ->
        raise "The `certfile` config must be set if tls is true!"

      certfile ->
        certfile
    end
  end

  defp set_listener(state, listener) do
    Logger.info("Telnet Listener Started")

    state = Map.put(state, :listener, listener)

    tls_config = Map.get(state.config, :tls)

    case is_nil(tls_config) || Enum.empty?(tls_config) do
      true ->
        {:noreply, state}

      false ->
        {:noreply, state, {:continue, :listen_tls}}
    end
  end

  defp set_tls_listener(state, listener) do
    Logger.info("TLS Listener Started")
    state = Map.put(state, :tls_listener, listener)
    {:noreply, state}
  end
end