lib/grizzly/zwave/commands/zip_packet/header_extensions.ex

defmodule Grizzly.ZWave.Commands.ZIPPacket.HeaderExtensions do
  @moduledoc """
  Functions for working with the header extension in a Z/IP Packet.
  """

  alias Grizzly.ZWave.Command

  alias Grizzly.ZWave.Commands.ZIPPacket.HeaderExtensions.{
    BinaryParser,
    EncapsulationFormatInfo,
    ExpectedDelay,
    InstallationAndMaintenanceReport
  }

  require Logger

  @type encapsulation_format_info :: :crc16 | EncapsulationFormatInfo.security()

  @type extension ::
          {:expected_delay, Command.delay_seconds()}
          | {:installation_and_maintenance_report, list()}
          | :installation_and_maintenance_get
          | {:encapsulation_format_info, [encapsulation_format_info()]}
          | :multicast_addressing

  @doc """
  Try to parse a binary string into `HeaderExtensions.t()`
  """
  @spec from_binary(binary()) :: [extension()]
  def from_binary(extensions) do
    extensions
    |> BinaryParser.from_binary()
    |> BinaryParser.parse(&parse_extension/1)
  end

  @spec to_binary([extension()]) :: binary()
  def to_binary(extensions) do
    Enum.reduce(extensions, <<>>, fn
      {:expected_delay, seconds}, bin ->
        bin <> ExpectedDelay.to_binary(seconds)

      {:encapsulation_format_info, security_classes}, bin ->
        bin <> EncapsulationFormatInfo.to_binary(security_classes)

      :multicast_addressing, bin ->
        bin <> <<0x05, 0x00>>

      :installation_and_maintenance_get, bin ->
        bin <> <<0x02, 0x00>>

      # We don't ever need to send this to Z/IP Gateway even if it's specified
      {:installation_and_maintenance_report, _}, bin ->
        bin

      extension, bin ->
        Logger.warning(
          "[Grizzly] Encoding not supported for Z/IP Packet header extension: #{inspect(extension)}"
        )

        bin
    end)
  end

  defp parse_extension(<<0x01, 0x03, seconds::24, rest::binary>>) do
    {{:expected_delay, seconds}, rest}
  end

  defp parse_extension(<<0x02, 0x00, rest::binary>>),
    do: {:installation_and_maintenance_get, rest}

  defp parse_extension(<<0x03, length, rest::binary>> = report) do
    <<_::binary-size(length), rest::binary>> = rest

    {{:installation_and_maintenance_report, InstallationAndMaintenanceReport.from_binary(report)},
     rest}
  end

  defp parse_extension(
         <<0x84, 0x02, security_to_security, _::size(7), crc16::size(1), rest::binary>>
       ) do
    security_to_security =
      EncapsulationFormatInfo.security_to_security_from_byte(security_to_security)

    crc16_bool = if crc16 == 1, do: true, else: false

    {{:encapsulation_format_info, EncapsulationFormatInfo.new(security_to_security, crc16_bool)},
     rest}
  end

  defp parse_extension(<<0x05, 0x00, rest::binary>>) do
    {:multicast_addressing, rest}
  end
end