Packages
smppex
0.1.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
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
@pdus_with_status_dependant_body %{
0x80000001 => true, # bind_transmitter_resp
0x80000002 => true, # bind_receiver_resp
0x80000009 => true, # bind_transceiver_resp
0x80000004 => true # submit_sm_resp
}
@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(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(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_mandatory?(raw_pdu) do
command_status = RawPdu.command_status(raw_pdu)
command_id = RawPdu.command_id(raw_pdu)
(command_status == 0) or (not Map.has_key?(@pdus_with_status_dependant_body, command_id))
end
defp parse_mandatory_fields(command_name, raw_pdu) do
case parse_mandatory?(raw_pdu) do
true ->
spec = MandatoryFieldsSpecs.spec_for(command_name)
raw_pdu |> RawPdu.body |> MandatoryFieldsParser.parse(spec)
false ->
{:ok, %{}, RawPdu.body(raw_pdu) }
end
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
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
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_mandatory?(pdu) do
command_status = Pdu.command_status(pdu)
command_id = Pdu.command_id(pdu)
(command_status == 0) or (not Map.has_key?(@pdus_with_status_dependant_body, command_id))
end
defp build_mandatory_fields(pdu, specs) do
case build_mandatory?(pdu) do
true -> pdu |> Pdu.mandatory_fields |> MandatoryFieldsBuilder.build(specs)
false -> {:ok, <<>>}
end
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