defmodule Modbux.Tcp do
@moduledoc """
Tcp message helper, functions that handles TCP responses/requests messages.
"""
alias Modbux.Request
alias Modbux.Response
require Logger
@spec pack_req(
{:fc | :phr | :rc | :rhr | :ri | :rir, integer, integer, maybe_improper_list | integer},
integer
) :: <<_::48, _::_*8>>
def pack_req(cmd, transid) do
cmd |> Request.pack() |> wrap(transid)
end
@spec parse_req(<<_::48, _::_*8>>) ::
{{:einval | :error | :fc | :phr | :rc | :rhr | :ri | :rir, byte, char, [any] | char}, char}
def parse_req(wraped) do
{pack, transid} = wraped |> unwrap
{pack |> Request.parse(), transid}
end
@spec pack_res(
{:fc | :phr | :rc | :rhr | :ri | :rir, integer, any, maybe_improper_list | integer},
any,
integer
) :: <<_::48, _::_*8>>
def pack_res(cmd, values, transid) do
cmd |> Response.pack(values) |> wrap(transid)
end
@spec parse_res(any, <<_::48, _::_*8>>, char) :: nil | [any] | {:error, any} | {:error, byte, <<_::104>>}
def parse_res(cmd, wraped, transid) do
Response.parse(cmd, wraped |> unwrap(transid))
end
@spec res_len({:fc | :phr | :rc | :rhr | :ri | :rir, any, any, any}) :: number
def res_len(cmd) do
Response.length(cmd) + 6
end
@spec req_len({:fc | :phr | :rc | :rhr | :ri | :rir, any, any, any}) :: integer
def req_len(cmd) do
Request.length(cmd) + 6
end
@spec wrap(binary, integer) :: <<_::48, _::_*8>>
def wrap(payload, transid) do
size = :erlang.byte_size(payload)
<<transid::16, 0, 0, size::16, payload::binary>>
end
@spec unwrap(<<_::48, _::_*8>>, char) :: nil | binary
def unwrap(<<transid::16, protocol_id_h, protocol_id_l, size::16, payload::binary>> = msg, transid) do
r_size = :erlang.byte_size(payload)
check_protocol_identifier(protocol_id_h, protocol_id_l)
data =
if size == r_size do
payload
else
Logger.error(
"(#{__MODULE__}) size = #{size}, payload_size = #{r_size}, msg = #{inspect(msg, base: :hex)}"
)
nil
end
data
end
@spec unwrap(<<_::48, _::_*8>>) :: {binary(), char()}
def unwrap(<<transid::16, protocol_id_h, protocol_id_l, size::16, payload::binary>>) do
^size = :erlang.byte_size(payload)
check_protocol_identifier(protocol_id_h, protocol_id_l)
{payload, transid}
end
def unwrap(inv_data) do
Logger.error("(#{__MODULE__}) invalid data: #{inspect(inv_data, base: :hex)}")
raise("(#{__MODULE__}) invalid data: #{inspect(inv_data)}")
end
# Protocol identifier -> MODBUS protocol = 0x00, 0x00
defp check_protocol_identifier(0, 0), do: :ok
defp check_protocol_identifier(protocol_id_h, protocol_id_l),
do: Logger.warning("(#{__MODULE__}) Protocol Identifier: #{inspect(<<protocol_id_h, protocol_id_l>>, base: :hex)}")
end