defmodule SMPPEX.Protocol.Unpack do alias SMPPEX.Protocol.Unpack.Helpers @null 0 @unexpected_data_end "Unexpected end of data" @invalid_c_octet_string_format "C-Octet String: wrong format" @invalid_fixed_c_octet_string "C-Octet String(fixed): invalid" @invalid_c_octet_string_no_terminator "C-Octet String(var): null terminator not found" @type unpack_error_result :: {:error, any} @type integer_size :: 1 | 2 | 4 @spec integer(binary, integer_size) :: unpack_error_result | {:ok, non_neg_integer, binary} def integer(bin, size) when (size == 1 or size == 2 or size == 4) and is_binary(bin) do integer_bit_size = size * 8 case bin do <> -> {:ok, int, rest} _ -> {:error, @unexpected_data_end} end end @type length_spec :: {:fixed, pos_integer} | {:max, pos_integer} @type kind :: :ascii | :hex | :dec @type c_octet_string_result :: unpack_error_result | {:ok, binary, binary} @spec c_octet_string(binary, length_spec) :: c_octet_string_result def c_octet_string(bin, length_spec) when is_binary(bin) do c_octet_string(bin, length_spec, :ascii) end @spec c_octet_string(binary, length_spec, kind) :: c_octet_string_result def c_octet_string(bin, {:fixed, len}, kind) when len >= 1 and is_binary(bin) do str_length = len - 1 case bin do << @null :: size(8), rest :: binary >> -> {:ok, "", rest} << str :: binary-size(str_length), @null :: size(8), rest :: binary >> -> case valid_kind?(str, kind) do true -> {:ok, str, rest} false -> {:error, @invalid_c_octet_string_format} end << _ :: binary-size(len), _ :: binary >> -> {:error, @invalid_fixed_c_octet_string} _ -> {:error, @unexpected_data_end} end end def c_octet_string(bin, {:max, len}, kind) when len >= 1 and is_binary(bin) do case Helpers.take_until(bin, @null, len) do {str, rest} -> case valid_kind?(str, kind) do true -> {:ok, str, rest} false -> {:error, @invalid_c_octet_string_format} end :not_found -> {:error, @invalid_c_octet_string_no_terminator} 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 c_octet_string(binary, non_neg_integer) :: unpack_error_result | {:ok, binary, binary} def octet_string(bin, len) when len >= 0 and is_binary(bin) do case bin do << str :: binary-size(len), rest :: binary >> -> {:ok, str, rest} _ -> {:error, @unexpected_data_end} end end @type parsed_tlv :: {non_neg_integer, binary} @spec tlv(binary) :: unpack_error_result | {:ok, parsed_tlv, binary} def tlv(bin) when byte_size(bin) < 4 do {:error, @unexpected_data_end} end def tlv(<>) do case value_and_rest do << value :: binary-size(len), rest :: binary >> -> {:ok, {tag, value}, rest} _ -> {:error, @unexpected_data_end} end end end