Packages
smppex
2.3.0
3.3.0
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
retired
3.1.0
retired
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.4.0
2.3.3
2.3.2
2.3.1
2.3.0
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.1.0
2.0.1
2.0.0
1.0.1
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.12
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
0.0.5
0.0.4
0.0.3
0.0.2
0.0.1
SMPP 3.4 protocol and framework implemented in Elixir
Current section
Files
Jump to
Current section
Files
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