defmodule SMPPEX.Protocol.MandatoryFieldsParser do @moduledoc false alias SMPPEX.Protocol.Unpack alias SMPPEX.Protocol.MandatoryFieldsSpecs @spec parse(binary, MandatoryFieldsSpecs.fields_spec()) :: {:ok, map, binary} | {:error, any} def parse(bin, spec), do: parse(bin, spec, Map.new()) @spec parse(binary, MandatoryFieldsSpecs.fields_spec(), map) :: {:ok, map, binary} | {:error, any} def parse(bin, [], parsed_fields) do {:ok, parsed_fields, bin} end def parse(bin, [field_spec | rest_field_specs], parsed_fields) do case parse_field(bin, field_spec, parsed_fields) do {:ok, new_parsed_fields, rest} -> parse(rest, rest_field_specs, new_parsed_fields) {:error, error} -> {:error, {"Error parsing field(s) #{inspect(field_spec)}", error}} end end defp parse_field(bin, {field_name, spec}, parsed_fields) when is_tuple(spec) do case read_value(bin, spec, parsed_fields) do {:ok, value, rest} -> {:ok, Map.put(parsed_fields, field_name, value), rest} {:error, _} = err -> err end end defp parse_field(bin, {field_name, {:times, times_field}, specs}, parsed_fields) do case read_value(bin, {:times, parsed_fields[times_field], specs}, parsed_fields) do {:ok, value, rest} -> {:ok, Map.put(parsed_fields, field_name, value), rest} {:error, _} = err -> err end end defp parse_field(bin, {:case, cases}, parsed_fields) when is_list(cases) do read_cases(bin, cases, parsed_fields) end defp read_value(bin, {:c_octet_string, {:max, n}}, parsed_fields) do Unpack.c_octet_string(bin, {:max, expand(n, parsed_fields)}) end defp read_value(bin, {:integer, n}, parsed_fields) do Unpack.integer(bin, expand(n, parsed_fields)) end defp read_value(bin, {:c_octet_string, {:fixed, n}}, parsed_fields) do Unpack.c_octet_string(bin, {:fixed, expand(n, parsed_fields)}) end defp read_value(bin, {:octet_string, n}, parsed_fields) do Unpack.octet_string(bin, expand(n, parsed_fields)) end defp read_value(bin, {:times, n, specs}, parsed_fields) do case read_values(bin, {expand(n, parsed_fields), []}, specs, Map.new()) do {:ok, values, rest} -> {:ok, Enum.reverse(values), rest} {:error, _} = err -> err end end defp read_values(bin, {0, values}, _specs, _parsed_fields), do: {:ok, values, bin} defp read_values(bin, {n, values}, specs, parsed_fields) do case parse(bin, specs, parsed_fields) do {:ok, parsed_fields_inner, rest} -> read_values(rest, {n - 1, [parsed_fields_inner | values]}, specs, parsed_fields) {:error, _} = err -> err end end defp read_cases(_bin, [], _parsed_fields), do: {:error, "No cases left"} defp read_cases(bin, [current_case | rest_cases], parsed_fields) do {field, value, specs} = current_case if expand(field, parsed_fields) == value do parse(bin, specs, parsed_fields) else read_cases(bin, rest_cases, parsed_fields) end end defp expand(n, _parsed_fields) when is_integer(n), do: n defp expand(n, parsed_fields) when is_atom(n), do: parsed_fields[n] end