Current section

Files

Jump to
grizzly lib grizzly unsolicited_server socket.ex
Raw

lib/grizzly/unsolicited_server/socket.ex

defmodule Grizzly.UnsolicitedServer.Socket do
@moduledoc false
use GenServer
require Logger
alias Grizzly.{Report, SeqNumber, Transport, ZWave, ZIPGateway}
alias Grizzly.ZWave.Command
alias Grizzly.ZWave.Commands.ZIPPacket
alias Grizzly.UnsolicitedServer.{Messages, SocketSupervisor, ResponseHandler}
@spec child_spec(Transport.t()) :: map()
def child_spec(transport) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [transport]},
restart: :temporary
}
end
@spec start_link(Transport.t()) :: GenServer.on_start()
def start_link(listening_transport) do
GenServer.start_link(__MODULE__, listening_transport)
end
@impl GenServer
def init(transport) do
{:ok, transport, {:continue, :accept}}
end
@impl GenServer
def handle_continue(:accept, listening_transport) do
with {:ok, accept_transport} <- Transport.accept(listening_transport),
{:ok, _sock} <- Transport.handshake(accept_transport) do
# Start a new listen socket to replace this one as this one is now not
# open for more traffic now
{:ok, _} = SocketSupervisor.start_socket(listening_transport)
else
other ->
Logger.warn(
"grizzly: Failed to start socket. Got this on SSL handshake: #{inspect(other)}"
)
end
{:noreply, listening_transport}
end
@impl GenServer
def handle_info({:ssl_closed, _} = message, transport) do
Logger.error("[CLOSED]: #{inspect(message)}")
{:stop, :normal, transport}
end
def handle_info({:grizzly, :binary_response, _does_not_matter}, transport) do
{:noreply, transport}
end
def handle_info(response, transport) do
{:ok, transport_response} = Transport.parse_response(transport, response)
case ResponseHandler.handle_response(transport_response) do
[] ->
:ok
actions ->
Enum.each(actions, &run_response_action(transport_response, &1))
end
{:noreply, transport}
end
defp run_response_action(response, {:send, command}) do
%Transport.Response{ip_address: ip_address, command: zippacket} = response
# We have to perverse the header extensions to ensure command encapsulation
# is correct when sending the response back to the Z-Wave PAN.
header_extensions = Command.param!(zippacket, :header_extensions)
node_id = ZIPGateway.node_id_from_ip(ip_address)
_ = send_ack_response(node_id, zippacket)
{:ok, zip_packet} =
ZIPPacket.with_zwave_command(command, SeqNumber.get_and_inc(),
flag: :ack_request,
header_extensions: header_extensions
)
binary = ZWave.to_binary(zip_packet)
Grizzly.send_binary(node_id, binary)
end
defp run_response_action(response, {:notify, command}) do
:ok = Messages.broadcast(response.ip_address, command)
end
defp run_response_action(response, {:forward_to_controller, command}) do
case Grizzly.send_command(:gateway, command.name, command.params) do
{:ok, report} ->
handle_grizzly_report(report, response)
error ->
error
end
end
defp handle_grizzly_report(%Report{type: :ack_response}, response) do
%Transport.Response{ip_address: ip_address} = response
zip_packet = ZIPPacket.make_ack_response(SeqNumber.get_and_inc())
binary = ZWave.to_binary(zip_packet)
node_id = ZIPGateway.node_id_from_ip(ip_address)
Grizzly.send_binary(node_id, binary)
end
defp handle_grizzly_report(%Report{type: :command, command: command}, response) do
%Transport.Response{ip_address: ip_address} = response
{:ok, zip_packet} = ZIPPacket.with_zwave_command(command, SeqNumber.get_and_inc(), flag: nil)
binary = ZWave.to_binary(zip_packet)
node_id = ZIPGateway.node_id_from_ip(ip_address)
Grizzly.send_binary(node_id, binary)
end
defp send_ack_response(node_id, zippacket) do
header_extensions = Command.param!(zippacket, :header_extensions)
seq = Command.param!(zippacket, :seq_number)
ack_bin =
seq
|> ZIPPacket.make_ack_response(header_extensions: header_extensions)
|> ZWave.to_binary()
Grizzly.send_binary(node_id, ack_bin)
end
end