Skip to content
5 changes: 5 additions & 0 deletions lib/switchx.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand Down
29 changes: 23 additions & 6 deletions lib/switchx/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ defmodule SwitchX.Connection do
applications_pending: Map.new()
]

@impl true
def callback_mode() do
:handle_event_function
end
Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)}")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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}, %{})
Expand Down
39 changes: 33 additions & 6 deletions lib/switchx/connection/inbound.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading