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