1defmodule SMPPEX.Protocol.MandatoryFieldsParser do 2 @moduledoc false 3 4 alias SMPPEX.Protocol.Unpack 5 alias SMPPEX.Protocol.MandatoryFieldsSpecs 6 7 @spec parse(binary, MandatoryFieldsSpecs.fields_spec()) :: {:ok, map, binary} | {:error, any} 8 9 def parse(bin, spec), do: parse(bin, spec, Map.new()) 10 11 @spec parse(binary, MandatoryFieldsSpecs.fields_spec(), map) :: 12 {:ok, map, binary} | {:error, any} 13 14 def parse(bin, [], parsed_fields) do 15 {:ok, parsed_fields, bin} 16 end 17 18 def parse(bin, [field_spec | rest_field_specs], parsed_fields) do 19 case parse_field(bin, field_spec, parsed_fields) do 20 {:ok, new_parsed_fields, rest} -> 21 parse(rest, rest_field_specs, new_parsed_fields) 22 23 {:error, error} -> 24 {:error, {"Error parsing field(s) #{inspect(field_spec)}", error}} 25 end 26 end 27 28 defp parse_field(bin, {field_name, spec}, parsed_fields) when is_tuple(spec) do 29 case read_value(bin, spec, parsed_fields) do 30 {:ok, value, rest} -> 31 {:ok, Map.put(parsed_fields, field_name, value), rest} 32 33 {:error, _} = err -> 34 err 35 end 36 end 37 38 defp parse_field(bin, {:case, cases}, parsed_fields) when is_list(cases) do 39 read_cases(bin, cases, parsed_fields) 40 end 41 42 defp read_value(bin, {:c_octet_string, {:max, n}}, parsed_fields) do 43 Unpack.c_octet_string(bin, {:max, expand(n, parsed_fields)}) 44 end 45 46 defp read_value(bin, {:integer, n}, parsed_fields) do 47 Unpack.integer(bin, expand(n, parsed_fields)) 48 end 49 50 defp read_value(bin, {:c_octet_string, {:fixed, n}}, parsed_fields) do 51 Unpack.c_octet_string(bin, {:fixed, expand(n, parsed_fields)}) 52 end 53 54 defp read_value(bin, {:octet_string, n}, parsed_fields) do 55 Unpack.octet_string(bin, expand(n, parsed_fields)) 56 end 57 58 defp read_value(bin, {:times, n, specs}, parsed_fields) do 59 case read_values(bin, {expand(n, parsed_fields), []}, specs, Map.new()) do 60 {:ok, values, rest} -> {:ok, Enum.reverse(values), rest} 61 {:error, _} = err -> err 62 end 63 end 64 65 defp read_values(bin, {0, values}, _specs, _parsed_fields), do: {:ok, values, bin} 66 67 defp read_values(bin, {n, values}, specs, parsed_fields) do 68 case parse(bin, specs, parsed_fields) do 69 {:ok, parsed_fields_inner, rest} -> 70 read_values(rest, {n - 1, [parsed_fields_inner | values]}, specs, parsed_fields) 71 72 {:error, _} = err -> 73 err 74 end 75 end 76 77 defp read_cases(_bin, [], _parsed_fields), do: {:error, "No cases left"} 78 79 defp read_cases(bin, [current_case | rest_cases], parsed_fields) do 80 {field, value, specs} = current_case 81 82 if expand(field, parsed_fields) == value do 83 parse(bin, specs, parsed_fields) 84 else 85 read_cases(bin, rest_cases, parsed_fields) 86 end 87 end 88 89 defp expand(n, _parsed_fields) when is_integer(n), do: n 90 defp expand(n, parsed_fields) when is_atom(n), do: parsed_fields[n] 91end 92