Current section

Files

Jump to
smppex lib smppex protocol pack.ex
Raw

lib/smppex/protocol/pack.ex

defmodule SMPPEX.Protocol.Pack do
@moduledoc false
import Bitwise
alias SMPPEX.Protocol.Unpack.Helpers
@null 0
@null_str <<@null>>
@invalid_integer "Integer: invalid value"
@invalid_c_octet_string_value "C-Octet String: invalid value"
@invalid_c_octet_string_format "C-Octet String: invalid format"
@invalid_octet_string "Octet String: invalid value"
@invalid_tlv_tag "TLV: invalid tag"
@invalid_tlv_value "TLV: invalid value"
@type pack_ok_result :: {:ok, binary}
@type pack_error_result :: {:error, any}
@type pack_result :: pack_ok_result | pack_error_result
@type integer_size :: 1 | 2 | 4
@spec integer(any, integer_size) :: pack_result
def integer(nil, size), do: integer(0, size)
def integer(int, _size) when not is_integer(int), do: {:error, @invalid_integer}
def integer(int, size) when size == 1 or size == 2 or size == 4 do
integer_bit_size = size * 8
if int >= 0 and int < 1 <<< integer_bit_size do
{:ok, <<int::big-unsigned-integer-size(integer_bit_size)>>}
else
{:error, @invalid_integer}
end
end
@type kind :: :ascii | :hex | :dec
@type length_spec :: {:fixed, pos_integer} | {:max, pos_integer}
@spec c_octet_string(any, length_spec) :: pack_result
def c_octet_string(nil, length_spec), do: c_octet_string("", length_spec, :ascii)
def c_octet_string(str, length_spec), do: c_octet_string(str, length_spec, :ascii)
@spec c_octet_string(any, length_spec, kind) :: pack_result
def c_octet_string(str, _spec, _kind) when not is_binary(str),
do: {:error, @invalid_c_octet_string_value}
def c_octet_string(nil, length_spec, kind), do: c_octet_string("", length_spec, kind)
def c_octet_string(str, {:fixed, len}, kind) when is_integer(len) and len >= 1 do
c_octet_string_with_general_check(str, &(&1 == len), kind)
end
def c_octet_string(str, {:max, len}, kind) when is_integer(len) and len >= 1 do
c_octet_string_with_general_check(str, &(&1 <= len), kind)
end
@spec c_octet_string_with_general_check(binary, (integer -> boolean), kind) :: pack_result
defp c_octet_string_with_general_check(str, check, kind) when is_function(check) do
str_length = byte_size(str)
cond do
str == "" ->
{:ok, @null_str}
check.(str_length + 1) ->
if valid_kind?(str, kind) do
{:ok, str <> @null_str}
else
{:error, @invalid_c_octet_string_format}
end
true ->
{:error, @invalid_c_octet_string_value}
end
end
@spec valid_kind?(binary, kind) :: boolean
defp valid_kind?(_str, :ascii), do: true
defp valid_kind?(str, :dec), do: Helpers.dec?(str)
defp valid_kind?(str, :hex), do: Helpers.hex?(str)
@spec octet_string(binary, non_neg_integer) :: pack_result
def octet_string(str, _len) when not is_binary(str), do: {:error, @invalid_octet_string}
def octet_string(str, len) when is_integer(len) and len >= 0 do
if byte_size(str) == len do
{:ok, str}
else
{:error, @invalid_octet_string}
end
end
@spec tlv(integer, binary) :: pack_result
def tlv(_tag, str) when not is_binary(str), do: {:error, @invalid_tlv_value}
def tlv(tag, _str) when not is_integer(tag), do: {:error, @invalid_tlv_tag}
def tlv(tag, _str) when tag < 0, do: {:error, @invalid_tlv_tag}
def tlv(tag, _str) when tag > 65_535, do: {:error, @invalid_tlv_tag}
def tlv(_tag, str) when byte_size(str) >= 65_536, do: {:error, @invalid_tlv_value}
def tlv(tag, str) do
len = byte_size(str)
{:ok, <<tag::big-unsigned-integer-size(16), len::big-unsigned-integer-size(16), str::binary>>}
end
end