diff --git a/lib/switchx.ex b/lib/switchx.ex index c5eda38..282b5a1 100644 --- a/lib/switchx.ex +++ b/lib/switchx.ex @@ -219,6 +219,11 @@ defmodule SwitchX do :gen_statem.stop(conn, :normal, 1_000) end + @spec stop(conn :: Pid) :: :ok | {:error, term} + def stop(conn) do + :gen_statem.stop(conn, :normal, 1_000) + end + @doc """ Hang up the call with a hangup_cause. """ diff --git a/lib/switchx/connection.ex b/lib/switchx/connection.ex index 05495cd..979b54a 100644 --- a/lib/switchx/connection.ex +++ b/lib/switchx/connection.ex @@ -21,6 +21,7 @@ defmodule SwitchX.Connection do applications_pending: Map.new() ] + @impl true def callback_mode() do :handle_event_function end @@ -33,6 +34,7 @@ defmodule SwitchX.Connection do :gen_statem.start_link(__MODULE__, [session_module, socket, :outbound], []) end + @impl true def init([owner, socket, :inbound]) when is_port(socket) do {:ok, {host, port}} = :inet.peername(socket) @@ -49,6 +51,7 @@ defmodule SwitchX.Connection do {:ok, :connecting, data} end + @impl true def init([session_module, socket, :outbound]) when is_port(socket) do {:ok, {host, port}} = :inet.peername(socket) @@ -72,22 +75,25 @@ defmodule SwitchX.Connection do end ## Handler events ## - - def handle_event({:call, from}, {:close}, state, data) do + @impl true + def handle_event({:call, from}, {:close}, _state, data) do :gen_tcp.close(data.socket) :gen_statem.reply(from, :ok) {:next_state, :disconnected, data} end + @impl true def handle_event({:call, from}, message, state, data) do apply(__MODULE__, state, [:call, message, from, data]) end + @impl true def handle_event(:info, {:tcp, _socket, "\n"}, _state, data) do # Empty line discarding {:keep_state, data} end + @impl true def handle_event(:info, :read_data, :connecting, data) do :gen_tcp.send(data.socket, "connect\n\n") event = Socket.recv(data.socket) @@ -99,6 +105,7 @@ defmodule SwitchX.Connection do {:next_state, :ready, data} end + @impl true def handle_event(:info, {:tcp, socket, payload}, state, data) do event = Socket.recv(socket, payload) :inet.setopts(socket, active: :once) @@ -111,11 +118,14 @@ defmodule SwitchX.Connection do end end + @impl true def handle_event(:info, _message, _state, data) do {:keep_state, data} end + @impl true def handle_event(:disconnect, event, state, data) do + Logger.info("handle_event :disconnect #{inspect(event)}") case event.headers["Content-Disposition"] do "linger" -> Logger.info("Disconnect hold due to linger, keeping state #{inspect(state)}") @@ -155,6 +165,7 @@ defmodule SwitchX.Connection do def ready(:call, {:api, args}, from, data) do data = put_in(data.api_calls, :queue.in(from, data.api_calls)) + :gen_tcp.send(data.socket, "api #{args}\n\n") {:keep_state, data} end @@ -252,13 +263,13 @@ defmodule SwitchX.Connection do end def ready(:call, {:bgapi, args}, from, data) do - job_uuid = UUID.uuid4() + # job_uuid = UUID.uuid4() :gen_tcp.send(data.socket, "bgapi #{args}\n\n") data = put_in(data.commands_sent, :queue.in(from, data.commands_sent)) {:keep_state, data} end - def disconnected(:call, payload, from, data) do + def disconnected(:call, _payload, from, data) do :gen_statem.reply(from, {:error, :disconnected}) {:keep_state, data} end @@ -284,7 +295,7 @@ defmodule SwitchX.Connection do %{headers: %{"Content-Type" => "command/reply", "Reply-Text" => "+OK accepted"}}, data ) do - Logger.info("Connected") + Logger.info("ESL Authenticated to FreeSWITCH") {:next_state, :ready, reply_from_queue("commands_sent", {:ok, "Accepted"}, data)} end @@ -328,18 +339,24 @@ defmodule SwitchX.Connection do end def ready(:event, event, data) do + # Logger.info("SwitchX ready: #{inspect(event)}") send(data.owner, {:switchx_event, event}) {:keep_state, data} end - def disconnected(:event, %{headers: %{"Content-Type" => "text/disconnect-notice"}}, data) do + def disconnected(:event, %{headers: %{"Content-Type" => "text/disconnect-notice"}} = event, data) do + + Logger.info("SwitchX disconnected with text/disconnect-notice: #{inspect(event)}") + send(data.owner, {:switchx_event, event}) {:keep_state, data} end def disconnected(:event, payload, data) do + Logger.info("SwitchX disconnected: #{inspect(payload)}") {:keep_state, data} end + @impl true def terminate(reason, _state, data) do :telemetry.execute([:switchx, :connection, data.connection_mode], %{value: -1}, %{}) diff --git a/lib/switchx/connection/inbound.ex b/lib/switchx/connection/inbound.ex index 0647f39..db81d97 100644 --- a/lib/switchx/connection/inbound.ex +++ b/lib/switchx/connection/inbound.ex @@ -5,6 +5,9 @@ defmodule SwitchX.Connection.Inbound do Inbound mode means you run your applications as clients, and connect to the FreeSWITCH server to invoke commands and control FreeSWITCH. """ + + require Logger + @mode :inbound @socket_opts [:binary, active: :once, packet: :line] @timeout 5_000 @@ -40,14 +43,38 @@ defmodule SwitchX.Connection.Inbound do end end + # added socket to the return tuple in a successful connection + def start(opts) do + host = Keyword.fetch!(opts, :host) + port = Keyword.fetch!(opts, :port) + + case perform_connect(host, port, @socket_opts, @timeout) do + {:ok, socket} -> + {:ok, client} = SwitchX.Connection.start_link(self(), socket, @mode) + :gen_tcp.controlling_process(socket, client) + {:ok, client, socket} + + {:error, reason} -> + {:error, reason} + end + end + + def close(client, socket) do + Logger.info("Closing gen_tcp connection") + :gen_tcp.close(socket) + :gen_statem.stop(client) + end + defp perform_connect(host, port, socket_opts, timeout) when is_binary(host) do - host = - host - |> String.split(".") - |> Enum.map(&String.to_integer/1) - |> List.to_tuple() + # Try to parse as an IP address first, otherwise treat as hostname + case :inet.parse_address(String.to_charlist(host)) do + {:ok, ip_tuple} -> + :gen_tcp.connect(ip_tuple, port, socket_opts, timeout) - perform_connect(host, port, socket_opts, timeout) + {:error, :einval} -> + # Not a valid IP address, treat as hostname (charlist format) + :gen_tcp.connect(String.to_charlist(host), port, socket_opts, timeout) + end end defp perform_connect(host, port, socket_opts, timeout) when is_tuple(host) do diff --git a/mix.exs b/mix.exs index 26a54fb..902cf9d 100644 --- a/mix.exs +++ b/mix.exs @@ -48,8 +48,8 @@ defmodule SwitchX.MixProject do [ {:uuid, "~> 1.1"}, {:mock, "~> 0.3.0", only: :test}, - {:telemetry, "~> 1.3.0"}, - {:ex_doc, "~> 0.19", only: :dev, runtime: false} + {:telemetry, "~> 1.0"}, + {:ex_doc, "~> 0.30", only: :dev, runtime: false} ] end end