defmodule SMPPEX.Pdu.Oserl do
@moduledoc """
Module for converting SMPPEX Pdu structs to format used by [Oserl](https://github.com/iamaleksey/oserl) library.
"""
alias SMPPEX.Pdu
alias SMPPEX.Pdu.Oserl, as: OserlPdu
alias SMPPEX.Pdu.NetworkErrorCode
alias SMPPEX.Protocol.TlvFormat
require Record
Record.defrecord(:network_error_code, type: 0, error: 0)
@type t :: {
command_id :: non_neg_integer,
command_status :: non_neg_integer,
sequence_number :: non_neg_integer,
[{field_name :: atom, field_value :: term}]
}
@spec to(pdu :: Pdu.t()) :: OserlPdu.t()
@doc """
Converts SMPPEX Pdu to Oserl format.
Unknown optional values are ignored, since Oserl stores them by symbolic names.
"""
def to(pdu) do
{
Pdu.command_id(pdu),
Pdu.command_status(pdu),
Pdu.sequence_number(pdu),
fields_to_list(pdu)
}
end
@spec from(oserl_pdu :: OserlPdu.t()) :: Pdu.t()
@doc """
Converts PDU from Oserl format to SMPPEX Pdu.
"""
def from({command_id, command_status, sequence_number, field_list} = _oserl_pdu) do
converted_field_list =
field_list
|> Enum.map(&preprocess/1)
|> Enum.reject(&is_nil/1)
|> Enum.map(&list_to_string/1)
{mandatory, optional} = list_to_fields(converted_field_list, %{}, %{})
Pdu.new(
{command_id, command_status, sequence_number},
mandatory,
optional
)
end
defp fields_to_list(pdu) do
(pdu |> Pdu.mandatory_fields() |> Map.to_list() |> Enum.map(&string_to_list(&1))) ++
(pdu |> Pdu.optional_fields() |> Map.to_list() |> ids_to_names)
end
defp list_to_fields([], mandatory, optional), do: {mandatory, optional}
defp list_to_fields([{name, value} | list], mandatory, optional) do
case kind(name) do
{:optional, id} -> list_to_fields(list, mandatory, Map.put(optional, id, value))
:mandatory -> list_to_fields(list, Map.put(mandatory, name, value), optional)
end
end
defp kind(id) when is_integer(id), do: {:optional, id}
defp kind(name) when is_atom(name) do
case TlvFormat.id_by_name(name) do
{:ok, id} -> {:optional, id}
:unknown -> :mandatory
end
end
defp ids_to_names(by_ids, by_names \\ [])
defp ids_to_names([], by_names), do: by_names
defp ids_to_names([{name, value} | by_ids], by_names) when is_atom(name),
do: ids_to_names(by_ids, [{name, value} | by_names])
defp ids_to_names([{id, value} | by_ids], by_names) when is_integer(id) do
case TlvFormat.name_by_id(id) do
{:ok, name} -> ids_to_names(by_ids, [string_to_list({name, value}) | by_names])
:unknown -> ids_to_names(by_ids, [{id, value} | by_names])
end
end
defp list_to_string({key, value}) when is_list(value), do: {key, :erlang.list_to_binary(value)}
defp list_to_string({key, value}), do: {key, value}
defp string_to_list({key, value}) when is_binary(value),
do: {key, :erlang.binary_to_list(value)}
defp string_to_list({key, value}), do: {key, value}
defp preprocess({:network_error_code, network_error_code(type: type_code, error: error_code)}),
do: {:network_error_code, NetworkErrorCode.encode(type_code, error_code)}
defp preprocess({:network_error_code, []}), do: nil
defp preprocess({key, value}), do: {key, value}
end