1defmodule SMPPEX.Pdu.Oserl do 2 @moduledoc """ 3 Module for converting SMPPEX Pdu structs to format used by [Oserl](https://github.com/iamaleksey/oserl) library. 4 5 """ 6 7 alias SMPPEX.Pdu 8 alias SMPPEX.Pdu.Oserl, as: OserlPdu 9 alias SMPPEX.Pdu.NetworkErrorCode 10 alias SMPPEX.Protocol.TlvFormat 11 12 require Record 13 Record.defrecord(:network_error_code, type: 0, error: 0) 14 15 @type t :: { 16 command_id :: non_neg_integer, 17 command_status :: non_neg_integer, 18 sequence_number :: non_neg_integer, 19 [{field_name :: atom, field_value :: term}] 20 } 21 22 @spec to(pdu :: Pdu.t()) :: OserlPdu.t() 23 24 @doc """ 25 Converts SMPPEX Pdu to Oserl format. 26 27 Unknown optional values are ignored, since Oserl stores them by symbolic names. 28 """ 29 def to(pdu) do 30 { 31 Pdu.command_id(pdu), 32 Pdu.command_status(pdu), 33 Pdu.sequence_number(pdu), 34 fields_to_list(pdu) 35 } 36 end 37 38 @spec from(oserl_pdu :: OserlPdu.t()) :: Pdu.t() 39 40 @doc """ 41 Converts PDU from Oserl format to SMPPEX Pdu. 42 """ 43 def from({command_id, command_status, sequence_number, field_list} = _oserl_pdu) do 44 converted_field_list = 45 field_list 46 |> Enum.map(&preprocess/1) 47 |> Enum.reject(&is_nil/1) 48 |> Enum.map(&list_to_string/1) 49 50 {mandatory, optional} = list_to_fields(converted_field_list, %{}, %{}) 51 52 Pdu.new( 53 {command_id, command_status, sequence_number}, 54 mandatory, 55 optional 56 ) 57 end 58 59 defp fields_to_list(pdu) do 60 (pdu |> Pdu.mandatory_fields() |> Map.to_list() |> Enum.map(&string_to_list(&1))) ++ 61 (pdu |> Pdu.optional_fields() |> Map.to_list() |> ids_to_names) 62 end 63 64 defp list_to_fields([], mandatory, optional), do: {mandatory, optional} 65 66 defp list_to_fields([{name, value} | list], mandatory, optional) do 67 case kind(name) do 68 {:optional, id} -> list_to_fields(list, mandatory, Map.put(optional, id, value)) 69 :mandatory -> list_to_fields(list, Map.put(mandatory, name, value), optional) 70 end 71 end 72 73 defp kind(id) when is_integer(id), do: {:optional, id} 74 75 defp kind(name) when is_atom(name) do 76 case TlvFormat.id_by_name(name) do 77 {:ok, id} -> {:optional, id} 78 :unknown -> :mandatory 79 end 80 end 81 82 defp ids_to_names(by_ids, by_names \\ []) 83 84 defp ids_to_names([], by_names), do: by_names 85 86 defp ids_to_names([{name, value} | by_ids], by_names) when is_atom(name), 87 do: ids_to_names(by_ids, [{name, value} | by_names]) 88 89 defp ids_to_names([{id, value} | by_ids], by_names) when is_integer(id) do 90 case TlvFormat.name_by_id(id) do 91 {:ok, name} -> ids_to_names(by_ids, [string_to_list({name, value}) | by_names]) 92 :unknown -> ids_to_names(by_ids, [{id, value} | by_names]) 93 end 94 end 95 96 defp list_to_string({key, value}) when is_list(value), do: {key, :erlang.list_to_binary(value)} 97 defp list_to_string({key, value}), do: {key, value} 98 99 defp string_to_list({key, value}) when is_binary(value), 100 do: {key, :erlang.binary_to_list(value)} 101 102 defp string_to_list({key, value}), do: {key, value} 103 104 defp preprocess({:network_error_code, network_error_code(type: type_code, error: error_code)}), 105 do: {:network_error_code, NetworkErrorCode.encode(type_code, error_code)} 106 107 defp preprocess({:network_error_code, []}), do: nil 108 defp preprocess({key, value}), do: {key, value} 109end 110