lib/jeff/command.ex

defmodule Jeff.Command do
  @moduledoc """
  Commands are sent from an ACU to a PD

  | Code | Name         | Description                           | Data Type               |
  |------|--------------|---------------------------------------|-------------------------|
  | 0x60 | POLL         | Poll                                  | -                       |
  | 0x61 | ID           | ID Report Request                     | -                       |
  | 0x62 | CAP          | PD Capabilities Request               | [Capability]            |
  | 0x64 | LSTAT        | Local Status Report Request           | -                       |
  | 0x65 | ISTAT        | Input Status Report Request           | -                       |
  | 0x66 | OSTAT        | Output Status Report Request          | -                       |
  | 0x67 | RSTAT        | Reader Status Report Request          | -                       |
  | 0x68 | OUT          | Output Control Command                | OutputSettings          |
  | 0x69 | LED          | Reader Led Control Command            | LedSettings             |
  | 0x6A | BUZ          | Reader Buzzer Control Command         | BuzzerSettings          |
  | 0x6B | TEXT         | Text Output Command                   | TextSettings            |
  | 0x6E | COMSET       | PD Communication Config Command       | ComSettings             |
  | 0x73 | BIOREAD      | Scan and Send Biometric Data          | Requested Return Format |
  | 0x74 | BIOMATCH     | Scan and Match Biometric Template     | Biometric Template      |
  | 0x75 | KEYSET       | Encryption Key Set Command            | EncryptionKey           |
  | 0x76 | CHLNG        | Challenge/Secure Session Init Request | ChallengeData           |
  | 0x77 | SCRYPT       | Server Cryptogram                     | EncryptionData          |
  | 0x7B | ACURXSIZE    | Max ACU receive size                  | Buffer size             |
  | 0x7C | FILETRANSFER | Send data file to PD                  | File contents           |
  | 0x80 | MFG          | Manufacturer Specific Command         | Any                     |
  | 0xA1 | XWR          | Extended write data                   | APDU and details        |
  | 0xA2 | ABORT        | Abort PD operation                    | -                       |
  | 0xA3 | PIVDATA      | Get PIV Data                          | Object details          |
  | 0xA4 | GENAUTH      | Request Authenticate                  | Request details         |
  | 0xA5 | CRAUTH       | Request Crypto Response               | Challenge details       |
  | 0xA7 | KEEPACTIVE   | PD read activation                    | Time duration           |
  """

  @type t() :: %__MODULE__{
          address: byte(),
          code: byte(),
          data: binary(),
          name: name(),
          caller: reference()
        }

  defstruct [:address, :code, :data, :name, :caller]

  alias Jeff.Command.{
    BuzzerSettings,
    ChallengeData,
    ComSettings,
    EncryptionKey,
    EncryptionServer,
    LedSettings,
    OutputSettings,
    TextSettings
  }

  @names %{
    0x60 => POLL,
    0x61 => ID,
    0x62 => CAP,
    0x64 => LSTAT,
    0x65 => ISTAT,
    0x66 => OSTAT,
    0x67 => RSTAT,
    0x68 => OUT,
    0x69 => LED,
    0x6A => BUZ,
    0x6B => TEXT,
    0x6E => COMSET,
    0x73 => BIOREAD,
    0x74 => BIOMATCH,
    0x75 => KEYSET,
    0x76 => CHLNG,
    0x77 => SCRYPT,
    0x7B => ACURXSIZE,
    0x7C => FILETRANSFER,
    0x80 => MFG,
    0xA1 => XWR,
    0xA2 => ABORT,
    0xA3 => PIVDATA,
    0xA4 => GENAUTH,
    0xA5 => CRAUTH,
    0xA7 => KEEPACTIVE
  }
  @codes Map.new(@names, fn {code, name} -> {name, code} end)

  @type name :: unquote(Enum.reduce(Map.values(@names), &{:|, [], [&1, &2]}))
  @type code :: unquote(Enum.reduce(Map.keys(@names), &{:|, [], [&1, &2]}))

  @spec new(Jeff.osdp_address(), name(), keyword()) :: t()
  def new(address, name, params \\ []) do
    {caller, params} = Keyword.pop(params, :caller)

    code = code(name)
    data = encode(name, params)

    %__MODULE__{
      address: address,
      code: code,
      data: data,
      name: name,
      caller: caller
    }
  end

  defp encode(POLL, _params), do: nil
  defp encode(ID, _params), do: <<0x00>>
  defp encode(CAP, _params), do: <<0x00>>
  defp encode(LSTAT, _params), do: nil
  defp encode(ISTAT, _params), do: nil
  defp encode(OSTAT, _params), do: nil
  defp encode(RSTAT, _params), do: nil
  defp encode(OUT, params), do: OutputSettings.encode(params)
  defp encode(LED, params), do: LedSettings.encode(params)
  defp encode(BUZ, params), do: BuzzerSettings.encode(params)
  defp encode(TEXT, params), do: TextSettings.encode(params)
  defp encode(COMSET, params), do: ComSettings.encode(params)
  defp encode(KEYSET, params), do: EncryptionKey.encode(params)
  defp encode(CHLNG, params), do: ChallengeData.encode(params)
  defp encode(SCRYPT, params), do: EncryptionServer.encode(params)
  defp encode(ACURXSIZE, size: size), do: <<size::size(16)-little>>
  defp encode(ABORT, _params), do: nil

  @spec code(name()) :: code()
  def code(name), do: @codes[name]
  @spec name(code()) :: name()
  def name(code), do: @names[code]
end