Current section
Files
Jump to
Current section
Files
lib/membrane_tcp/socket.ex
defmodule Membrane.TCP.Socket do
@moduledoc false
@enforce_keys [:connection_side, :port_no, :ip_address]
defstruct [
:port_no,
:ip_address,
:socket_handle,
:connection_side,
state: :uninitialized,
sock_opts: []
]
@type t :: %__MODULE__{
ip_address: :inet.socket_address(),
port_no: :inet.port_number(),
socket_handle: :gen_tcp.socket() | nil,
state: :listening | :connected | :uninitialized,
connection_side: :server | :client,
sock_opts: [:gen_tcp.option()]
}
@spec listen(socket :: t()) :: {:ok, listen_socket :: t()} | {:error, :inet.posix()}
def listen(%__MODULE__{port_no: port_no, ip_address: ip, sock_opts: sock_opts} = local_socket) do
listen_result =
:gen_tcp.listen(port_no, [:binary, ip: ip, active: false, reuseaddr: true] ++ sock_opts)
with {:ok, listen_socket_handle} <- listen_result,
# Port may change if 0 is used, ip - when either `:any` or `:loopback` is passed
{:ok, {real_ip_addr, real_port_no}} <- :inet.sockname(listen_socket_handle) do
updated_socket = %__MODULE__{
local_socket
| socket_handle: listen_socket_handle,
port_no: real_port_no,
ip_address: real_ip_addr,
state: :listening
}
{:ok, updated_socket}
end
end
@spec accept(listening_socket :: t()) ::
{:ok, connected_socket :: t()} | {:error, :inet.posix()}
def accept(%__MODULE__{socket_handle: socket_handle, state: :listening} = socket) do
accept_result = :gen_tcp.accept(socket_handle)
with {:ok, connected_socket_handle} <- accept_result do
:gen_tcp.close(socket_handle)
updated_socket = %__MODULE__{
socket
| socket_handle: connected_socket_handle,
state: :connected
}
{:ok, updated_socket}
end
end
@spec connect(local :: t(), target :: t()) :: {:ok, t()} | {:error, :inet.posix()}
def connect(
%__MODULE__{port_no: local_port_no, ip_address: local_ip, sock_opts: sock_opts} =
local_socket,
%__MODULE__{port_no: target_port_no, ip_address: target_ip}
) do
connect_result =
:gen_tcp.connect(
target_ip,
target_port_no,
[:binary, ip: local_ip, port: local_port_no, active: false, reuseaddr: true] ++ sock_opts
)
with {:ok, socket_handle} <- connect_result,
# Port may change if 0 is used, ip - when either `:any` or `:loopback` is passed
{:ok, {real_ip_addr, real_port_no}} <- :inet.sockname(socket_handle) do
updated_socket = %__MODULE__{
local_socket
| socket_handle: socket_handle,
port_no: real_port_no,
ip_address: real_ip_addr,
state: :connected
}
{:ok, updated_socket}
end
end
@spec close(socket :: t()) :: t()
def close(%__MODULE__{socket_handle: handle} = socket) when is_port(handle) do
:ok = :gen_tcp.close(handle)
%__MODULE__{socket | socket_handle: nil, state: :uninitialized}
end
@spec send(local_socket :: t(), payload :: Membrane.Payload.t()) ::
:ok | {:error, :closed | :inet.posix()}
def send(%__MODULE__{socket_handle: socket_handle}, payload) when is_port(socket_handle) do
:gen_tcp.send(socket_handle, payload)
end
@spec recv(socket :: t(), timeout :: non_neg_integer()) ::
{:ok, Membrane.Payload.t()} | {:error, :closed | :timeout | :inet.posix()}
def recv(%__MODULE__{socket_handle: socket_handle}, timeout \\ 0) do
:gen_tcp.recv(socket_handle, 0, timeout)
end
end