Packages
smppex
2.2.8
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/pdu.ex
defmodule SMPPEX.Pdu do
@moduledoc """
Module for working with Pdu struct representing parsed SMPP PDU
"""
alias SMPPEX.Protocol.TlvFormat
alias SMPPEX.Protocol.CommandNames
alias SMPPEX.Pdu
@type t :: %Pdu{
command_id: non_neg_integer,
command_status: non_neg_integer,
sequence_number: non_neg_integer,
ref: reference,
mandatory: map,
optional: map
}
defstruct command_id: 0,
command_status: 0,
sequence_number: 0,
ref: nil,
mandatory: %{},
optional: %{}
@type header :: {non_neg_integer, non_neg_integer, non_neg_integer} | non_neg_integer
@spec new(header, map, map) :: t
@doc """
Construct a new Pdu from header, mandatory fields and optional(TLV) fields.
Header may be either an integer, then it is treated as command id,
or a tuple `{command_id, command_status, sequence_number}`
Each Pdu is created with a unique ref field, by which one can later
trace Pdu's identity.
## Examples
iex(1)> SMPPEX.Pdu.new(1)
%SMPPEX.Pdu{command_id: 1, command_status: 0, mandatory: %{}, optional: %{},
ref: #Reference<0.0.3.215>, sequence_number: 0}
iex(2)> SMPPEX.Pdu.new({1, 0, 123}, %{system_id: "sid", password: "pass"}, %{})
%SMPPEX.Pdu{command_id: 1, command_status: 0,
mandatory: %{password: "pass", system_id: "sid"}, optional: %{},
ref: #Reference<0.0.3.219>, sequence_number: 123}
"""
def new(header, mandatory_fields \\ %{}, optional_fields \\ %{}) do
case header do
c_id when is_integer(c_id) ->
%Pdu{
command_id: c_id,
ref: make_ref(),
mandatory: mandatory_fields,
optional: optional_fields
}
{c_id, c_status, s_number} ->
%Pdu{
command_id: c_id,
command_status: c_status,
sequence_number: s_number,
ref: make_ref(),
mandatory: mandatory_fields,
optional: optional_fields
}
end
end
@spec command_name(t) :: atom
@doc """
Returns Pdu's symbolic command name as an atom or `:unknown` if Pdu's `command_id`
do not correspond to any real SMPP command.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new(1)
iex(2)> SMPPEX.Pdu.command_name(pdu)
:bind_receiver
iex(3)> pdu = SMPPEX.Pdu.new(1111111)
iex(4)> SMPPEX.Pdu.command_name(pdu)
:unknown
"""
def command_name(pdu) do
case CommandNames.name_by_id(pdu.command_id) do
{:ok, name} -> name
:unknown -> :unknown
end
end
@spec command_id(t) :: non_neg_integer
@doc """
Returns Pdu's `command_id`.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new(1)
iex(2)> SMPPEX.Pdu.command_id(pdu)
1
"""
def command_id(pdu) do
pdu.command_id |> to_int
end
@spec command_status(t) :: non_neg_integer
@doc """
Returns Pdu's `command_status`.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new({1, 4, 123})
iex(2)> SMPPEX.Pdu.command_status(pdu)
4
"""
def command_status(pdu) do
pdu.command_status |> to_int
end
@spec sequence_number(t) :: non_neg_integer
@doc """
Returns Pdu's `sequence_number`.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new({1, 4, 123})
iex(2)> SMPPEX.Pdu.sequence_number(pdu)
123
"""
def sequence_number(pdu) do
pdu.sequence_number |> to_int
end
@doc """
Returns Pdu's unique reference `ref`.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new({1, 4, 123})
iex(2)> is_reference SMPPEX.Pdu.ref(pdu)
true
"""
def ref(pdu) do
pdu.ref
end
@spec mandatory_field(t, atom) :: term
@doc """
Get Pdu mandatory field. If Pdu does not have the field, `nil` is returned.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new({1, 4, 123}, %{system_id: "system_id"})
iex(2)> SMPPEX.Pdu.mandatory_field(pdu, :system_id)
"system_id"
iex(3)> SMPPEX.Pdu.mandatory_field(pdu, :short_message)
nil
"""
def mandatory_field(pdu, name) when is_atom(name) do
pdu.mandatory[name]
end
@spec set_mandatory_field(t, atom, any) :: t
@doc """
Sets Pdu mandatory field. New Pdu is returned.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new({1, 4, 123}, %{system_id: "system_id"})
iex(2)> pdu1 = SMPPEX.Pdu.set_mandatory_field(pdu, :password, "pass")
iex(3)> SMPPEX.Pdu.mandatory_field(pdu1, :password)
"pass"
"""
def set_mandatory_field(pdu, name, value) do
%Pdu{pdu | mandatory: Map.put(pdu.mandatory, name, value)}
end
@spec optional_field(t, integer | atom) :: any
@doc """
Get Pdu optional(TLV) field by name or by integer id. If Pdu does not have the
field or field name is unknown, `nil` is returned.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new(4, %{}, %{0x0424 => "hello"})
iex(2)> SMPPEX.Pdu.optional_field(pdu, :message_payload)
"hello"
iex(3)> SMPPEX.Pdu.optional_field(pdu, 0x0424)
"hello"
iex(4)> SMPPEX.Pdu.optional_field(pdu, :receipted_message_id)
nil
iex(5)> SMPPEX.Pdu.optional_field(pdu, :unknown_tlv_name)
nil
"""
def optional_field(pdu, id) when is_integer(id) do
case Map.has_key?(pdu.optional, id) do
true ->
pdu.optional[id]
false ->
case TlvFormat.name_by_id(id) do
{:ok, name} -> pdu.optional[name]
:unknown -> nil
end
end
end
def optional_field(pdu, name) when is_atom(name) do
case Map.has_key?(pdu.optional, name) do
true ->
pdu.optional[name]
false ->
case TlvFormat.id_by_name(name) do
{:ok, id} -> pdu.optional[id]
:unknown -> nil
end
end
end
@spec set_optional_field(t, integer | atom, any) :: t
@doc """
Sets Pdu optional field. New Pdu is returned.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new(4)
iex(2)> pdu1 = SMPPEX.Pdu.set_optional_field(pdu, :message_payload, "hello")
iex(3)> SMPPEX.Pdu.optional_field(pdu1, 0x0424)
"hello"
"""
def set_optional_field(pdu, name, value) when is_atom(name) do
optional = Map.delete(pdu.optional, name)
optional =
case TlvFormat.id_by_name(name) do
{:ok, id} -> Map.delete(optional, id)
:unknown -> optional
end
%Pdu{pdu | optional: Map.put(optional, name, value)}
end
def set_optional_field(pdu, id, value) when is_integer(id) do
optional = Map.delete(pdu.optional, id)
optional =
case TlvFormat.name_by_id(id) do
{:ok, name} -> Map.delete(optional, name)
:unknown -> optional
end
%Pdu{pdu | optional: Map.put(optional, id, value)}
end
@spec field(t, integer | atom) :: any
@doc """
Get Pdu mandatory or optional(TLV) field by name or by integer id. If Pdu does not have the
field or field name is unknown, `nil` is returned.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new(4, %{short_message: "hi"}, %{0x0424 => "hello"})
iex(2)> SMPPEX.Pdu.field(pdu, :message_payload)
"hello"
iex(3)> SMPPEX.Pdu.field(pdu, 0x0424)
"hello"
iex(4)> SMPPEX.Pdu.field(pdu, :short_message)
"hi"
iex(5)> SMPPEX.Pdu.field(pdu, :unknown_name)
nil
"""
def field(pdu, id) when is_integer(id) do
optional_field(pdu, id)
end
def field(pdu, name) do
mandatory_field(pdu, name) || optional_field(pdu, name)
end
@spec optional_fields(t) :: map
@doc """
Get the whole set of optional(TLV) fields as a map.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new(4, %{short_message: "hi"}, %{0x0424 => "hello"})
iex(2)> SMPPEX.Pdu.optional_fields(pdu)
%{0x0424 => "hello"}
"""
def optional_fields(pdu), do: pdu.optional
@spec mandatory_fields(t) :: map
@doc """
Get the whole set of mandatory fields as a map.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new(4, %{short_message: "hi"}, %{0x0424 => "hello"})
iex(2)> SMPPEX.Pdu.mandatory_fields(pdu)
%{short_message: "hi"}
"""
def mandatory_fields(pdu), do: pdu.mandatory
@spec same?(t, t) :: boolean
@doc """
Checks if two Pdus are copies of the same Pdu.
## Examples
iex(1)> pdu1 = SMPPEX.Pdu.new(4)
iex(2)> pdu2 = SMPPEX.Pdu.new(4)
iex(3)> SMPPEX.Pdu.same?(pdu1, pdu2)
false
iex(4)> SMPPEX.Pdu.same?(pdu1, pdu1)
true
"""
def same?(pdu1, pdu2), do: pdu1.ref != nil and pdu2.ref != nil and pdu1.ref == pdu2.ref
@spec resp?(t) :: boolean
@doc """
Checks if Pdu is a response Pdu.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new(4)
iex(2)> SMPPEX.Pdu.resp?(pdu)
false
iex(3)> pdu = SMPPEX.Pdu.new(0x80000004)
iex(4)> SMPPEX.Pdu.resp?(pdu)
true
"""
def resp?(pdu), do: command_id(pdu) > 0x80000000
@spec success_resp?(t) :: boolean
@doc """
Checks if Pdu is a successful response Pdu.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new({0x80000004, 1, 0})
iex(2)> SMPPEX.Pdu.success_resp?(pdu)
false
iex(3)> pdu = SMPPEX.Pdu.new({0x80000004, 0, 0})
iex(4)> SMPPEX.Pdu.success_resp?(pdu)
true
"""
def success_resp?(pdu) do
resp?(pdu) and command_status(pdu) == 0
end
@spec bind?(t) :: boolean
@doc """
Checks if Pdu is a bind request.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new(4)
iex(2)> SMPPEX.Pdu.bind?(pdu)
false
iex(3)> pdu = SMPPEX.Pdu.new(1)
iex(4)> SMPPEX.Pdu.bind?(pdu)
true
"""
def bind?(pdu) do
command_id = Pdu.command_id(pdu)
case CommandNames.name_by_id(command_id) do
{:ok, command_name} ->
command_name == :bind_receiver or command_name == :bind_transmitter or
command_name == :bind_transceiver
:unknown ->
false
end
end
@spec bind_resp?(t) :: boolean
@doc """
Checks if Pdu is a bind response.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new(0x80000004)
iex(2)> SMPPEX.Pdu.bind_resp?(pdu)
false
iex(3)> pdu = SMPPEX.Pdu.new(0x80000001)
iex(4)> SMPPEX.Pdu.bind_resp?(pdu)
true
"""
def bind_resp?(pdu) do
command_id = Pdu.command_id(pdu)
case CommandNames.name_by_id(command_id) do
{:ok, command_name} ->
command_name == :bind_receiver_resp or command_name == :bind_transmitter_resp or
command_name == :bind_transceiver_resp
:unknown ->
false
end
end
@type addr :: {String.t(), byte, byte}
@spec source(t) :: addr
@doc """
Returns Pdu's `:source_addr`, `:source_addr_ton` and `:source_addr_npi` fields
in a tuple.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new(4, %{source_addr: "from", source_addr_ton: 1, source_addr_npi: 2})
iex(2)> SMPPEX.Pdu.source(pdu)
{"from", 1, 2}
"""
def source(pdu) do
fields_as_tuple(pdu, [:source_addr, :source_addr_ton, :source_addr_npi])
end
@spec dest(t) :: addr
@doc """
Returns Pdu's `:destination_addr`, `:dest_addr_ton` and `:dest_addr_npi` fields
in a tuple.
## Examples
iex(1)> pdu = SMPPEX.Pdu.new(4, %{destination_addr: "to", dest_addr_ton: 1, dest_addr_npi: 2})
iex(2)> SMPPEX.Pdu.dest(pdu)
{"to", 1, 2}
"""
def dest(pdu) do
fields_as_tuple(pdu, [:destination_addr, :dest_addr_ton, :dest_addr_npi])
end
@spec as_reply_to(pdu :: Pdu.t(), reply_to_pdu :: Pdu.t()) :: Pdu.t()
@doc """
Makes `pdu` be reply to the `reply_to_pdu`, i.e. assigns `reply_to_pdu`'s
`sequence_number` to `pdu`.
## Examples
iex(1)> pdu1 = SMPPEX.Pdu.new({0x00000004, 0, 123})
iex(2)> pdu2 = SMPPEX.Pdu.new(0x80000004) |> SMPPEX.Pdu.as_reply_to(pdu1)
iex(3)> SMPPEX.Pdu.sequence_number(pdu2)
123
"""
def as_reply_to(pdu, reply_to_pdu) do
%Pdu{pdu | sequence_number: reply_to_pdu.sequence_number}
end
defp fields_as_tuple(pdu, fields) do
fields
|> Enum.map(fn field_name -> Pdu.field(pdu, field_name) end)
|> List.to_tuple()
end
defp to_int(val) when is_integer(val), do: val
defp to_int(_), do: 0
end