lib/grizzly/zwave/commands/learn_mode_set_status.ex

defmodule Grizzly.ZWave.Commands.LearnModeSetStatus do
  @moduledoc """
  This command is used to indicate the progress of the Learn Mode Set command.

  Params:

    * `:seq_number` - the command sequence number

    * `:status` - the outcome of the learn mode, one of :done, :failed or :security_failed

    * `:new_node_id` - the new node id assigned to the device

    * `:granted_keys` - indicates which network keys were granted during bootstrapping; a list with :s2_unauthenticated, :s2_authenticated,
                        :s2_access_control and/or :s0 (optional - v2)

    * `:kex_fail_type` - indicates which error occurred in case S2 bootstrapping was not successful; one of :none, :key, :scheme, :curves,
                        :decrypt, :cancel, :auth, :get, :verify or :report -- see Grizzly.ZWave.Security (optional - v2)

    * `:dsk` - the DSK of the including controller that performed S2 bootstrapping to the node (optional - v2)

  """

  @behaviour Grizzly.ZWave.Command

  alias Grizzly.ZWave
  alias Grizzly.ZWave.{Command, DecodeError, DSK, Security}
  alias Grizzly.ZWave.CommandClasses.NetworkManagementBasicNode

  @type status :: :done | :failed | :security_failed
  @type param ::
          {:seq_number, ZWave.seq_number()}
          | {:status, status}
          | {:new_node_id, Grizzly.Node.id()}
          | {:granted_keys, [Security.key()]}
          | {:kex_fail_type, Security.key_exchange_fail_type()}
          | {:dsk, DSK.t()}

  @impl true
  def new(params) do
    command = %Command{
      name: :learn_mode_set_status,
      command_byte: 0x02,
      command_class: NetworkManagementBasicNode,
      params: params,
      impl: __MODULE__
    }

    {:ok, command}
  end

  @impl true
  def encode_params(command) do
    seq_number = Command.param!(command, :seq_number)
    status_byte = Command.param!(command, :status) |> encode_status()
    new_node_id = Command.param!(command, :new_node_id)
    granted_keys = Command.param(command, :granted_keys)

    if granted_keys == nil do
      <<seq_number, status_byte, 0x00, new_node_id>>
    else
      granted_keys_byte = Security.keys_to_byte(granted_keys)

      kex_fail_type_byte =
        Command.param!(command, :kex_fail_type) |> Security.failed_type_to_byte()

      dsk = Command.param!(command, :dsk)

      <<seq_number, status_byte, 0x00, new_node_id, granted_keys_byte, kex_fail_type_byte>> <>
        dsk.raw
    end
  end

  @impl true
  def decode_params(<<seq_number, status_byte, 0x00, new_node_id>>) do
    with {:ok, status} <- decode_status(status_byte) do
      {:ok, [seq_number: seq_number, status: status, new_node_id: new_node_id]}
    else
      {:error, %DecodeError{}} = error ->
        error
    end
  end

  def decode_params(
        <<seq_number, status_byte, 0x00, new_node_id, granted_keys_byte, kex_fail_type_byte,
          dsk_binary::binary>>
      ) do
    granted_keys = Security.byte_to_keys(granted_keys_byte)
    kex_fail_type = Security.failed_type_from_byte(kex_fail_type_byte)

    with {:ok, status} <- decode_status(status_byte),
         dsk <- DSK.new(dsk_binary) do
      {:ok,
       [
         seq_number: seq_number,
         status: status,
         new_node_id: new_node_id,
         granted_keys: granted_keys,
         kex_fail_type: kex_fail_type,
         dsk: dsk
       ]}
    else
      {:error, %DecodeError{}} = error ->
        error
    end
  end

  defp encode_status(:done), do: 0x06
  defp encode_status(:failed), do: 0x07
  defp encode_status(:security_failed), do: 0x09

  defp decode_status(0x06), do: {:ok, :done}
  defp decode_status(0x07), do: {:ok, :failed}
  defp decode_status(0x09), do: {:ok, :security_failed}

  defp decode_status(byte),
    do: {:error, %DecodeError{value: byte, param: :status, command: :learn_mode_set_status}}
end