diff --git a/lib/jeff.ex b/lib/jeff.ex index a6e409d..90264b1 100644 --- a/lib/jeff.ex +++ b/lib/jeff.ex @@ -1,47 +1,80 @@ defmodule Jeff do - alias Jeff.ACU + @moduledoc """ + Control an Access Control Unit (ACU) and send commands to a Peripheral Device (PD) + """ + alias Jeff.{ACU, Command, Device, Reply} + + @type acu() :: GenServer.server() + @type device_opt() :: ACU.device_opt() + @type osdp_address() :: 0x00..0x7F + + @doc """ + Start an ACU process. + """ + @spec start_acu([ACU.start_opt()]) :: GenServer.on_start() def start_acu(opts \\ []) do ACU.start_link(opts) end + @doc """ + Register a peripheral device on the ACU communication bus. + """ + @spec add_pd(acu(), osdp_address(), [device_opt()]) :: Device.t() def add_pd(acu, address, opts \\ []) do ACU.add_device(acu, address, opts) end + @doc """ + Requests the return of the PD ID Report. + """ + @spec id_report(acu(), osdp_address()) :: Reply.IdReport.t() | Reply.ErrorCode.t() def id_report(acu, address) do - ACU.send_command(acu, address, ID) + ACU.send_command(acu, address, ID).data end + @doc """ + Requests the PD to return a list of its functional capabilities, such as the + type and number of input points, outputs points, reader ports, etc. + """ + @spec capabilities(acu(), osdp_address()) :: [Reply.Capability.t()] | Reply.ErrorCode.t() def capabilities(acu, address) do - ACU.send_command(acu, address, CAP) + ACU.send_command(acu, address, CAP).data end + @doc """ + Instructs the PD to reply with a local status report. + """ + @spec local_status(acu(), osdp_address()) :: Reply.LocalStatus.t() | Reply.ErrorCode.t() def local_status(acu, address) do - ACU.send_command(acu, address, LSTAT) - end - - def input_status(acu, address) do - ACU.send_command(acu, address, ISTAT) + ACU.send_command(acu, address, LSTAT).data end + @doc """ + Controls the LEDs associated with one or more readers. + """ + @spec set_led(acu(), osdp_address(), Command.LedSettings.params()) :: + Reply.ACK | Reply.ErrorCode.t() def set_led(acu, address, params) do - ACU.send_command(acu, address, LED, params) + ACU.send_command(acu, address, LED, params).data end + @doc """ + Defines commands to a single, monotone audible annunciator (beeper or buzzer) + that may be associated with a reader. + """ + @spec set_buzzer(acu(), osdp_address(), Command.BuzzerSettings.params()) :: + Reply.ACK | Reply.ErrorCode.t() def set_buzzer(acu, address, params) do - ACU.send_command(acu, address, BUZ, params) + ACU.send_command(acu, address, BUZ, params).data end + @doc """ + Sets the PD's communication parameters. + """ + @spec set_com(acu(), osdp_address(), Command.ComSettings.params()) :: + Reply.ComData.t() | Reply.ErrorCode.t() def set_com(acu, address, params) do - ACU.send_command(acu, address, COMSET, params) - end - - def set_key(acu, address, params) do - ACU.send_command(acu, address, KEYSET, params) - end - - def abort(acu, address) do - ACU.send_command(acu, address, ABORT) + ACU.send_command(acu, address, COMSET, params).data end end diff --git a/lib/jeff/acu.ex b/lib/jeff/acu.ex index 8d9a26d..2c3b8e6 100644 --- a/lib/jeff/acu.ex +++ b/lib/jeff/acu.ex @@ -1,4 +1,8 @@ defmodule Jeff.ACU do + @moduledoc """ + GenServer process for an ACU + """ + require Logger use GenServer @@ -6,37 +10,56 @@ defmodule Jeff.ACU do @max_reply_delay 200 - @type osdp_address :: 0x0..0x7F + @type acu() :: Jeff.acu() @type address_availability :: :available | :registered | :timeout | :error + @type osdp_address() :: Jeff.osdp_address() @type start_opt() :: - {:name, :atom} + {:name, atom()} | {:serial_port, String.t()} + @type device_opt() :: {:check_scheme, atom()} + + @doc """ + Start the ACU process. + """ @spec start_link([start_opt()]) :: GenServer.on_start() def start_link(opts \\ []) do {name, opts} = Keyword.pop(opts, :name) GenServer.start_link(__MODULE__, opts, name: name) end - def add_device(pid, address, opts \\ []) do - GenServer.call(pid, {:add_device, address, opts}) + @doc """ + Register a peripheral device on the ACU communication bus. + """ + @spec add_device(acu(), osdp_address(), [device_opt()]) :: Device.t() + def add_device(acu, address, opts \\ []) do + GenServer.call(acu, {:add_device, address, opts}) end - def send_command(pid, address, name, params \\ []) do - GenServer.call(pid, {:send_command, address, name, params}) + @doc """ + Send a command to a peripheral device. + """ + @spec send_command(acu(), osdp_address(), atom(), keyword()) :: Reply.t() + def send_command(acu, address, name, params \\ []) do + GenServer.call(acu, {:send_command, address, name, params}) end - def send_command_oob(pid, address, name, params \\ []) do - GenServer.call(pid, {:send_command_oob, address, name, params}) + @doc """ + Send a command to a peripheral device that is not yet registered on the ACU. + Intended to be used for maintenance/diagnostic purposes. + """ + @spec send_command_oob(acu(), osdp_address(), atom(), keyword()) :: Reply.t() + def send_command_oob(acu, address, name, params \\ []) do + GenServer.call(acu, {:send_command_oob, address, name, params}) end @doc """ Determine if a device is available to be registered on the bus. """ - @spec check_address(GenServer.server(), osdp_address()) :: address_availability() - def check_address(pid, address) do - GenServer.call(pid, {:check_address, address}) + @spec check_address(acu(), osdp_address()) :: address_availability() + def check_address(acu, address) do + GenServer.call(acu, {:check_address, address}) end @impl GenServer diff --git a/lib/jeff/bus.ex b/lib/jeff/bus.ex index acecffe..4a609bb 100644 --- a/lib/jeff/bus.ex +++ b/lib/jeff/bus.ex @@ -1,4 +1,6 @@ defmodule Jeff.Bus do + @moduledoc false + alias Jeff.Device defstruct registry: %{}, diff --git a/lib/jeff/command.ex b/lib/jeff/command.ex index 1225de6..2c3e8f4 100644 --- a/lib/jeff/command.ex +++ b/lib/jeff/command.ex @@ -1,32 +1,35 @@ defmodule Jeff.Command do @moduledoc """ - 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 + 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__{ diff --git a/lib/jeff/command/buzzer_settings.ex b/lib/jeff/command/buzzer_settings.ex index 8e2a50e..90432dc 100644 --- a/lib/jeff/command/buzzer_settings.ex +++ b/lib/jeff/command/buzzer_settings.ex @@ -1,14 +1,65 @@ defmodule Jeff.Command.BuzzerSettings do + @moduledoc """ + Reader Buzzer Control Command + + OSDP v2.2 Specification Reference: 6.11 + """ + defstruct reader: 0x00, tone: 0x01, on_time: 0x00, off_time: 0x00, count: 0x00 + @typedoc """ + Requested tone state + + 0x00 = no tone (off) – use of this value is deprecated. + 0x01 = off + 0x02 = default tone + 0x03-0xff = Reserved for future use + """ + @type tone_code() :: 0x00..0xFF + + @typedoc """ + The ON duration of the sound, in units of 100ms. Must be nonzero unless the + tone code is 0x01 (off). + """ + @type on_time() :: 0x00..0xFF + + @typedoc """ + The OFF duration of the sound, in units of 100ms. + """ + @type off_time() :: 0x00..0xFF + + @typedoc """ + The number of times to repeat the ON/OFF cycle. 0 = tone continues until + another tone command is received. + """ + @type count() :: 0x00..0xFF + + @type t :: %__MODULE__{ + reader: integer(), + tone: tone_code(), + on_time: on_time(), + off_time: off_time(), + count: count() + } + + @type param() :: + {:reader, integer()} + | {:tone, tone_code()} + | {:on_time, on_time()} + | {:off_time, off_time()} + | {:count, count()} + @type params :: t() | [param()] + + @spec new(params()) :: t() def new(params \\ []) do struct(__MODULE__, params) end + @spec encode(params()) :: binary() def encode(params) do settings = new(params) diff --git a/lib/jeff/command/challenge_data.ex b/lib/jeff/command/challenge_data.ex index d2bcef2..0b1e600 100644 --- a/lib/jeff/command/challenge_data.ex +++ b/lib/jeff/command/challenge_data.ex @@ -1,3 +1,5 @@ defmodule Jeff.Command.ChallengeData do + @moduledoc false + def encode(server_rnd: rnd), do: rnd end diff --git a/lib/jeff/command/com_settings.ex b/lib/jeff/command/com_settings.ex index edc512a..82d4ac0 100644 --- a/lib/jeff/command/com_settings.ex +++ b/lib/jeff/command/com_settings.ex @@ -1,11 +1,31 @@ defmodule Jeff.Command.ComSettings do + @moduledoc """ + Communication configuration command + + OSDP v2.2 Specification Reference: 6.13 + """ + defstruct address: 0x00, baud: 9600 + @type baud() :: 9600 + + @type t :: %__MODULE__{ + address: Jeff.osdp_address(), + baud: baud() + } + + @type param() :: + {:address, Jeff.osdp_address()} + | {:baud, baud()} + @type params :: t() | [param()] + + @spec new(params()) :: t() def new(params \\ []) do struct(__MODULE__, params) end + @spec encode(params()) :: binary() def encode(params) do %{address: address, baud: baud} = new(params) <> diff --git a/lib/jeff/command/encryption_key.ex b/lib/jeff/command/encryption_key.ex index edeb1db..bd2cfa8 100644 --- a/lib/jeff/command/encryption_key.ex +++ b/lib/jeff/command/encryption_key.ex @@ -1,4 +1,6 @@ defmodule Jeff.Command.EncryptionKey do + @moduledoc false + @secure_channel_base_key 0x01 defstruct [:key, type: @secure_channel_base_key] diff --git a/lib/jeff/command/encryption_server.ex b/lib/jeff/command/encryption_server.ex index f05c7c5..c06bb8e 100644 --- a/lib/jeff/command/encryption_server.ex +++ b/lib/jeff/command/encryption_server.ex @@ -1,3 +1,4 @@ defmodule Jeff.Command.EncryptionServer do + @moduledoc false def encode(cryptogram: cryptogram), do: cryptogram end diff --git a/lib/jeff/command/led_settings.ex b/lib/jeff/command/led_settings.ex index ed58171..0a91dea 100644 --- a/lib/jeff/command/led_settings.ex +++ b/lib/jeff/command/led_settings.ex @@ -1,29 +1,36 @@ defmodule Jeff.Command.LedSettings do @moduledoc """ + Reader LED control command + + OSDP v2.2 Specification Reference: 6.10 + Temporary Control Code Values - ----------------------------- - Code | Description - 0x00 | NOP – do not alter this LED's temporary settings. The remaining values of the temporary settings record are ignored. - 0x01 | Cancel any temporary operation and display this LED's permanent state immediately. - 0x02 | Set the temporary state as given and start timer immediately. + + | Code | Description + |------|----------------------------------------------------------------------------------------------------------------------| + | 0x00 | NOP – do not alter this LED's temporary settings. The remaining values of the temporary settings record are ignored. | + | 0x01 | Cancel any temporary operation and display this LED's permanent state immediately. | + | 0x02 | Set the temporary state as given and start timer immediately. | Permanent Control Code Values - ----------------------------- - Code | Description - 0x00 | NOP – do not alter this LED's permanent settings. The remaining values of the temporary settings record are ignored. - 0x01 | Set the permanent state as given. + + | Code | Description + |-----------------------------------------------------------------------------------------------------------------------------| + | 0x00 | NOP – do not alter this LED's permanent settings. The remaining values of the temporary settings record are ignored. | + | 0x01 | Set the permanent state as given. | Color Values - ------------ - Value | Description - 0 | Black (off/unlit) - 1 | Red - 2 | Green - 3 | Amber - 4 | Blue - 5 | Magenta - 6 | Cyan - 7 | White + + | Value | Description | + |---------------------------| + | 0 | Black (off/unlit) | + | 1 | Red | + | 2 | Green | + | 3 | Amber | + | 4 | Blue | + | 5 | Magenta | + | 6 | Cyan | + | 7 | White | """ defstruct reader: 0x0, @@ -40,10 +47,44 @@ defmodule Jeff.Command.LedSettings do perm_on_color: 0x00, perm_off_color: 0x00 + @type t :: %__MODULE__{ + reader: integer(), + led: integer(), + temp_mode: 0x00 | 0x01 | 0x02, + temp_on_time: integer(), + temp_off_time: integer(), + temp_on_color: 0x00..0x07, + temp_off_color: 0x00..0x07, + temp_timer: integer(), + perm_mode: 0x00 | 0x01, + perm_on_time: integer(), + perm_off_time: integer(), + perm_on_color: 0x00..0x07, + perm_off_color: 0x00..0x07 + } + + @type param() :: + {:reader, integer()} + | {:led, integer()} + | {:temp_mode, 0x00 | 0x01 | 0x02} + | {:temp_on_time, integer()} + | {:temp_off_time, integer()} + | {:temp_on_color, 0x00..0x07} + | {:temp_off_color, 0x00..0x07} + | {:temp_timer, integer()} + | {:perm_mode, 0x00 | 0x01} + | {:perm_on_time, integer()} + | {:perm_off_time, integer()} + | {:perm_on_color, 0x00..0x07} + | {:perm_off_color, 0x00..0x07} + @type params() :: t() | [param()] + + @spec new(params()) :: t() def new(params) do struct(__MODULE__, params) end + @spec encode(params()) :: binary() def encode(params) do settings = new(params) diff --git a/lib/jeff/command/output_settings.ex b/lib/jeff/command/output_settings.ex index 3310921..4bca8bb 100644 --- a/lib/jeff/command/output_settings.ex +++ b/lib/jeff/command/output_settings.ex @@ -1,15 +1,20 @@ defmodule Jeff.Command.OutputSettings do @moduledoc """ + Output control command + + OSDP v2.2 Specification Reference: 6.7 + Control Code Values - ------------------- - Code | Description - 0x00 | NOP – do not alter this output - 0x01 | Set the permanent state to OFF, abort timed operation (if any) - 0x02 | Set the permanent state to ON, abort timed operation (if any) - 0x03 | Set the permanent state to OFF, allow timed operation to complete - 0x04 | Set the permanent state to ON, allow timed operation to complete - 0x05 | Set the temporary state to ON, resume permanent state on timeout - 0x06 | Set the temporary state to OFF, resume permanent state on timeout + + | Code | Description | + |------|-------------------------------------------------------------------| + | 0x00 | NOP – do not alter this output | + | 0x01 | Set the permanent state to OFF, abort timed operation (if any) | + | 0x02 | Set the permanent state to ON, abort timed operation (if any) | + | 0x03 | Set the permanent state to OFF, allow timed operation to complete | + | 0x04 | Set the permanent state to ON, allow timed operation to complete | + | 0x05 | Set the temporary state to ON, resume permanent state on timeout | + | 0x06 | Set the temporary state to OFF, resume permanent state on timeout | """ def encode(output: output, code: code, timer: timer) do diff --git a/lib/jeff/command/text_settings.ex b/lib/jeff/command/text_settings.ex index eb7f502..f618a1c 100644 --- a/lib/jeff/command/text_settings.ex +++ b/lib/jeff/command/text_settings.ex @@ -1,4 +1,6 @@ defmodule Jeff.Command.TextSettings do + @moduledoc false + defstruct reader: 0x00, temporary?: false, wrap?: false, diff --git a/lib/jeff/control_info.ex b/lib/jeff/control_info.ex index 25c9a03..9949009 100644 --- a/lib/jeff/control_info.ex +++ b/lib/jeff/control_info.ex @@ -1,4 +1,6 @@ defmodule Jeff.ControlInfo do + @moduledoc false + def encode(%{sequence: seq, check_scheme: cs, security?: sec}) do encode(seq, cs, sec) end diff --git a/lib/jeff/device.ex b/lib/jeff/device.ex index 3b42c46..b7498d5 100644 --- a/lib/jeff/device.ex +++ b/lib/jeff/device.ex @@ -1,12 +1,13 @@ defmodule Jeff.Device do - @moduledoc false + @moduledoc """ + Peripheral Device configuration and handling + """ - @type device_address :: 0..127 @type check_scheme :: :checksum | :crc @type sequence_number :: 0..3 @type t :: %__MODULE__{ - address: device_address(), + address: Jeff.osdp_address(), check_scheme: check_scheme(), security?: boolean(), secure_channel: term(), diff --git a/lib/jeff/error_checks.ex b/lib/jeff/error_checks.ex index 945a08a..4ab175c 100644 --- a/lib/jeff/error_checks.ex +++ b/lib/jeff/error_checks.ex @@ -1,4 +1,6 @@ defmodule Jeff.ErrorChecks do + @moduledoc false + use Bitwise @spec crc(binary()) :: integer() diff --git a/lib/jeff/events/card_read.ex b/lib/jeff/events/card_read.ex index ad4abd7..d507d68 100644 --- a/lib/jeff/events/card_read.ex +++ b/lib/jeff/events/card_read.ex @@ -4,7 +4,7 @@ defmodule Jeff.Events.CardRead do """ @type t :: %__MODULE__{ - address: 0..127, + address: Jeff.osdp_address(), data: binary(), format: non_neg_integer(), length: non_neg_integer(), diff --git a/lib/jeff/events/keypress.ex b/lib/jeff/events/keypress.ex index ab79b79..efca10a 100644 --- a/lib/jeff/events/keypress.ex +++ b/lib/jeff/events/keypress.ex @@ -4,7 +4,7 @@ defmodule Jeff.Events.Keypress do """ @type t :: %__MODULE__{ - address: 0..127, + address: Jeff.osdp_address(), count: non_neg_integer(), keys: binary(), reader: non_neg_integer() diff --git a/lib/jeff/message.ex b/lib/jeff/message.ex index 787d722..23115a5 100644 --- a/lib/jeff/message.ex +++ b/lib/jeff/message.ex @@ -1,4 +1,6 @@ defmodule Jeff.Message do + @moduledoc false + alias Jeff.{ControlInfo, SecureChannel} import Jeff.ErrorChecks diff --git a/lib/jeff/reply.ex b/lib/jeff/reply.ex index 08df569..5b8e1cf 100644 --- a/lib/jeff/reply.ex +++ b/lib/jeff/reply.ex @@ -1,31 +1,34 @@ defmodule Jeff.Reply do @moduledoc """ - Name | Code | Description | Data Type - ACK | 0x40 | Command accepted, nothing else to report | - - NAK | 0x41 | Command not processed | ErrorCode - PDID | 0x45 | PD ID Report | IdReport - PDCAP | 0x46 | PD Capabilities Report | [Capability] - LSTATR | 0x48 | Local Status Report | Report data - ISTATR | 0x49 | Input Status Report | Report data - OSTATR | 0x4A | Output Status Report | Report data - RSTATR | 0x4B | Reader Status Report | Report data - RAW | 0x50 | Reader Data – Raw bit image of card data | CardData - FMT | 0x51 | Reader Data – Formatted character stream | Card data - KEYPAD | 0x53 | Keypad Data | KeypadData - COM | 0x54 | PD Communications Configuration Report | ComData - BIOREADR | 0x57 | Biometric Data | Biometric data - BIOMATCHR | 0x58 | Biometric Match Result | Result - CCRYPT | 0x76 | Client's ID, Random Number, and Cryptogram | EncryptionClient - BUSY | 0x79 | PD is Busy reply | - - RMAC_I | 0x78 | Initial R-MAC | Encryption Data - FTSTAT | 0x7A | File transfer status | Status details - PIVDATAR | 0x80 | PIV Data Reply | credential data - GENAUTHR | 0x81 | Authentication response | response details - CRAUTHR | 0x82 | Response to challenge | response details - MFGSTATR | 0x83 | MFG specific status | status details - MFGERRR | 0x84 | MFG specific error | error details - MFGREP | 0x90 | Manufacturer Specific Reply | Any - XRD | 0xB1 | Extended Read Response | APDU and details + Replies are sent from a PD to an ACU in response to a command + + | Name | Code | Description | Data Type | + |-----------|------|--------------------------------------------|------------------| + | ACK | 0x40 | Command accepted, nothing else to report | - | + | NAK | 0x41 | Command not processed | ErrorCode | + | PDID | 0x45 | PD ID Report | IdReport | + | PDCAP | 0x46 | PD Capabilities Report | [Capability] | + | LSTATR | 0x48 | Local Status Report | Report data | + | ISTATR | 0x49 | Input Status Report | Report data | + | OSTATR | 0x4A | Output Status Report | Report data | + | RSTATR | 0x4B | Reader Status Report | Report data | + | RAW | 0x50 | Reader Data – Raw bit image of card data | CardData | + | FMT | 0x51 | Reader Data – Formatted character stream | CardData | + | KEYPAD | 0x53 | Keypad Data | KeypadData | + | COM | 0x54 | PD Communications Configuration Report | ComData | + | BIOREADR | 0x57 | Biometric Data | Biometric data | + | BIOMATCHR | 0x58 | Biometric Match Result | Result | + | CCRYPT | 0x76 | Client's ID, Random Number, and Cryptogram | EncryptionClient | + | BUSY | 0x79 | PD is Busy reply | - | + | RMAC_I | 0x78 | Initial R-MAC | Encryption Data | + | FTSTAT | 0x7A | File transfer status | Status details | + | PIVDATAR | 0x80 | PIV Data Reply | credential data | + | GENAUTHR | 0x81 | Authentication response | response details | + | CRAUTHR | 0x82 | Response to challenge | response details | + | MFGSTATR | 0x83 | MFG specific status | status details | + | MFGERRR | 0x84 | MFG specific error | error details | + | MFGREP | 0x90 | Manufacturer Specific Reply | Any | + | XRD | 0xB1 | Extended Read Response | APDU and details | """ use Bitwise @@ -44,7 +47,7 @@ defmodule Jeff.Reply do @type t() :: %__MODULE__{ address: byte(), code: byte(), - data: binary() | map(), + data: binary() | map() | list(), name: atom() } @@ -108,9 +111,7 @@ defmodule Jeff.Reply do address &&& 0b01111111 end - defp decode(_name, nil), do: nil - defp decode(_name, <<>>), do: nil - defp decode(ACK, _data), do: nil + defp decode(ACK, _data), do: Jeff.Reply.ACK defp decode(NAK, data), do: ErrorCode.decode(data) defp decode(PDID, data), do: IdReport.decode(data) defp decode(PDCAP, data), do: Capability.decode(data) @@ -120,6 +121,8 @@ defmodule Jeff.Reply do defp decode(RAW, data), do: CardData.decode(data) defp decode(CCRYPT, data), do: EncryptionClient.decode(data) defp decode(RMAC_I, data), do: data + defp decode(_name, nil), do: nil + defp decode(_name, <<>>), do: nil defp decode(name, data), do: Module.concat(__MODULE__, name).decode(data) def code(name), do: @codes[name] diff --git a/lib/jeff/reply/capability.ex b/lib/jeff/reply/capability.ex index 2818176..b385023 100644 --- a/lib/jeff/reply/capability.ex +++ b/lib/jeff/reply/capability.ex @@ -1,6 +1,19 @@ defmodule Jeff.Reply.Capability do + @moduledoc """ + Peripheral Device Capabilities Report + + OSDP v2.2 Specification Reference: 7.5 + """ + defstruct [:function, :compliance, :number_of, :description] + @type t :: %__MODULE__{ + function: integer(), + compliance: integer(), + number_of: integer(), + description: String.t() + } + @functions %{ 1 => "Contact Status Monitoring", 2 => "Output Control", @@ -29,6 +42,7 @@ defmodule Jeff.Reply.Capability do } end + @spec decode(binary()) :: [t()] def decode(data) do do_decode(data, []) end diff --git a/lib/jeff/reply/card_data.ex b/lib/jeff/reply/card_data.ex index acfbcb7..3632d03 100644 --- a/lib/jeff/reply/card_data.ex +++ b/lib/jeff/reply/card_data.ex @@ -1,4 +1,10 @@ defmodule Jeff.Reply.CardData do + @moduledoc """ + Card Data Report + + OSDP v2.2 Specification Reference: 7.10 + """ + defstruct [:reader, :format, :length, :data] def decode(<>) do diff --git a/lib/jeff/reply/com_data.ex b/lib/jeff/reply/com_data.ex index 6aeac38..f1cc0bb 100644 --- a/lib/jeff/reply/com_data.ex +++ b/lib/jeff/reply/com_data.ex @@ -1,7 +1,19 @@ defmodule Jeff.Reply.ComData do + @moduledoc """ + Communication Configuration Report + + OSDP v2.2 Specification Reference: 7.13 + """ + defstruct address: 0x00, baud: 9600 + @type t :: %__MODULE__{ + address: Jeff.osdp_address(), + baud: 9600 + } + + @spec decode(binary()) :: t() def decode(<>) do %__MODULE__{address: address, baud: baud} end diff --git a/lib/jeff/reply/encryption_client.ex b/lib/jeff/reply/encryption_client.ex index d2d25a9..2cdf913 100644 --- a/lib/jeff/reply/encryption_client.ex +++ b/lib/jeff/reply/encryption_client.ex @@ -1,4 +1,6 @@ defmodule Jeff.Reply.EncryptionClient do + @moduledoc false + defstruct [:cuid, :rnd, :cryptogram] def decode(<< diff --git a/lib/jeff/reply/error_code.ex b/lib/jeff/reply/error_code.ex index 28d40b1..025368d 100644 --- a/lib/jeff/reply/error_code.ex +++ b/lib/jeff/reply/error_code.ex @@ -1,6 +1,17 @@ defmodule Jeff.Reply.ErrorCode do + @moduledoc """ + Negative Acknowledge – Error Response + + OSDP v2.2 Specification Reference: 7.3 + """ + defstruct [:code, :description] + @type t :: %__MODULE__{ + code: integer(), + description: String.t() + } + @description %{ 0x00 => "No error", 0x01 => "Message check character(s) error (bad cksum/crc)", diff --git a/lib/jeff/reply/id_report.ex b/lib/jeff/reply/id_report.ex index beccb09..c2cf6bb 100644 --- a/lib/jeff/reply/id_report.ex +++ b/lib/jeff/reply/id_report.ex @@ -1,6 +1,20 @@ defmodule Jeff.Reply.IdReport do + @moduledoc """ + Device identification Report + + OSDP v2.2 Specification Reference: 7.4 + """ + defstruct [:vendor, :model, :version, :serial, :firmware] + @type t :: %__MODULE__{ + firmware: String.t(), + model: integer(), + serial: String.t(), + vendor: String.t(), + version: integer() + } + def decode(data) do << vendor1, diff --git a/lib/jeff/reply/keypad_data.ex b/lib/jeff/reply/keypad_data.ex index 79522b6..97fe40f 100644 --- a/lib/jeff/reply/keypad_data.ex +++ b/lib/jeff/reply/keypad_data.ex @@ -1,4 +1,10 @@ defmodule Jeff.Reply.KeypadData do + @moduledoc """ + Keypad Data Report + + OSDP v2.2 Specification Reference: 7.12 + """ + defstruct [:reader, :count, :keys] def decode(<>) do diff --git a/lib/jeff/reply/local_status.ex b/lib/jeff/reply/local_status.ex index 2f94fca..26f0bc8 100644 --- a/lib/jeff/reply/local_status.ex +++ b/lib/jeff/reply/local_status.ex @@ -1,6 +1,23 @@ defmodule Jeff.Reply.LocalStatus do + @moduledoc """ + Local Status Report + + OSDP v2.2 Specification Reference: 7.6 + """ + defstruct [:tamper, :power] + @type tamper_status() :: :normal | :tamper + @type power_status() :: :normal | :failure + @type tamper_code() :: 0x00 | 0x01 + @type power_code() :: 0x00 | 0x01 + + @type t :: %__MODULE__{ + tamper: tamper_status(), + power: power_status() + } + + @spec new(tamper_code(), power_code()) :: t() def new(tamper_code, power_code) do %__MODULE__{ tamper: tamper_status(tamper_code), @@ -8,6 +25,7 @@ defmodule Jeff.Reply.LocalStatus do } end + @spec decode(binary()) :: t() def decode(<>) do __MODULE__.new(tamper, power) end diff --git a/lib/jeff/secure_channel.ex b/lib/jeff/secure_channel.ex index c5df302..e8538b0 100644 --- a/lib/jeff/secure_channel.ex +++ b/lib/jeff/secure_channel.ex @@ -1,4 +1,6 @@ defmodule Jeff.SecureChannel do + @moduledoc false + use Bitwise defstruct [ diff --git a/test/bus_test.exs b/test/bus_test.exs index a63a2bb..aa7c0a7 100644 --- a/test/bus_test.exs +++ b/test/bus_test.exs @@ -141,7 +141,7 @@ defmodule BusTest do poll_command = Command.new(0x01, POLL) ack_reply = Reply.new(0x01, ACK) id_command = Command.new(0x01, ID) - pdid_reply = Reply.new(0x01, PDID) + pdid_reply = Reply.new(0x01, PDID_TEST) bus = Bus.new() bus = Bus.add_device(bus, address: 0x01) diff --git a/test/reply_test.exs b/test/reply_test.exs index 268677e..96a6ac3 100644 --- a/test/reply_test.exs +++ b/test/reply_test.exs @@ -5,12 +5,12 @@ defmodule ReplyTest do test "build a new reply from a message" do reply = Reply.new(%Message{address: 0x01, code: 0x40, data: nil}) - assert %{code: 0x40, name: ACK, data: nil} = reply + assert %{code: 0x40, name: ACK, data: Jeff.Reply.ACK} = reply end test "build a new reply from module" do reply = Reply.new(0x01, ACK) - assert %{address: 0x01, code: 0x40, name: ACK, data: nil} = reply + assert %{address: 0x01, code: 0x40, name: ACK, data: Jeff.Reply.ACK} = reply end test "build a reply with an unknown code" do