Packages
smppex
2.2.5
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/multipart.ex
defmodule SMPPEX.Pdu.Multipart do
@moduledoc """
Module for operating with multipart information packed as UDH in message body.
"""
alias :proplists, as: Proplists
alias SMPPEX.Pdu
alias SMPPEX.Pdu.UDH
@concateneated_8bit_ref_ie_id 0x00
@concateneated_16bit_ref_ie_id 0x08
@error_invalid_8bit_ie "Invalid 8bit refrence number concatenated short messages info"
@error_invalid_16bit_ie "Invalid 16bit refrence number concatenated short messages info"
@error_invalid_pdu "Pdu has no message field"
@error_not_a_multipart_message "Message is not multipart"
@error_invalid_ref_num "Invalid refrence number in multipart info"
@error_invalid_count "Invalid count in multipart info"
@error_invalid_seq_num "Invalid sequence number in multipart info"
@error_invalid_max "Invalid limits for splitting message"
@error_invalid_message "Invalid message"
@type actual_part_info ::
{ref_num :: non_neg_integer, count :: non_neg_integer, seq_num :: non_neg_integer}
@type part_info :: :single | actual_part_info
@type extract_result :: {:ok, part_info, binary} | {:error, term}
@spec extract_from_message(binary) :: extract_result
@doc """
Extracts multipart information from binary message.
Returns one of the following:
* `{:ok, :single, message}` if the `message` does not contain any multipart information and represents a
single message. The outcoming `message` is cleared from UDH bytes.
* `{:ok, {ref_num, count, seq_num}, message}` if the original message contains multipart information in
UDH fields. The outcoming `message` is cleared from UDH bytes.
* `{:error, reason}`
## Example
iex> data = <<0x05, 0x00, 0x03, 0x03, 0x02, 0x01, "message">>
iex> SMPPEX.Pdu.Multipart.extract_from_message(data)
{:ok, {3,2,1}, "message"}
iex> data = <<0x06, 0x08, 0x04, 0x00, 0x03, 0x02, 0x01, "message">>
iex> SMPPEX.Pdu.Multipart.extract_from_message(data)
{:ok, {3,2,1}, "message"}
"""
def extract_from_message(message) when is_binary(message) do
case UDH.extract(message) do
{:ok, ies, message} ->
case extract_from_ies(ies) do
{:ok, part_info} -> {:ok, part_info, message}
{:error, _} = err -> err
end
{:error, _} = err ->
err
end
end
@spec extract_from_pdu(Pdu.t()) :: extract_result
@doc """
Extracts multipart information from PDU.
Returns one of the following:
* `{:ok, :single, message}` if the `message` does not contain any multipart information and represents a
single message. The outcoming `message` is cleared from UDH bytes.
* `{:ok, {ref_num, count, seq_num}, message}` if the original message contains multipart information in
UDH fields. The outcoming `message` is cleared from UDH bytes.
* `{:error, reason}`
## Example
iex> pdu = Pdu.new({1,0,1}, %{esm_class: 0b01000000, short_message: <<0x05, 0x00, 0x03, 0x03, 0x02, 0x01, "message">>})
iex> SMPPEX.Pdu.Multipart.extract_from_pdu(pdu)
{:ok, {3,2,1}, "message"}
iex> pdu = Pdu.new({1,0,1}, %{short_message: <<0x05, 0x00, 0x03, 0x03, 0x02, 0x01, "message">>})
iex> SMPPEX.Pdu.Multipart.extract_from_pdu(pdu)
{:error, "#{@error_not_a_multipart_message}"}
"""
def extract_from_pdu(%Pdu{} = pdu) do
message = Pdu.field(pdu, :message_payload) || Pdu.field(pdu, :short_message)
if message do
if UDH.has_udh?(pdu) do
extract_from_message(message)
else
{:error, @error_not_a_multipart_message}
end
else
{:error, @error_invalid_pdu}
end
end
@spec extract(Pdu.t() | binary) :: extract_result
@doc """
Extracts multipart information from PDU or directly from binary message. This function is deprecated. Use `extract_from_pdu/1` or `extract_from_message/1` instead.
Returns one of the following:
* `{:ok, :single, message}` if the `message` does not contain any multipart information and represents a
single message;
* `{:ok, {ref_num, count, seq_num}, message}` if the original message contains multipart information in
UDH fields. The outcoming `message` is cleared from UDH bytes.
* `{:error, reason}`
## Example
iex> data = <<0x05, 0x00, 0x03, 0x03, 0x02, 0x01, "message">>
iex> SMPPEX.Pdu.Multipart.extract(data)
{:ok, {3,2,1}, "message"}
iex> data = <<0x06, 0x08, 0x04, 0x00, 0x03, 0x02, 0x01, "message">>
iex> SMPPEX.Pdu.Multipart.extract(data)
{:ok, {3,2,1}, "message"}
iex> pdu = Pdu.new({1,0,1}, %{esm_class: 0b01000000, short_message: <<0x05, 0x00, 0x03, 0x03, 0x02, 0x01, "message">>})
iex> SMPPEX.Pdu.Multipart.extract(pdu)
{:ok, {3,2,1}, "message"}
iex> pdu = Pdu.new({1,0,1}, %{short_message: <<0x05, 0x00, 0x03, 0x03, 0x02, 0x01, "message">>})
iex> SMPPEX.Pdu.Multipart.extract(pdu)
{:error, "#{@error_not_a_multipart_message}"}
"""
def extract(message) when is_binary(message) do
extract_from_message(message)
end
def extract(pdu) do
extract_from_pdu(pdu)
end
@spec extract_from_ies(list(UDH.ie())) :: {:ok, part_info} | {:error, any}
@doc """
Extracts multipart information from already parsed list of UDH IEs.
Return one of the following:
* `{:ok, :single}` if IEs do not contain any multipart message related ones;
* `{:ok, {ref_num, count, seq_num}}` if there are multipart message related IEs (the first is taken);
* `{:error, reason}` in case of errors.
## Example
iex> ies = [{0, <<0x03, 0x02, 0x01>>}]
iex> SMPPEX.Pdu.Multipart.extract_from_ies(ies)
{:ok, {3, 2, 1}}
iex> ies = [{0, <<0x03, 0x02, 0x01>>}, {8, <<0x00, 0x04, 0x02, 0x01>>}]
iex> SMPPEX.Pdu.Multipart.extract_from_ies(ies)
{:ok, {3, 2, 1}}
iex> ies = [{8, <<0x00, 0x03, 0x02, 0x01>>}]
iex> SMPPEX.Pdu.Multipart.extract_from_ies(ies)
{:ok, {3, 2, 1}}
iex> ies = [{8, <<0x00, 0x03, 0x02>>}]
iex> SMPPEX.Pdu.Multipart.extract_from_ies(ies)
{:error, "#{@error_invalid_16bit_ie}"}
iex> ies = []
iex> SMPPEX.Pdu.Multipart.extract_from_ies(ies)
{:ok, :single}
"""
def extract_from_ies(ies) do
cond do
Proplists.is_defined(@concateneated_8bit_ref_ie_id, ies) ->
@concateneated_8bit_ref_ie_id |> Proplists.get_value(ies) |> parse_8bit
Proplists.is_defined(@concateneated_16bit_ref_ie_id, ies) ->
@concateneated_16bit_ref_ie_id |> Proplists.get_value(ies) |> parse_16bit
true ->
{:ok, :single}
end
end
defp parse_8bit(<<
ref_num::integer-unsigned-size(8),
count::integer-unsigned-size(8),
seq_num::integer-unsigned-size(8)
>>) do
{:ok, {ref_num, count, seq_num}}
end
defp parse_8bit(_), do: {:error, @error_invalid_8bit_ie}
defp parse_16bit(<<
ref_num::integer-big-unsigned-size(16),
count::integer-unsigned-size(8),
seq_num::integer-unsigned-size(8)
>>) do
{:ok, {ref_num, count, seq_num}}
end
defp parse_16bit(_), do: {:error, @error_invalid_16bit_ie}
@spec multipart_ie(actual_part_info) :: {:error, term} | {:ok, UDH.ie()}
@doc """
Generates IE encoding multipart information.
## Example
iex> SMPPEX.Pdu.Multipart.multipart_ie({3,2,1})
{:ok, {0, <<0x03, 0x02, 0x01>>}}
iex> SMPPEX.Pdu.Multipart.multipart_ie({256,2,1})
{:ok, {8, <<0x01, 0x00, 0x02, 0x01>>}}
iex> SMPPEX.Pdu.Multipart.multipart_ie({1, 1, 256})
{:error, "#{@error_invalid_seq_num}"}
"""
def multipart_ie({ref_num, _count, _seq_num}) when ref_num < 0 or ref_num > 65_535,
do: {:error, @error_invalid_ref_num}
def multipart_ie({_ref_num, count, _seq_num}) when count < 1 or count > 255,
do: {:error, @error_invalid_count}
def multipart_ie({_ref_num, _count, seq_num}) when seq_num < 1 or seq_num > 255,
do: {:error, @error_invalid_seq_num}
def multipart_ie({ref_num, count, seq_num}) do
{
:ok,
if ref_num > 255 do
{@concateneated_16bit_ref_ie_id,
<<
ref_num::integer-big-unsigned-size(16),
count::integer-unsigned-size(8),
seq_num::integer-unsigned-size(8)
>>}
else
{@concateneated_8bit_ref_ie_id,
<<
ref_num::integer-unsigned-size(8),
count::integer-unsigned-size(8),
seq_num::integer-unsigned-size(8)
>>}
end
}
end
@spec prepend_message_with_part_info(actual_part_info, binary) :: {:error, term} | {:ok, binary}
@doc """
Prepends message with multipart info encoded as UDH.
## Example
iex> SMPPEX.Pdu.Multipart.prepend_message_with_part_info({3,2,1}, "message")
{:ok, <<0x05, 0x00, 0x03, 0x03, 0x02, 0x01, "message">>}
iex> SMPPEX.Pdu.Multipart.prepend_message_with_part_info({256,2,1}, "message")
{:ok, <<0x06, 0x08, 0x04, 0x01, 0x00, 0x02, 0x01, "message">>}
"""
def prepend_message_with_part_info(part_info, message) do
case multipart_ie(part_info) do
{:ok, ie} -> UDH.add([ie], message)
{:error, _} = err -> err
end
end
@type split_result :: {:ok, :unsplit} | {:ok, :split, [binary]} | {:error, term}
@spec split_message(ref_num :: integer, message :: binary, max_len :: integer) :: split_result
@doc """
Splits message into parts prepending each part with multipart information UDH
so that the resulting size of each part does not exceed `max_len` bytes.
The result is one of the following:
* `{:ok, :unsplit}` if the message already fits into `max_len` bytes;
* `{:ok, :split, parts}` if the message was succesfully split into `parts`;
* `{:error, reason}` in case of errors.
## Example
iex> SMPPEX.Pdu.Multipart.split_message(123, "abc", 3)
{:ok, :unsplit}
iex> SMPPEX.Pdu.Multipart.split_message(123, "abcdefg", 6)
{:error, "#{@error_invalid_max}"}
iex> SMPPEX.Pdu.Multipart.split_message(123, "abcdefghi", 8)
{:ok, :split, [
<<0x05, 0x00, 0x03, 0x7b, 0x05, 0x01, "ab">>,
<<0x05, 0x00, 0x03, 0x7b, 0x05, 0x02, "cd">>,
<<0x05, 0x00, 0x03, 0x7b, 0x05, 0x03, "ef">>,
<<0x05, 0x00, 0x03, 0x7b, 0x05, 0x04, "gh">>,
<<0x05, 0x00, 0x03, 0x7b, 0x05, 0x05, "i">>
]}
"""
def split_message(_ref_num, message, _max_len) when not is_binary(message),
do: {:error, @error_invalid_message}
def split_message(ref_num, _message, _max_len) when ref_num < 0,
do: {:error, @error_invalid_ref_num}
def split_message(ref_num, _message, _max_len) when ref_num > 65_535,
do: {:error, @error_invalid_ref_num}
def split_message(_ref_num, _message, max_len) when max_len < 0,
do: {:error, @error_invalid_max}
def split_message(ref_num, message, max_len) do
case prepend_message_with_part_info({ref_num, 1, 1}, <<>>) do
{:ok, bin} ->
max_split = if max_len >= byte_size(bin), do: max_len - byte_size(bin), else: 0
split_message(ref_num, message, max_len, max_split)
{:error, _} = err ->
err
end
end
@spec split_message(
ref_num :: integer,
message :: binary,
max_len :: integer,
max_split :: integer
) :: split_result
@doc """
Splits message into parts not exceeding `max_split` bytes and prepending each part with multipart information UDH.
The message is not split if its size does not exceed `max_len` bytes.
The results format is the same as in `split_message/3`.
## Example
iex> SMPPEX.Pdu.Multipart.split_message(123, "abcdefghi", 0, 2)
{:ok, :split, [
<<0x05, 0x00, 0x03, 0x7b, 0x05, 0x01, "ab">>,
<<0x05, 0x00, 0x03, 0x7b, 0x05, 0x02, "cd">>,
<<0x05, 0x00, 0x03, 0x7b, 0x05, 0x03, "ef">>,
<<0x05, 0x00, 0x03, 0x7b, 0x05, 0x04, "gh">>,
<<0x05, 0x00, 0x03, 0x7b, 0x05, 0x05, "i">>
]}
"""
def split_message(_ref_num, message, _max_len, _max_split) when not is_binary(message),
do: {:error, @error_invalid_message}
def split_message(ref_num, _message, _max_len, _max_split) when ref_num < 0,
do: {:error, @error_invalid_ref_num}
def split_message(ref_num, _message, _max_len, _max_split) when ref_num > 65_535,
do: {:error, @error_invalid_ref_num}
def split_message(_ref_num, _message, max_len, _max_split) when max_len < 0,
do: {:error, @error_invalid_max}
def split_message(_ref_num, _message, _max_len, max_split) when max_split < 0,
do: {:error, @error_invalid_max}
def split_message(ref_num, message, max_len, max_split) do
message_size = byte_size(message)
if message_size <= max_len do
{:ok, :unsplit}
else
if max_split > 0 do
part_count = message_part_count(message_size, max_split)
split_message_into_parts({ref_num, part_count, 1}, message, max_split, [])
else
{:error, @error_invalid_max}
end
end
end
defp message_part_count(message_size, max_size) do
if rem(message_size, max_size) == 0 do
div(message_size, max_size)
else
1 + div(message_size, max_size)
end
end
defp split_message_into_parts({_ref_num, count, n}, <<>>, _max_len, parts) when n > count,
do: {:ok, :split, Enum.reverse(parts)}
defp split_message_into_parts({ref_num, count, n} = part_info, message, max_len, parts) do
{part, rest} =
case message do
<<part::binary-size(max_len), rest::binary>> -> {part, rest}
last_part -> {last_part, <<>>}
end
case prepend_message_with_part_info(part_info, part) do
{:ok, part_with_info} ->
split_message_into_parts({ref_num, count, n + 1}, rest, max_len, [part_with_info | parts])
{:error, _} = err ->
err
end
end
end