lib/caas.ex

defmodule CaaS do
  use GenServer

  def start_link(args) do
    # GenServer.start_link(__MODULE__, :init, [socket])
    pid = spawn(__MODULE__, :init, [args])
    {:ok, pid}
  end

  def init(args = %{password: _}) do
    socket =
      receive do
        {:pass_socket, socket} -> socket
      end

    :inet.setopts(socket, packet: 4)

    auth = "auth:" <> args.password

    case :gen_tcp.recv(socket, 0) do
      {:ok, ^auth} ->
        :gen_tcp.send(socket,"auth ok\r\n")

        worker(socket, [])

      {:ok, unk} ->
        :gen_tcp.send(socket, "auth error\r\n")
        :gen_tcp.close(socket)
    end
  end

  def worker(socket, context) do
    {:ok, data} = :gen_tcp.recv(socket, 0)
    IO.puts("received #{inspect(data)}")

    context =
      try do
        {res, context} = Code.eval_string(data, context)
        # IO.inspect(context)
        :gen_tcp.send(socket, inspect(res))

        IO.inspect(res)
        context
      catch
        a, b ->
          IO.puts("error #{inspect(a)} #{inspect(b)}")
          IO.puts("#{inspect(__STACKTRACE__)}")
          :gen_tcp.send(socket, "error\r\n#{inspect(b)}")
          context
      end

    __MODULE__.worker(socket, context)
  end
end

defmodule CaaS.Acceptor do
  use GenServer

  def start_link(%{port: port} = args) do
    GenServer.start_link(__MODULE__, args, [])
  end

  def init(%{port: port, password: password} = args) do
    params = %{
      password: password
    }

    IO.puts("caas listen on port #{port}")

    {:ok, listenSocket} =
      :gen_tcp.listen(port, [
        {:ip, {0, 0, 0, 0}},
        {:active, false},
        {:reuseaddr, true},
        {:nodelay, true},
        :binary
      ])

    {:ok, _} = :prim_inet.async_accept(listenSocket, -1)

    {:ok, %{listen_socket: listenSocket, clients: [], params: params}}
  end

  def handle_info(
        {:inet_async, listenSocket, _, {:ok, clientSocket}},
        state = %{params: %{} = params}
      ) do
    :prim_inet.async_accept(listenSocket, -1)
    {:ok, pid} = CaaS.start_link(params)
    :inet_db.register_socket(clientSocket, :inet_tcp)

    :gen_tcp.controlling_process(clientSocket, pid)

    send(pid, {:pass_socket, clientSocket})

    Process.monitor(pid)

    {:noreply, %{state | clients: [pid | state.clients]}}
  end

  def handle_info({:inet_async, _listenSocket, _, error}, state) do
    IO.puts(
      "#{inspect(__MODULE__)}: Error in inet_async accept, shutting down. #{inspect(error)}"
    )

    {:stop, error, state}
  end

  def handle_info(_, state) do
    {:noreply, state}
  end

  def handle_call(:get_clients, _from, state) do
    {:reply, state.clients, state}
  end
end