<commit_before>defmodule SMPPEX.Pdu.Multipart do

  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 part_info :: :single | {integer, integer, integer}
  @type extract_result :: {:ok, part_info, binary} | {:error, any}
  @type extract_source :: Pdu.t | binary

  @spec extract(extract_source) :: extract_result

  def extract(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

  def extract(pdu) do
    message = Pdu.field(pdu, :message_payload) || Pdu.field(pdu, :short_message)
    if message do
      if UDH.has_udh?(pdu) do
        extract(message)
      else
        {:error, @error_not_a_multipart_message}
      end
    else
      {:error, @error_invalid_pdu}
    end
  end

  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}

  def multipart_ie({ref_num, _count, _seq_num}) when ref_num < 0 or ref_num > 65535, 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

  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
<commit_msg>SMPPEX.Pdu.Multipart typespecs<commit_after>defmodule SMPPEX.Pdu.Multipart do

  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 :: {non_neg_integer, non_neg_integer, non_neg_integer}
  @type part_info :: :single | actual_part_info
  @type extract_result :: {:ok, part_info, binary} | {:error, any}
  @type extract_source :: Pdu.t | binary

  @spec extract(extract_source) :: extract_result

  def extract(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

  def extract(pdu) do
    message = Pdu.field(pdu, :message_payload) || Pdu.field(pdu, :short_message)
    if message do
      if UDH.has_udh?(pdu) do
        extract(message)
      else
        {:error, @error_not_a_multipart_message}
      end
    else
      {:error, @error_invalid_pdu}
    end
  end

  @spec extract_from_ies(list(UDH.ie)) :: {:ok, part_info} | {:error, any}

  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, any} | {:ok, UDH.ie}

  def multipart_ie({ref_num, _count, _seq_num}) when ref_num < 0 or ref_num > 65535, 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, any} | {:ok, binary}

  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

{'fraction_non_alphanumeric': 0.12048861743475847, 'fraction_numerical': 0.018600777345918935, 'mean_word_length': 4.045518207282913, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3683078', 'n_tokens_mistral': 2742, 'n_tokens_neox': 2611, 'n_words': 585}