Current section

Files

Jump to
smppex lib smppex protocol.ex
Raw

lib/smppex/protocol.ex

defmodule SMPPEX.Protocol do
@moduledoc false
alias SMPPEX.Protocol.CommandNames
alias SMPPEX.Protocol.MandatoryFieldsSpecs
alias SMPPEX.Protocol.MandatoryFieldsParser
alias SMPPEX.Protocol.OptionalFieldsParser
alias SMPPEX.Protocol.MandatoryFieldsBuilder
alias SMPPEX.Protocol.OptionalFieldsBuilder
alias SMPPEX.RawPdu
alias SMPPEX.Pdu
@type error :: any
@type pdu_parse_result :: {:pdu, Pdu.t} | {:unparsed_pdu, RawPdu.t, error}
@type parse_result :: {:ok, nil, binary} | {:ok, pdu_parse_result, binary} | {:error, error}
@spec parse(binary) :: parse_result
def parse(bin) when byte_size(bin) < 4 do
{:ok, nil, bin}
end
def parse(bin) do
<<command_length :: big-unsigned-integer-size(32), rest :: binary >> = bin
cond do
command_length < 16 ->
{:error, "Invalid PDU command_length #{inspect command_length}"}
command_length <= byte_size(bin) ->
body_length = command_length - 16
<< header :: binary-size(12), body :: binary-size(body_length), next_pdus :: binary >> = rest
{:ok, parse_pdu(header, body), next_pdus}
true ->
{:ok, nil, bin}
end
end
defp parse_pdu(header, body) do
header = parse_header(header)
raw_pdu = RawPdu.new(header, body)
case CommandNames.name_by_id(RawPdu.command_id(raw_pdu)) do
{:ok, name} -> parse_body_if_needed(name, raw_pdu)
:unknown -> {:unparsed_pdu, raw_pdu, "Unknown command_id"}
end
end
defp parse_header(<<command_id :: big-unsigned-integer-size(32), command_status :: big-unsigned-integer-size(32), sequence_number :: big-unsigned-integer-size(32)>>) do
{command_id, command_status, sequence_number}
end
defp parse_body_if_needed(name, raw_pdu) do
case parse_body(name, raw_pdu) do
{:pdu, _} = pdu -> pdu
{:unparsed_pdu, _, _} = unparsed_pdu ->
if parse_body?(raw_pdu) do
unparsed_pdu
else
{:pdu, Pdu.new(RawPdu.header(raw_pdu), %{}, %{})}
end
end
end
defp parse_body(command_name, raw_pdu) do
case parse_mandatory_fields(command_name, raw_pdu) do
{:ok, fields, rest} ->
case OptionalFieldsParser.parse(rest) do
{:ok, tlvs} ->
{:pdu, Pdu.new(RawPdu.header(raw_pdu), fields, tlvs)}
{:error, error} -> {:unparsed_pdu, raw_pdu, error}
end
{:error, error} -> {:unparsed_pdu, raw_pdu, error}
end
end
defp parse_body?(raw_pdu) do
!RawPdu.resp?(raw_pdu) || RawPdu.success_resp?(raw_pdu)
end
defp parse_mandatory_fields(command_name, raw_pdu) do
spec = MandatoryFieldsSpecs.spec_for(command_name)
raw_pdu |> RawPdu.body |> MandatoryFieldsParser.parse(spec)
end
@type build_result :: {:ok, binary} | {:error, error}
@spec build(Pdu.t) :: build_result
def build(pdu) do
case build_header(pdu) do
{:ok, mandatory_specs, header_bin} -> build_body(pdu, header_bin, mandatory_specs)
{:error, error} -> {:error, {"Error building header part", error}}
end
end
defp build_body(pdu, header_bin, mandatory_specs) do
if build_body?(pdu) do
case build_mandatory_fields(pdu, mandatory_specs) do
{:ok, mandatory_bin} ->
case build_optional_fields(pdu) do
{:ok, optional_bin} -> {:ok, concat_pdu_binary_parts(header_bin, mandatory_bin, optional_bin)}
{:error, error} -> {:error, {"Error building optional field part", error}}
end
{:error, error} -> {:error, {"Error building mandatory field part", error}}
end
else
{:ok, concat_pdu_binary_parts(header_bin, "", "")}
end
end
defp build_header(pdu) do
{command_id, command_status, sequence_number} = {
Pdu.command_id(pdu),
Pdu.command_status(pdu),
Pdu.sequence_number(pdu)
}
case CommandNames.name_by_id(command_id) do
{:ok, name} -> {:ok,
MandatoryFieldsSpecs.spec_for(name),
<<command_id :: big-unsigned-integer-size(32), command_status :: big-unsigned-integer-size(32), sequence_number :: big-unsigned-integer-size(32)>>}
:unknown -> {:error, "Unknown command_id #{inspect command_id}"}
end
end
defp build_body?(pdu) do
!Pdu.resp?(pdu) || Pdu.success_resp?(pdu)
end
defp build_mandatory_fields(pdu, specs) do
pdu |> Pdu.mandatory_fields |> MandatoryFieldsBuilder.build(specs)
end
defp build_optional_fields(pdu) do
pdu |> Pdu.optional_fields |> OptionalFieldsBuilder.build
end
defp concat_pdu_binary_parts(header, mandatory, optional) do
pdu_data = [header, mandatory, optional] |> List.flatten |> Enum.join
size = byte_size(pdu_data) + 4
<< size :: big-unsigned-integer-size(32), pdu_data :: binary >>
end
end