defmodule Grizzly.Transport do
@moduledoc """
Behaviour and functions for communicating to `zipgateway`
"""
alias Grizzly.ZIPGateway
defmodule Response do
@moduledoc """
The response from parse response
"""
alias Grizzly.ZWave.Command
@type t() :: %__MODULE__{
port: :inet.port_number() | nil,
ip_address: :inet.ip_address() | nil,
command: Command.t()
}
@enforce_keys [:command]
defstruct port: nil, ip_address: nil, command: nil
end
alias Grizzly.ZWave.{Command, DecodeError}
@opaque t() :: %__MODULE__{impl: module(), assigns: map()}
@type socket() :: :ssl.sslsocket() | :inet.socket()
@type args() :: [
ip_address: :inet.ip_address(),
port: :inet.port_number(),
transport: t()
]
@type parse_opt() :: {:raw, boolean()}
@typedoc """
After starting a server options can be passed back to the caller so that the
caller can do any other work it might seem fit.
Options:
* - `:strategy` - this informs the caller if the transport needs to wait for
connects to accept or if the socket can just process incoming messages.
If the strategy is `:accept` that is to mean the socket is okay to start
accepting new connections.
"""
@type listen_option() :: {:strategy, :none | :accept}
@enforce_keys [:impl]
defstruct assigns: %{}, impl: nil
@callback open(keyword()) :: {:ok, t()} | {:error, :timeout}
@callback listen(t()) :: {:ok, t(), [listen_option()]} | {:error, any()}
@callback accept(t()) :: {:ok, t()} | {:error, any()}
@callback handshake(t()) :: {:ok, t()} | {:error, any()}
@callback send(t(), binary(), keyword()) :: :ok
@callback peername(t()) :: {:ok, {:inet.ip_address(), :inet.port_number()}} | {:error, any()}
@callback parse_response(any(), [parse_opt()]) ::
{:ok, Response.t() | binary() | :connection_closed} | {:error, DecodeError.t()}
@callback close(t()) :: :ok
@doc """
Make a new `Grizzly.Transport`
If need to optionally assign some priv data you can map that into this function.
"""
@spec new(module(), map()) :: t()
def new(impl, assigns \\ %{}) do
%__MODULE__{
impl: impl,
assigns: assigns
}
end
@doc """
Update the assigns with this field and value
"""
@spec assigns(t(), atom(), any()) :: t()
def assigns(transport, assign, assign_value) do
new_assigns = Map.put(transport.assigns, assign, assign_value)
%__MODULE__{transport | assigns: new_assigns}
end
@doc """
Get the assign value for the field
"""
@spec assign(t(), atom(), any()) :: any()
def assign(transport, assign, default \\ nil),
do: Map.get(transport.assigns, assign, default)
@doc """
Listen using a transport
"""
@spec listen(t()) :: {:ok, t(), [listen_option()]} | {:error, any()}
def listen(transport) do
%__MODULE__{impl: transport_impl} = transport
transport_impl.listen(transport)
end
@doc """
Accept a new connection
"""
@spec accept(t()) :: {:ok, t()} | {:error, any()}
def accept(transport) do
%__MODULE__{impl: transport_impl} = transport
transport_impl.accept(transport)
end
@doc """
Preform the handshake
"""
@spec handshake(t()) :: {:ok, t()} | {:error, any()}
def handshake(transport) do
%__MODULE__{impl: transport_impl} = transport
transport_impl.handshake(transport)
end
@doc """
Open the transport
"""
@spec open(module(), args()) :: {:ok, t()} | {:error, :timeout}
def open(transport_module, args) do
transport_module.open(args)
end
@doc """
Send binary data using a transport
"""
@spec send(t(), binary(), keyword()) :: :ok
def send(transport, binary, opts \\ []) do
%__MODULE__{impl: transport_impl} = transport
transport_impl.send(transport, binary, opts)
end
@spec peername(t()) :: {:ok, {:inet.ip_address(), :inet.port_number()}} | {:error, any()}
def peername(transport) do
%__MODULE__{impl: transport_impl} = transport
transport_impl.peername(transport)
end
@spec node_id(t()) :: {:ok, Grizzly.node_id()} | {:error, any()}
def node_id(transport) do
case peername(transport) do
{:ok, {ip, _}} -> {:ok, ZIPGateway.node_id_from_ip(ip)}
{:error, reason} -> {:error, reason}
end
end
@doc """
Parse the response for the transport
"""
@spec parse_response(t(), any()) ::
{:ok, Response.t() | binary() | :connection_closed} | {:error, DecodeError.t()}
def parse_response(transport, response, opts \\ []) do
%__MODULE__{impl: transport_impl} = transport
opts = [transport: transport] ++ opts
transport_impl.parse_response(response, opts)
end
end