mgmt/osdp: Add support for OSDP in PD mode of operation

Open Supervised Device Protocol (OSDP) describes the communication
protocol for interfacing one or more Peripheral Devices (PD) to a
Control Panel (CP). The PDs are slave devices that waits for commands
from a CP. The communication happens over a RS485 multi-drop connection
with specification for a secure channel communication.

This patch adds initial support for OSDP in PD mode without secure
channel.

Signed-off-by: Siddharth Chandrasekaran <siddharth@embedjournal.com>
This commit is contained in:
Siddharth Chandrasekaran
2020-06-28 20:05:51 +05:30
committed by Carles Cufí
parent 27f207694c
commit 9a91b4adf9
11 changed files with 2169 additions and 0 deletions

225
include/mgmt/osdp.h Normal file
View File

@@ -0,0 +1,225 @@
/*
* Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _OSDP_H_
#define _OSDP_H_
#include <zephyr.h>
#include <stdint.h>
#include <sys/slist.h>
#ifdef __cplusplus
extern "C" {
#endif
#define OSDP_CMD_TEXT_MAX_LEN 32
/**
* @brief Various card formats that a PD can support. This is sent to CP
* when a PD must report a card read.
*/
enum osdp_card_formats_e {
OSDP_CARD_FMT_RAW_UNSPECIFIED,
OSDP_CARD_FMT_RAW_WIEGAND,
OSDP_CARD_FMT_ASCII,
OSDP_CARD_FMT_SENTINEL
};
/**
* @brief Command sent from CP to Control digital output of PD.
*
* @param output_no 0 = First Output, 1 = Second Output, etc.
* @param control_code One of the following:
* 0 - NOP do not alter this output
* 1 - set the permanent state to OFF, abort timed operation (if any)
* 2 - set the permanent state to ON, abort timed operation (if any)
* 3 - set the permanent state to OFF, allow timed operation to complete
* 4 - set the permanent state to ON, allow timed operation to complete
* 5 - set the temporary state to ON, resume perm state on timeout
* 6 - set the temporary state to OFF, resume permanent state on timeout
* @param tmr_count Time in units of 100 ms
*/
struct osdp_cmd_output {
uint8_t output_no;
uint8_t control_code;
uint16_t tmr_count;
};
/**
* @brief LED Colors as specified in OSDP for the on_color/off_color parameters.
*/
enum osdp_led_color_e {
OSDP_LED_COLOR_NONE,
OSDP_LED_COLOR_RED,
OSDP_LED_COLOR_GREEN,
OSDP_LED_COLOR_AMBER,
OSDP_LED_COLOR_BLUE,
OSDP_LED_COLOR_SENTINEL
};
/**
* @brief LED params sub-structure. Part of LED command. See struct osdp_cmd_led
*
* @param control_code One of the following:
* Temporary Control Code:
* 0 - NOP - do not alter this LED's temporary settings
* 1 - Cancel any temporary operation and display this LED's permanent state
* immediately
* 2 - Set the temporary state as given and start timer immediately
* Permanent Control Code:
* 0 - NOP - do not alter this LED's permanent settings
* 1 - Set the permanent state as given
* @param on_count The ON duration of the flash, in units of 100 ms
* @param off_count The OFF duration of the flash, in units of 100 ms
* @param on_color Color to set during the ON timer (enum osdp_led_color_e)
* @param off_color Color to set during the OFF timer (enum osdp_led_color_e)
* @param timer Time in units of 100 ms (only for temporary mode)
*/
struct osdp_cmd_led_params {
uint8_t control_code;
uint8_t on_count;
uint8_t off_count;
uint8_t on_color;
uint8_t off_color;
uint16_t timer;
};
/**
* @brief Sent from CP to PD to control the behaviour of it's on-board LEDs
*
* @param reader 0 = First Reader, 1 = Second Reader, etc.
* @param led_number 0 = first LED, 1 = second LED, etc.
* @param temporary ephemeral LED status descriptor
* @param permanent permanent LED status descriptor
*/
struct osdp_cmd_led {
uint8_t reader;
uint8_t led_number;
struct osdp_cmd_led_params temporary;
struct osdp_cmd_led_params permanent;
};
/**
* @brief Sent from CP to control the behaviour of a buzzer in the PD.
*
* @param reader 0 = First Reader, 1 = Second Reader, etc.
* @param tone_code 0: no tone, 1: off, 2: default tone, 3+ is TBD.
* @param on_count The ON duration of the flash, in units of 100 ms
* @param off_count The OFF duration of the flash, in units of 100 ms
* @param rep_count The number of times to repeat the ON/OFF cycle; 0: forever
*/
struct osdp_cmd_buzzer {
uint8_t reader;
uint8_t tone_code;
uint8_t on_count;
uint8_t off_count;
uint8_t rep_count;
};
/**
* @brief Command to manuplate any display units that the PD supports.
*
* @param reader 0 = First Reader, 1 = Second Reader, etc.
* @param cmd One of the following:
* 1 - permanent text, no wrap
* 2 - permanent text, with wrap
* 3 - temp text, no wrap
* 4 - temp text, with wrap
* @param temp_time duration to display temporary text, in seconds
* @param offset_row row to display the first character (1 indexed)
* @param offset_col column to display the first character (1 indexed)
* @param length Number of characters in the string
* @param data The string to display
*/
struct osdp_cmd_text {
uint8_t reader;
uint8_t cmd;
uint8_t temp_time;
uint8_t offset_row;
uint8_t offset_col;
uint8_t length;
uint8_t data[OSDP_CMD_TEXT_MAX_LEN];
};
/**
* @brief Sent in response to a COMSET command. Set communication parameters to
* PD. Must be stored in PD non-volatile memory.
*
* @param addr Unit ID to which this PD will respond after the change takes
* effect.
* @param baud baud rate value 9600/38400/115200
*/
struct osdp_cmd_comset {
uint8_t addr;
uint32_t baud;
};
/**
* @brief This command transfers an encryption key from the CP to a PD.
*
* @param key_type Type of keys:
* - 0x01 Secure Channel Base Key
* @param len Number of bytes of key data - (Key Length in bits + 7) / 8
* @param data Key data
*/
struct osdp_cmd_keyset {
uint8_t key_type;
uint8_t len;
uint8_t data[32];
};
/**
* @brief OSDP application exposed commands
*/
enum osdp_cmd_e {
OSDP_CMD_OUTPUT = 1,
OSDP_CMD_LED,
OSDP_CMD_BUZZER,
OSDP_CMD_TEXT,
OSDP_CMD_KEYSET,
OSDP_CMD_COMSET,
OSDP_CMD_SENTINEL
};
/**
* @brief OSDP Command Structure. This is a wrapper for all individual OSDP
* commands.
*
* @param id used to select specific commands in union. Type: enum osdp_cmd_e
* @param led LED command structure
* @param buzzer buzzer command structure
* @param text text command structure
* @param output output command structure
* @param comset comset command structure
* @param keyset keyset command structure
*/
struct osdp_cmd {
sys_snode_t node;
int id;
union {
struct osdp_cmd_led led;
struct osdp_cmd_buzzer buzzer;
struct osdp_cmd_text text;
struct osdp_cmd_output output;
struct osdp_cmd_comset comset;
struct osdp_cmd_keyset keyset;
};
};
/**
* @param cmd pointer to a command structure that would be filled by the driver.
*
* @retval 0 on success.
* @retval -1 on failure.
*/
int osdp_pd_get_cmd(struct osdp_cmd *cmd);
#ifdef __cplusplus
}
#endif
#endif /* _OSDP_H_ */

View File

@@ -2,3 +2,4 @@
add_subdirectory_ifdef(CONFIG_MCUMGR mcumgr)
add_subdirectory_ifdef(CONFIG_UPDATEHUB updatehub)
add_subdirectory_ifdef(CONFIG_OSDP osdp)

View File

@@ -8,4 +8,6 @@ source "subsys/mgmt/mcumgr/Kconfig"
source "subsys/mgmt/updatehub/Kconfig"
source "subsys/mgmt/osdp/Kconfig"
endmenu

View File

@@ -0,0 +1,17 @@
#
# Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
#
# SPDX-License-Identifier: Apache-2.0
#
zephyr_library()
# PD mode specific sources
zephyr_library_sources_ifdef(CONFIG_OSDP_MODE_PD src/osdp_pd.c)
# Common sources
zephyr_library_sources_ifdef(CONFIG_OSDP
src/osdp_phy.c
src/osdp_common.c
src/osdp.c
)

80
subsys/mgmt/osdp/Kconfig Normal file
View File

@@ -0,0 +1,80 @@
#
# Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
#
# SPDX-License-Identifier: Apache-2.0
#
menuconfig OSDP
bool "Open Supervised Device Protocol (OSDP) driver"
select RING_BUFFER
select SERIAL
select SERIAL_HAS_DRIVER
select SERIAL_SUPPORT_INTERRUPT
select UART_INTERRUPT_DRIVEN
select ENTROPY_GENERATOR
select TEST_RANDOM_GENERATOR
help
Add support for Open Supervised Device Protocol (OSDP)
if OSDP
# Workaround for not being able to have commas in macro arguments
DT_CHOSEN_Z_OSDP_UART := zephyr,osdp-uart
config OSDP_UART_DEV_NAME
string "Device name of UART device for OSDP"
default "$(dt_chosen_label,$(DT_CHOSEN_Z_OSDP_UART))" if HAS_DTS
default "UART_2"
help
This option specifies the name of UART device to be used for
OSDP
config OSDP_UART_BAUD_RATE
int "OSDP UART baud rate"
default 115200
help
OSDP defines that baud rate can be either 9600 or 38400 or
115200.
config OSDP_LOG_LEVEL
int "OSDP Logging Level"
default 1
help
Set the logging level for the OSDP driver
config OSDP_UART_BUFFER_LENGTH
int "OSDP UART buffer length"
default 256
help
OSDP RX and TX buffer FIFO length.
config OSDP_THREAD_STACK_SIZE
int "OSDP Thread stack size"
default 512
help
Thread stack size for osdp refresh thread
config OSDP_PACKET_TRACE
bool "Print bytes sent/received over OSDP to console"
help
Prints bytes sent/received over OSDP to console for debugging.
LOG_HEXDUMP_DBG() is used to achieve this and can be very verbose.
choice
prompt "OSDP Mode of Operation"
default OSDP_MODE_PD
config OSDP_MODE_CP
bool "Configure OSDP in Control Panel mode"
help
Configure this device to operate as a CP (Control Panel)
config OSDP_MODE_PD
bool "Configure OSDP in Peripheral Device mode"
help
Configure this device to operate as a PD (Peripheral Device)
endchoice
if OSDP_MODE_PD
source "subsys/mgmt/osdp/Kconfig.pd"
endif
endif # OSDP

228
subsys/mgmt/osdp/Kconfig.pd Normal file
View File

@@ -0,0 +1,228 @@
#
# Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
#
# SPDX-License-Identifier: Apache-2.0
#
# OSDP_NUM_CONNECTED_PD is used in CP mode. It is a constant set to 1 when
# operating as a PD.
config OSDP_NUM_CONNECTED_PD
int
default 1
help
In PD mode, number of connected PDs is is always 1 and cannot
be configured.
config OSDP_PD_COMMAND_QUEUE_SIZE
int "OSDP Peripheral Device command queue size"
default 16
help
The number of commands that can be queued to a given PD. In CP mode,
the queue size is multiplied by number of connected PD so this can grow
very quickly.
config OSDP_PD_ADDRESS
int "Peripheral Device Address"
default 1
range 1 126
help
The 7 least significant bits represent the address of the PD to which
the message is directed, or the address of the PD sending the reply.
Address 0x7F is reserved as a broadcast address to which all PDs would
respond.
menu "Peripheral Device ID Information"
config OSDP_PD_ID_VENDOR_CODE
hex "PD Vendor Code"
default 0x001A2B3C
range 0 0x00FFFFFF
help
IEEE assigned OUI. Least 24 bits are valid.
config OSDP_PD_ID_MODEL
int "PD Product Model Number"
default 1
range 0 255
help
Manufacturer's model number. Least 8 bits are valid.
config OSDP_PD_ID_VERSION
int "PD Product Version"
default 1
range 0 255
help
Manufacturer's version of this product. Least 8 bits are valid.
config OSDP_PD_ID_SERIAL_NUMBER
hex "PD Serial Number"
default 0xCAFEBABE
range 0 0xFFFFFFFF
help
A 4-byte serial number for the PD.
config OSDP_PD_ID_FIRMWARE_VERSION
hex "PD Firmware Version"
default 0x00010100
range 0 0x00FFFFFF
help
Firmware revision code.
- Bit 0-7 : build version number;
- Bit 8-15 : minor version number;
- Bit 16-23: major version number;
endmenu # "PD ID Information"
menu "OSDP PD Capabilities"
menu "Contact Status Monitoring"
config OSDP_PD_CAP_CONTACT_STATUS_MONITORING_COMP_LEVEL
int "Compliance Level"
default 0
range 0 4
help
Possible values:
- 01: PD monitors and reports the state of the circuit without any
supervision. The PD encodes the circuit status per its default
interpretation of contact state to active/inactive status.
- 02: Like 01, plus: The PD accepts configuration of the encoding of the
open/closed circuit status to the reported active/inactive status. (User
may configure each circuit as "normally closed" or "normally open".)
- 03: Like 02, plus: PD supports supervised monitoring. The operating mode
for each circuit is determined by configuration settings.
- 04: Like 03, plus: the PD supports custom End-Of-Line settings within
the Manufacturer's guidelines.
config OSDP_PD_CAP_CONTACT_STATUS_MONITORING_NUM_ITEMS
int "Number of items"
default 0
help
The number of Inputs
endmenu # "Contact Status Monitoring"
menu "Output Control"
config OSDP_PD_CAP_OUTPUT_CONTROL_COMP_LEVEL
int "Compliance Level"
default 0
range 0 4
help
Possible values:
- 01: The PD is able to activate and deactivate the Output per direct
command from the CP.
- 02: Like 01, plus: The PD is able to accept configuration of the Output
driver to set the inactive state of the Output. The typical state of an
inactive Output is the state of the Output when no power is applied to the
PD and the output device (relay) is not energized. The inverted drive
setting causes the PD to energize the Output during the inactive state and
de-energize the Output during the active state. This feature allows the
support of "fail-safe/fail-secure" operating modes.
- 03: Like 01, plus: The PD is able to accept timed commands to the
Output. A timed command specifies the state of the Output for the
specified duration.
- 04: Like 02 and 03 - normal/inverted drive and timed operation.
config OSDP_PD_CAP_OUTPUT_CONTROL_NUM_ITEMS
int "Number of items"
default 0
help
The number of Outputs.
endmenu # "Output Control"
menu "LED Control"
config OSDP_PD_CAP_READER_LED_CONTROL_COMP_LEVEL
int "Compliance Level"
default 0
range 0 4
help
Possible values:
- 01: the PD support on/off control only
- 02: the PD supports timed commands
- 03: like 02, plus bi-color LEDs
- 04: like 02, plus tri-color LEDs
config OSDP_PD_CAP_READER_LED_CONTROL_NUM_ITEMS
int "Number of items"
default 0
help
The number of LEDs per reader.
endmenu # "LED Control"
menu "Audible Output"
config OSDP_PD_CAP_READER_AUDIBLE_OUTPUT_COMP_LEVEL
int "Compliance Level"
default 0
range 0 2
help
Possible values:
- 01: the PD support on/off control only
- 02: the PD supports timed commands
config OSDP_PD_CAP_READER_AUDIBLE_OUTPUT_NUM_ITEMS
int "Number of items"
default 0
help
The number of audible annunciators per reader
endmenu # "Audible Output"
menu "Text Output"
config OSDP_PD_CAP_READER_TEXT_OUTPUT_COMP_LEVEL
int "Compliance Level"
default 0
range 0 4
help
Possible values:
- 00: The PD has no text display support
- 01: The PD supports 1 row of 16 characters
- 02: the PD supports 2 rows of 16 characters
- 03: the PD supports 4 rows of 16 characters
- 04: TBD.
config OSDP_PD_CAP_READER_TEXT_OUTPUT_NUM_ITEMS
int "Number of items"
default 0
help
Number of textual displays per reader
endmenu # "Text Output"
menu "Card Data Format"
config OSDP_PD_CAP_CARD_DATA_FORMAT_COMP_LEVEL
int "Compliance Level"
default 0
range 0 3
help
Possible values:
- 01: the PD sends card data to the CP as array of bits, not exceeding
1024 bits.
- 02: the PD sends card data to the CP as array of BCD characters, not
exceeding 256 characters.
- 03: the PD can send card data to the CP as array of bits, or as an
array of BCD characters.
endmenu # "Card Data Format"
menu "Time Keeping"
config OSDP_PD_CAP_TIME_KEEPING_COMP_LEVEL
int "Compliance Level"
default 0
range 0 2
help
Possible values:
- 00: The PD does not support time/date functionality
- 01: The PD understands time/date settings per Command osdp_TDSET
- 02: The PD is able to locally update the time and date
endmenu # "Time Keeping"
endmenu # "OSDP PD Capabilities"

182
subsys/mgmt/osdp/src/osdp.c Normal file
View File

@@ -0,0 +1,182 @@
/*
* Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <kernel.h>
#include <init.h>
#include <device.h>
#include <drivers/uart.h>
#include <sys/ring_buffer.h>
#include <logging/log.h>
#include "osdp_common.h"
LOG_MODULE_REGISTER(osdp, CONFIG_OSDP_LOG_LEVEL);
struct osdp_device {
struct ring_buf rx_buf;
struct ring_buf tx_buf;
int rx_event_data;
struct k_fifo rx_event_fifo;
uint8_t rx_fbuf[CONFIG_OSDP_UART_BUFFER_LENGTH];
uint8_t tx_fbuf[CONFIG_OSDP_UART_BUFFER_LENGTH];
struct device *dev;
};
static struct osdp osdp_ctx;
static struct osdp_cp osdp_cp_ctx;
static struct osdp_pd osdp_pd_ctx[CONFIG_OSDP_NUM_CONNECTED_PD];
static struct osdp_device osdp_device;
static struct k_thread osdp_refresh_thread;
static K_THREAD_STACK_DEFINE(osdp_thread_stack, CONFIG_OSDP_THREAD_STACK_SIZE);
static void osdp_uart_isr(struct device *dev, void *user_data)
{
uint8_t buf[64];
size_t read, wrote, len;
struct osdp_device *p = user_data;
while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {
if (uart_irq_rx_ready(dev)) {
read = uart_fifo_read(dev, buf, sizeof(buf));
if (read) {
wrote = ring_buf_put(&p->rx_buf, buf, read);
if (wrote < read) {
LOG_ERR("RX: Drop %u", read - wrote);
}
}
}
if (uart_irq_tx_ready(dev)) {
len = ring_buf_get(&p->tx_buf, buf, 1);
if (!len) {
uart_irq_tx_disable(dev);
} else {
wrote = uart_fifo_fill(dev, buf, 1);
}
}
}
/* wake osdp_refresh thread */
k_fifo_put(&p->rx_event_fifo, &p->rx_event_data);
}
static int osdp_uart_receive(void *data, uint8_t *buf, int len)
{
struct osdp_device *p = data;
return (int)ring_buf_get(&p->rx_buf, buf, len);
}
static int osdp_uart_send(void *data, uint8_t *buf, int len)
{
int sent = 0;
struct osdp_device *p = data;
sent = (int)ring_buf_put(&p->tx_buf, buf, len);
uart_irq_tx_enable(p->dev);
return sent;
}
static void osdp_uart_flush(void *data)
{
struct osdp_device *p = data;
ring_buf_reset(&p->tx_buf);
ring_buf_reset(&p->rx_buf);
}
struct osdp *osdp_get_ctx()
{
return &osdp_ctx;
}
static struct osdp *osdp_build_ctx()
{
int i;
struct osdp *ctx;
struct osdp_pd *pd;
ctx = &osdp_ctx;
ctx->cp = &osdp_cp_ctx;
ctx->cp->__parent = ctx;
ctx->cp->num_pd = CONFIG_OSDP_NUM_CONNECTED_PD;
ctx->pd = &osdp_pd_ctx[0];
SET_CURRENT_PD(ctx, 0);
for (i = 0; i < CONFIG_OSDP_NUM_CONNECTED_PD; i++) {
pd = TO_PD(ctx, i);
pd->__parent = ctx;
k_mem_slab_init(&pd->cmd.slab,
pd->cmd.slab_buf, sizeof(struct osdp_cmd),
CONFIG_OSDP_PD_COMMAND_QUEUE_SIZE);
}
return ctx;
}
void osdp_refresh(void *arg1, void *arg2, void *arg3)
{
struct osdp *ctx = osdp_get_ctx();
while (1) {
k_fifo_get(&osdp_device.rx_event_fifo, K_FOREVER);
osdp_update(ctx);
}
}
static int osdp_init(struct device *arg)
{
ARG_UNUSED(arg);
uint8_t c;
struct osdp *ctx;
struct osdp_device *p = &osdp_device;
struct osdp_channel channel = {
.send = osdp_uart_send,
.recv = osdp_uart_receive,
.flush = osdp_uart_flush,
.data = &osdp_device,
};
k_fifo_init(&p->rx_event_fifo);
ring_buf_init(&p->rx_buf, sizeof(p->rx_fbuf), p->rx_fbuf);
ring_buf_init(&p->tx_buf, sizeof(p->tx_fbuf), p->tx_fbuf);
/* init OSDP uart device */
p->dev = device_get_binding(CONFIG_OSDP_UART_DEV_NAME);
if (p->dev == NULL) {
LOG_ERR("Failed to get UART dev binding");
return -1;
}
uart_irq_rx_disable(p->dev);
uart_irq_tx_disable(p->dev);
uart_irq_callback_user_data_set(p->dev, osdp_uart_isr, p);
/* Drain UART fifo */
while (uart_irq_rx_ready(p->dev)) {
uart_fifo_read(p->dev, &c, 1);
}
/* Both TX and RX are interrupt driven */
uart_irq_rx_enable(p->dev);
/* setup OSDP */
ctx = osdp_build_ctx();
if (osdp_setup(ctx, &channel)) {
LOG_ERR("Failed to setup OSDP device!");
return -1;
}
LOG_INF("OSDP init okay!");
/* kick off refresh thread */
k_thread_create(&osdp_refresh_thread, osdp_thread_stack,
CONFIG_OSDP_THREAD_STACK_SIZE, osdp_refresh,
NULL, NULL, NULL, K_PRIO_COOP(2), 0, K_NO_WAIT);
return 0;
}
SYS_INIT(osdp_init, POST_KERNEL, 10);

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <ctype.h>
#include <device.h>
#include <sys/crc.h>
#include <logging/log.h>
#include "osdp_common.h"
LOG_MODULE_DECLARE(osdp, CONFIG_OSDP_LOG_LEVEL);
void osdp_dump(const char *head, uint8_t *buf, int len)
{
LOG_HEXDUMP_DBG(buf, len, head);
}
uint16_t osdp_compute_crc16(const uint8_t *buf, size_t len)
{
return crc16_itu_t(0x1D0F, buf, len);
}
int64_t osdp_millis_now(void)
{
return (int64_t) k_uptime_get();
}
int64_t osdp_millis_since(int64_t last)
{
int64_t tmp = last;
return (int64_t) k_uptime_delta(&tmp);
}
struct osdp_cmd *osdp_cmd_alloc(struct osdp_pd *pd)
{
struct osdp_cmd *cmd = NULL;
if (k_mem_slab_alloc(&pd->cmd.slab, (void **)&cmd, K_MSEC(100))) {
LOG_ERR("Memory allocation time-out");
return NULL;
}
return cmd;
}
void osdp_cmd_free(struct osdp_pd *pd, struct osdp_cmd *cmd)
{
k_mem_slab_free(&pd->cmd.slab, (void **)&cmd);
}

View File

@@ -0,0 +1,442 @@
/*
* Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _OSDP_COMMON_H_
#define _OSDP_COMMON_H_
#include <mgmt/osdp.h>
#include <assert.h>
#define OSDP_RESP_TOUT_MS (200)
#define OSDP_CMD_SLAB_BUF_SIZE \
(sizeof(struct osdp_cmd) * CONFIG_OSDP_PD_COMMAND_QUEUE_SIZE)
#define ISSET_FLAG(p, f) (((p)->flags & (f)) == (f))
#define SET_FLAG(p, f) ((p)->flags |= (f))
#define CLEAR_FLAG(p, f) ((p)->flags &= ~(f))
#define BYTE_0(x) (uint8_t)(((x) >> 0) & 0xFF)
#define BYTE_1(x) (uint8_t)(((x) >> 8) & 0xFF)
#define BYTE_2(x) (uint8_t)(((x) >> 16) & 0xFF)
#define BYTE_3(x) (uint8_t)(((x) >> 24) & 0xFF)
/* casting helpers */
#define TO_OSDP(p) ((struct osdp *)p)
#define TO_CP(p) (((struct osdp *)(p))->cp)
#define TO_PD(p, i) (((struct osdp *)(p))->pd + i)
#define TO_CTX(p) ((struct osdp *)p->__parent)
#define GET_CURRENT_PD(p) (TO_CP(p)->current_pd)
#define SET_CURRENT_PD(p, i) \
do { \
TO_CP(p)->current_pd = TO_PD(p, i); \
TO_CP(p)->pd_offset = i; \
} while (0)
#define PD_MASK(ctx) \
(uint32_t)((1 << (TO_CP(ctx)->num_pd + 1)) - 1)
#define AES_PAD_LEN(x) ((x + 16 - 1) & (~(16 - 1)))
#define NUM_PD(ctx) (TO_CP(ctx)->num_pd)
#define OSDP_COMMAND_DATA_MAX_LEN sizeof(struct osdp_cmd)
/**
* @brief OSDP reserved commands
*/
#define CMD_POLL 0x60
#define CMD_ID 0x61
#define CMD_CAP 0x62
#define CMD_DIAG 0x63
#define CMD_LSTAT 0x64
#define CMD_ISTAT 0x65
#define CMD_OSTAT 0x66
#define CMD_RSTAT 0x67
#define CMD_OUT 0x68
#define CMD_LED 0x69
#define CMD_BUZ 0x6A
#define CMD_TEXT 0x6B
#define CMD_RMODE 0x6C
#define CMD_TDSET 0x6D
#define CMD_COMSET 0x6E
#define CMD_DATA 0x6F
#define CMD_XMIT 0x70
#define CMD_PROMPT 0x71
#define CMD_SPE 0x72
#define CMD_BIOREAD 0x73
#define CMD_BIOMATCH 0x74
#define CMD_KEYSET 0x75
#define CMD_CHLNG 0x76
#define CMD_SCRYPT 0x77
#define CMD_CONT 0x79
#define CMD_ABORT 0x7A
#define CMD_MAXREPLY 0x7B
#define CMD_MFG 0x80
#define CMD_SCDONE 0xA0
#define CMD_XWR 0xA1
/**
* @brief OSDP reserved responses
*/
#define REPLY_ACK 0x40
#define REPLY_NAK 0x41
#define REPLY_PDID 0x45
#define REPLY_PDCAP 0x46
#define REPLY_LSTATR 0x48
#define REPLY_ISTATR 0x49
#define REPLY_OSTATR 0x4A
#define REPLY_RSTATR 0x4B
#define REPLY_RAW 0x50
#define REPLY_FMT 0x51
#define REPLY_PRES 0x52
#define REPLY_KEYPPAD 0x53
#define REPLY_COM 0x54
#define REPLY_SCREP 0x55
#define REPLY_SPER 0x56
#define REPLY_BIOREADR 0x57
#define REPLY_BIOMATCHR 0x58
#define REPLY_CCRYPT 0x76
#define REPLY_RMAC_I 0x78
#define REPLY_MFGREP 0x90
#define REPLY_BUSY 0x79
#define REPLY_XRD 0xB1
/**
* @brief secure block types
*/
#define SCS_11 0x11 /* CP -> PD -- CMD_CHLNG */
#define SCS_12 0x12 /* PD -> CP -- REPLY_CCRYPT */
#define SCS_13 0x13 /* CP -> PD -- CMD_SCRYPT */
#define SCS_14 0x14 /* PD -> CP -- REPLY_RMAC_I */
#define SCS_15 0x15 /* CP -> PD -- packets w MAC w/o ENC */
#define SCS_16 0x16 /* PD -> CP -- packets w MAC w/o ENC */
#define SCS_17 0x17 /* CP -> PD -- packets w MAC w ENC*/
#define SCS_18 0x18 /* PD -> CP -- packets w MAC w ENC*/
/* PD Flags */
#define PD_FLAG_SC_CAPABLE 0x00000001 /* PD secure channel capable */
#define PD_FLAG_TAMPER 0x00000002 /* local tamper status */
#define PD_FLAG_POWER 0x00000004 /* local power status */
#define PD_FLAG_R_TAMPER 0x00000008 /* remote tamper status */
#define PD_FLAG_SKIP_SEQ_CHECK 0x00000040 /* disable seq checks (debug) */
#define PD_FLAG_SC_USE_SCBKD 0x00000080 /* in this SC attempt, use SCBKD */
#define PD_FLAG_SC_ACTIVE 0x00000100 /* secure channel is active */
#define PD_FLAG_PD_MODE 0x80000000 /* device is setup as PD */
enum osdp_pd_nak_code_e {
/**
* @brief Dummy
*/
OSDP_PD_NAK_NONE,
/**
* @brief Message check character(s) error (bad cksum/crc)
*/
OSDP_PD_NAK_MSG_CHK,
/**
* @brief Command length error
*/
OSDP_PD_NAK_CMD_LEN,
/**
* @brief Unknown Command Code Command not implemented by PD
*/
OSDP_PD_NAK_CMD_UNKNOWN,
/**
* @brief Unexpected sequence number detected in the header
*/
OSDP_PD_NAK_SEQ_NUM,
/**
* @brief Unexpected sequence number detected in the header
*/
OSDP_PD_NAK_SC_UNSUP,
/**
* @brief unsupported security block or security conditions not met
*/
OSDP_PD_NAK_SC_COND,
/**
* @brief BIO_TYPE not supported
*/
OSDP_PD_NAK_BIO_TYPE,
/**
* @brief BIO_FORMAT not supported
*/
OSDP_PD_NAK_BIO_FMT,
/**
* @brief Unable to process command record
*/
OSDP_PD_NAK_RECORD,
/**
* @brief Dummy
*/
OSDP_PD_NAK_SENTINEL
};
enum osdp_pd_state_e {
OSDP_PD_STATE_IDLE,
OSDP_PD_STATE_SEND_REPLY,
OSDP_PD_STATE_ERR,
};
enum osdp_pkt_errors_e {
OSDP_ERR_PKT_FMT = -1,
OSDP_ERR_PKT_WAIT = -2,
OSDP_ERR_PKT_SKIP = -3
};
/**
* @brief Various PD capability function codes.
*/
enum osdp_pd_cap_function_code_e {
/**
* @brief Dummy.
*/
OSDP_PD_CAP_UNUSED,
/**
* @brief This function indicates the ability to monitor the status of a
* switch using a two-wire electrical connection between the PD and the
* switch. The on/off position of the switch indicates the state of an
* external device.
*
* The PD may simply resolve all circuit states to an open/closed
* status, or it may implement supervision of the monitoring circuit.
* A supervised circuit is able to indicate circuit fault status in
* addition to open/closed status.
*/
OSDP_PD_CAP_CONTACT_STATUS_MONITORING,
/**
* @brief This function provides a switched output, typically in the
* form of a relay. The Output has two states: active or inactive. The
* Control Panel (CP) can directly set the Output's state, or, if the PD
* supports timed operations, the CP can specify a time period for the
* activation of the Output.
*/
OSDP_PD_CAP_OUTPUT_CONTROL,
/**
* @brief This capability indicates the form of the card data is
* presented to the Control Panel.
*/
OSDP_PD_CAP_CARD_DATA_FORMAT,
/**
* @brief This capability indicates the presence of and type of LEDs.
*/
OSDP_PD_CAP_READER_LED_CONTROL,
/**
* @brief This capability indicates the presence of and type of an
* Audible Annunciator (buzzer or similar tone generator)
*/
OSDP_PD_CAP_READER_AUDIBLE_OUTPUT,
/**
* @brief This capability indicates that the PD supports a text display
* emulating character-based display terminals.
*/
OSDP_PD_CAP_READER_TEXT_OUTPUT,
/**
* @brief This capability indicates that the type of date and time
* awareness or time keeping ability of the PD.
*/
OSDP_PD_CAP_TIME_KEEPING,
/**
* @brief All PDs must be able to support the checksum mode. This
* capability indicates if the PD is capable of supporting CRC mode.
*/
OSDP_PD_CAP_CHECK_CHARACTER_SUPPORT,
/**
* @brief This capability indicates the extent to which the PD supports
* communication security (Secure Channel Communication)
*/
OSDP_PD_CAP_COMMUNICATION_SECURITY,
/**
* @brief This capability indicates the maximum size single message the
* PD can receive.
*/
OSDP_PD_CAP_RECEIVE_BUFFERSIZE,
/**
* @brief This capability indicates the maximum size multi-part message
* which the PD can handle.
*/
OSDP_PD_CAP_LARGEST_COMBINED_MESSAGE_SIZE,
/**
* @brief This capability indicates whether the PD supports the
* transparent mode used for communicating directly with a smart card.
*/
OSDP_PD_CAP_SMART_CARD_SUPPORT,
/**
* @brief This capability indicates the number of credential reader
* devices present. Compliance levels are bit fields to be assigned as
* needed.
*/
OSDP_PD_CAP_READERS,
/**
* @brief This capability indicates the ability of the reader to handle
* biometric input
*/
OSDP_PD_CAP_BIOMETRICS,
/**
* @brief Capability Sentinel
*/
OSDP_PD_CAP_SENTINEL
};
/**
* @brief PD capability structure. Each PD capability has a 3 byte
* representation.
*
* @param function_code One of enum osdp_pd_cap_function_code_e.
* @param compliance_level A function_code dependent number that indicates what
* the PD can do with this capability.
* @param num_items Number of such capability entities in PD.
*/
struct osdp_pd_cap {
uint8_t function_code;
uint8_t compliance_level;
uint8_t num_items;
};
/**
* @brief PD ID information advertised by the PD.
*
* @param version 3-bytes IEEE assigned OUI
* @param model 1-byte Manufacturer's model number
* @param vendor_code 1-Byte Manufacturer's version number
* @param serial_number 4-byte serial number for the PD
* @param firmware_version 3-byte version (major, minor, build)
*/
struct osdp_pd_id {
int version;
int model;
uint32_t vendor_code;
uint32_t serial_number;
uint32_t firmware_version;
};
struct osdp_channel {
/**
* @brief pointer to a block of memory that will be passed to the
* send/receive method. This is optional and can be left empty.
*/
void *data;
/**
* @brief pointer to function that copies received bytes into buffer
* @param data for use by underlying layers. channel_s::data is passed
* @param buf byte array copy incoming data
* @param len sizeof `buf`. Can copy utmost `len` bytes into `buf`
*
* @retval +ve: number of bytes copied on to `bug`. Must be <= `len`
* @retval -ve on errors
*/
int (*recv)(void *data, uint8_t *buf, int maxlen);
/**
* @brief pointer to function that sends byte array into some channel
* @param data for use by underlying layers. channel_s::data is passed
* @param buf byte array to be sent
* @param len number of bytes in `buf`
*
* @retval +ve: number of bytes sent. must be <= `len`
* @retval -ve on errors
*/
int (*send)(void *data, uint8_t *buf, int len);
/**
* @brief pointer to function that drops all bytes in TX/RX fifo
* @param data for use by underlying layers. channel_s::data is passed
*/
void (*flush)(void *data);
};
struct osdp_cmd_queue {
sys_slist_t queue;
struct k_mem_slab slab;
uint8_t slab_buf[OSDP_CMD_SLAB_BUF_SIZE];
};
struct osdp_pd {
void *__parent;
int offset;
uint32_t flags;
/* OSDP specified data */
int baud_rate;
int address;
int seq_number;
struct osdp_pd_cap cap[OSDP_PD_CAP_SENTINEL];
struct osdp_pd_id id;
/* PD state management */
enum osdp_pd_state_e pd_state;
int64_t tstamp;
int64_t sc_tstamp;
int phy_state;
uint8_t rx_buf[CONFIG_OSDP_UART_BUFFER_LENGTH];
int rx_buf_len;
int64_t phy_tstamp;
int cmd_id;
int reply_id;
uint8_t cmd_data[OSDP_COMMAND_DATA_MAX_LEN];
struct osdp_channel channel;
struct osdp_cmd_queue cmd;
};
struct osdp_cp {
void *__parent;
uint32_t flags;
int num_pd;
struct osdp_pd *current_pd; /* current operational pd's pointer */
int pd_offset; /* current pd's offset into ctx->pd */
};
struct osdp {
int magic;
uint32_t flags;
uint8_t sc_master_key[16];
struct osdp_cp *cp;
struct osdp_pd *pd;
};
/* from osdp_phy.c */
int osdp_phy_packet_init(struct osdp_pd *p, uint8_t *buf, int max_len);
int osdp_phy_packet_finalize(struct osdp_pd *p, uint8_t *buf,
int len, int max_len);
int osdp_phy_decode_packet(struct osdp_pd *p, uint8_t *buf, int len);
void osdp_phy_state_reset(struct osdp_pd *pd);
int osdp_phy_packet_get_data_offset(struct osdp_pd *p, const uint8_t *buf);
uint8_t *osdp_phy_packet_get_smb(struct osdp_pd *p, const uint8_t *buf);
/* from osdp_common.c */
int64_t osdp_millis_now(void);
int64_t osdp_millis_since(int64_t last);
void osdp_dump(const char *head, uint8_t *buf, int len);
uint16_t osdp_compute_crc16(const uint8_t *buf, size_t len);
struct osdp_cmd *osdp_cmd_alloc(struct osdp_pd *pd);
void osdp_cmd_free(struct osdp_pd *pd, struct osdp_cmd *cmd);
/* from osdp.c */
struct osdp *osdp_get_ctx();
/* must be implemented by CP or PD */
int osdp_setup(struct osdp *ctx, struct osdp_channel *channel);
void osdp_update(struct osdp *ctx);
#endif /* _OSDP_COMMON_H_ */

View File

@@ -0,0 +1,662 @@
/*
* Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_DECLARE(osdp, CONFIG_OSDP_LOG_LEVEL);
#include <stdlib.h>
#include <string.h>
#include "osdp_common.h"
#define TAG "PD: "
#define CMD_POLL_DATA_LEN 0
#define CMD_LSTAT_DATA_LEN 0
#define CMD_ISTAT_DATA_LEN 0
#define CMD_OSTAT_DATA_LEN 0
#define CMD_RSTAT_DATA_LEN 0
#define CMD_ID_DATA_LEN 1
#define CMD_CAP_DATA_LEN 1
#define CMD_OUT_DATA_LEN 4
#define CMD_LED_DATA_LEN 14
#define CMD_BUZ_DATA_LEN 5
#define CMD_TEXT_DATA_LEN 6 /* variable length command */
#define CMD_COMSET_DATA_LEN 5
#define REPLY_ACK_LEN 1
#define REPLY_PDID_LEN 13
#define REPLY_PDCAP_LEN 1 /* variable length command */
#define REPLY_PDCAP_ENTITY_LEN 3
#define REPLY_LSTATR_LEN 3
#define REPLY_RSTATR_LEN 2
#define REPLY_COM_LEN 6
#define REPLY_NAK_LEN 2
static struct osdp_pd_id osdp_pd_id = {
.version = CONFIG_OSDP_PD_ID_VERSION,
.model = CONFIG_OSDP_PD_ID_MODEL,
.vendor_code = CONFIG_OSDP_PD_ID_VENDOR_CODE,
.serial_number = CONFIG_OSDP_PD_ID_SERIAL_NUMBER,
.firmware_version = CONFIG_OSDP_PD_ID_FIRMWARE_VERSION,
};
static struct osdp_pd_cap osdp_pd_cap[] = {
/* Driver Implicit cababilities */
{
OSDP_PD_CAP_CHECK_CHARACTER_SUPPORT,
1, /* The PD supports the 16-bit CRC-16 mode */
0, /* N/A */
},
/* Configured from Kconfig */
{
OSDP_PD_CAP_CONTACT_STATUS_MONITORING,
CONFIG_OSDP_PD_CAP_CONTACT_STATUS_MONITORING_COMP_LEVEL,
CONFIG_OSDP_PD_CAP_CONTACT_STATUS_MONITORING_NUM_ITEMS,
},
{
OSDP_PD_CAP_OUTPUT_CONTROL,
CONFIG_OSDP_PD_CAP_OUTPUT_CONTROL_COMP_LEVEL,
CONFIG_OSDP_PD_CAP_OUTPUT_CONTROL_NUM_ITEMS,
},
{
OSDP_PD_CAP_READER_LED_CONTROL,
CONFIG_OSDP_PD_CAP_READER_LED_CONTROL_COMP_LEVEL,
CONFIG_OSDP_PD_CAP_READER_LED_CONTROL_NUM_ITEMS,
},
{
OSDP_PD_CAP_READER_AUDIBLE_OUTPUT,
CONFIG_OSDP_PD_CAP_READER_AUDIBLE_OUTPUT_COMP_LEVEL,
CONFIG_OSDP_PD_CAP_READER_AUDIBLE_OUTPUT_NUM_ITEMS,
},
{
OSDP_PD_CAP_READER_TEXT_OUTPUT,
CONFIG_OSDP_PD_CAP_READER_TEXT_OUTPUT_COMP_LEVEL,
CONFIG_OSDP_PD_CAP_READER_TEXT_OUTPUT_NUM_ITEMS,
},
{
OSDP_PD_CAP_CARD_DATA_FORMAT,
CONFIG_OSDP_PD_CAP_CARD_DATA_FORMAT_COMP_LEVEL,
0, /* N/A set to 0 */
},
{
OSDP_PD_CAP_TIME_KEEPING,
CONFIG_OSDP_PD_CAP_TIME_KEEPING_COMP_LEVEL,
0, /* N/A set to 0 */
},
{ -1, 0, 0 } /* Sentinel */
};
static void pd_enqueue_command(struct osdp_pd *pd, struct osdp_cmd *cmd)
{
sys_slist_append(&pd->cmd.queue, &cmd->node);
}
static struct osdp_cmd *pd_get_last_command(struct osdp_pd *pd)
{
return (struct osdp_cmd *)sys_slist_peek_tail(&pd->cmd.queue);
}
static void pd_decode_command(struct osdp_pd *pd, uint8_t *buf, int len)
{
int i, ret = -1, pos = 0;
struct osdp_cmd *cmd;
pd->reply_id = 0;
pd->cmd_id = buf[pos++];
len--;
switch (pd->cmd_id) {
case CMD_POLL:
if (len != CMD_POLL_DATA_LEN) {
break;
}
pd->reply_id = REPLY_ACK;
ret = 0;
break;
case CMD_LSTAT:
if (len != CMD_LSTAT_DATA_LEN) {
break;
}
pd->reply_id = REPLY_LSTATR;
ret = 0;
break;
case CMD_ISTAT:
if (len != CMD_ISTAT_DATA_LEN) {
break;
}
pd->reply_id = REPLY_ISTATR;
ret = 0;
break;
case CMD_OSTAT:
if (len != CMD_OSTAT_DATA_LEN) {
break;
}
pd->reply_id = REPLY_OSTATR;
ret = 0;
break;
case CMD_RSTAT:
if (len != CMD_RSTAT_DATA_LEN) {
break;
}
pd->reply_id = REPLY_RSTATR;
ret = 0;
break;
case CMD_ID:
if (len != CMD_ID_DATA_LEN) {
break;
}
pos++; /* Skip reply type info. */
pd->reply_id = REPLY_PDID;
ret = 0;
break;
case CMD_CAP:
if (len != CMD_CAP_DATA_LEN) {
break;
}
pos++; /* Skip reply type info. */
pd->reply_id = REPLY_PDCAP;
ret = 0;
break;
case CMD_OUT:
if (len != CMD_OUT_DATA_LEN) {
break;
}
cmd = osdp_cmd_alloc(pd);
if (cmd == NULL) {
LOG_ERR(TAG "cmd alloc error");
break;
}
cmd->id = OSDP_CMD_OUTPUT;
cmd->output.output_no = buf[pos++];
cmd->output.control_code = buf[pos++];
cmd->output.tmr_count = buf[pos++];
cmd->output.tmr_count |= buf[pos++] << 8;
pd_enqueue_command(pd, cmd);
pd->reply_id = REPLY_ACK;
ret = 0;
break;
case CMD_LED:
if (len != CMD_LED_DATA_LEN) {
break;
}
cmd = osdp_cmd_alloc(pd);
if (cmd == NULL) {
LOG_ERR(TAG "cmd alloc error");
break;
}
cmd->id = OSDP_CMD_LED;
cmd->led.reader = buf[pos++];
cmd->led.led_number = buf[pos++];
cmd->led.temporary.control_code = buf[pos++];
cmd->led.temporary.on_count = buf[pos++];
cmd->led.temporary.off_count = buf[pos++];
cmd->led.temporary.on_color = buf[pos++];
cmd->led.temporary.off_color = buf[pos++];
cmd->led.temporary.timer = buf[pos++];
cmd->led.temporary.timer |= buf[pos++] << 8;
cmd->led.permanent.control_code = buf[pos++];
cmd->led.permanent.on_count = buf[pos++];
cmd->led.permanent.off_count = buf[pos++];
cmd->led.permanent.on_color = buf[pos++];
cmd->led.permanent.off_color = buf[pos++];
pd_enqueue_command(pd, cmd);
pd->reply_id = REPLY_ACK;
ret = 0;
break;
case CMD_BUZ:
if (len != CMD_BUZ_DATA_LEN) {
break;
}
cmd = osdp_cmd_alloc(pd);
if (cmd == NULL) {
LOG_ERR(TAG "cmd alloc error");
break;
}
cmd->id = OSDP_CMD_BUZZER;
cmd->buzzer.reader = buf[pos++];
cmd->buzzer.tone_code = buf[pos++];
cmd->buzzer.on_count = buf[pos++];
cmd->buzzer.off_count = buf[pos++];
cmd->buzzer.rep_count = buf[pos++];
pd_enqueue_command(pd, cmd);
pd->reply_id = REPLY_ACK;
ret = 0;
break;
case CMD_TEXT:
if (len < CMD_TEXT_DATA_LEN) {
break;
}
cmd = osdp_cmd_alloc(pd);
if (cmd == NULL) {
LOG_ERR(TAG "cmd alloc error");
break;
}
cmd->id = OSDP_CMD_TEXT;
cmd->text.reader = buf[pos++];
cmd->text.cmd = buf[pos++];
cmd->text.temp_time = buf[pos++];
cmd->text.offset_row = buf[pos++];
cmd->text.offset_col = buf[pos++];
cmd->text.length = buf[pos++];
if (cmd->text.length > OSDP_CMD_TEXT_MAX_LEN ||
((len - CMD_TEXT_DATA_LEN) < cmd->text.length) ||
cmd->text.length > OSDP_CMD_TEXT_MAX_LEN) {
osdp_cmd_free(pd, cmd);
break;
}
for (i = 0; i < cmd->text.length; i++) {
cmd->text.data[i] = buf[pos++];
}
pd_enqueue_command(pd, cmd);
pd->reply_id = REPLY_ACK;
ret = 0;
break;
case CMD_COMSET:
if (len != CMD_COMSET_DATA_LEN) {
break;
}
cmd = osdp_cmd_alloc(pd);
if (cmd == NULL) {
LOG_ERR(TAG "cmd alloc error");
break;
}
cmd->id = OSDP_CMD_COMSET;
cmd->comset.addr = buf[pos++];
cmd->comset.baud = buf[pos++];
cmd->comset.baud |= buf[pos++] << 8;
cmd->comset.baud |= buf[pos++] << 16;
cmd->comset.baud |= buf[pos++] << 24;
if (cmd->comset.addr >= 0x7F || (cmd->comset.baud != 9600 &&
cmd->comset.baud != 38400 && cmd->comset.baud != 115200)) {
LOG_ERR("COMSET Failed! command discarded.");
cmd->comset.addr = pd->address;
cmd->comset.baud = pd->baud_rate;
}
pd_enqueue_command(pd, cmd);
pd->reply_id = REPLY_COM;
ret = 0;
break;
default:
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_CMD_UNKNOWN;
ret = 0;
break;
}
if (ret != 0) {
LOG_ERR(TAG "Invalid command structure. CMD: %02x, Len: %d",
pd->cmd_id, len);
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_CMD_LEN;
}
if (pd->cmd_id != CMD_POLL) {
LOG_DBG(TAG "CMD: %02x REPLY: %02x", pd->cmd_id, pd->reply_id);
}
}
/**
* Returns:
* +ve: length of command
* -ve: error
*/
static int pd_build_reply(struct osdp_pd *pd, uint8_t *buf, int max_len)
{
int i, data_off, len = 0, ret = -1;
struct osdp_cmd *cmd;
data_off = osdp_phy_packet_get_data_offset(pd, buf);
buf += data_off;
max_len -= data_off;
if (max_len <= 0) {
LOG_ERR(TAG "Out of buffer space!");
return -1;
}
switch (pd->reply_id) {
case REPLY_ACK:
if (max_len < REPLY_ACK_LEN) {
LOG_ERR(TAG "Out of buffer space!");
break;
}
buf[len++] = pd->reply_id;
ret = 0;
break;
case REPLY_PDID:
if (max_len < REPLY_PDID_LEN) {
LOG_ERR(TAG "Out of buffer space!");
break;
}
buf[len++] = pd->reply_id;
buf[len++] = BYTE_0(pd->id.vendor_code);
buf[len++] = BYTE_1(pd->id.vendor_code);
buf[len++] = BYTE_2(pd->id.vendor_code);
buf[len++] = pd->id.model;
buf[len++] = pd->id.version;
buf[len++] = BYTE_0(pd->id.serial_number);
buf[len++] = BYTE_1(pd->id.serial_number);
buf[len++] = BYTE_2(pd->id.serial_number);
buf[len++] = BYTE_3(pd->id.serial_number);
buf[len++] = BYTE_3(pd->id.firmware_version);
buf[len++] = BYTE_2(pd->id.firmware_version);
buf[len++] = BYTE_1(pd->id.firmware_version);
ret = 0;
break;
case REPLY_PDCAP:
if (max_len < REPLY_PDCAP_LEN) {
LOG_ERR(TAG "Out of buffer space!");
break;
}
buf[len++] = pd->reply_id;
for (i = 0; i < OSDP_PD_CAP_SENTINEL; i++) {
if (pd->cap[i].function_code != i) {
continue;
}
if (max_len < REPLY_PDCAP_ENTITY_LEN) {
LOG_ERR(TAG "Out of buffer space!");
break;
}
buf[len++] = i;
buf[len++] = pd->cap[i].compliance_level;
buf[len++] = pd->cap[i].num_items;
max_len -= REPLY_PDCAP_ENTITY_LEN;
}
ret = 0;
break;
case REPLY_LSTATR:
if (max_len < REPLY_LSTATR_LEN) {
LOG_ERR(TAG "Out of buffer space!");
break;
}
buf[len++] = pd->reply_id;
buf[len++] = ISSET_FLAG(pd, PD_FLAG_TAMPER);
buf[len++] = ISSET_FLAG(pd, PD_FLAG_POWER);
ret = 0;
break;
case REPLY_RSTATR:
if (max_len < REPLY_RSTATR_LEN) {
LOG_ERR(TAG "Out of buffer space!");
break;
}
buf[len++] = pd->reply_id;
buf[len++] = ISSET_FLAG(pd, PD_FLAG_R_TAMPER);
ret = 0;
break;
case REPLY_COM:
if (max_len < REPLY_COM_LEN) {
LOG_ERR(TAG "Out of buffer space!");
break;
}
/**
* If COMSET succeeds, the PD must reply with the old params and
* then switch to the new params from then then on. We have the
* new params in the commands struct that we just enqueued so
* we can peek at tail of command queue and set that to
* pd->addr/pd->baud_rate.
*
* TODO: Persist pd->address and pd->baud_rate via
* subsys/settings
*/
cmd = pd_get_last_command(pd);
if (cmd == NULL || cmd->id != OSDP_CMD_COMSET) {
LOG_ERR(TAG "Failed to fetch queue tail for COMSET");
break;
}
buf[len++] = pd->reply_id;
buf[len++] = cmd->comset.addr;
buf[len++] = BYTE_0(cmd->comset.baud);
buf[len++] = BYTE_1(cmd->comset.baud);
buf[len++] = BYTE_2(cmd->comset.baud);
buf[len++] = BYTE_3(cmd->comset.baud);
pd->address = (int)cmd->comset.addr;
pd->baud_rate = (int)cmd->comset.baud;
LOG_INF("COMSET Succeeded! New PD-Addr: %d; Baud: %d",
pd->address, pd->baud_rate);
ret = 0;
break;
case REPLY_NAK:
if (max_len < REPLY_NAK_LEN) {
LOG_ERR(TAG "Fatal: insufficent space for sending NAK");
return -1;
}
buf[len++] = pd->reply_id;
buf[len++] = pd->cmd_data[0];
ret = 0;
break;
}
if (ret != 0) {
/* catch all errors and report it as a RECORD error to CP */
LOG_ERR(TAG "ReplyID unknown or insufficent space or some other"
"error. Sending NAK");
if (max_len < REPLY_NAK_LEN) {
LOG_ERR(TAG "Fatal: insufficent space for sending NAK");
return -1;
}
buf[0] = REPLY_NAK;
buf[1] = OSDP_PD_NAK_RECORD;
len = 2;
}
return len;
}
/**
* pd_send_reply - blocking send; doesn't handle partials
* Returns:
* 0 - success
* -1 - failure
*/
static int pd_send_reply(struct osdp_pd *pd)
{
int ret, len;
/* init packet buf with header */
len = osdp_phy_packet_init(pd, pd->rx_buf, sizeof(pd->rx_buf));
if (len < 0) {
return -1;
}
/* fill reply data */
ret = pd_build_reply(pd, pd->rx_buf, sizeof(pd->rx_buf));
if (ret <= 0) {
return -1;
}
len += ret;
/* finalize packet */
len = osdp_phy_packet_finalize(pd, pd->rx_buf, len, sizeof(pd->rx_buf));
if (len < 0) {
return -1;
}
ret = pd->channel.send(pd->channel.data, pd->rx_buf, len);
if (IS_ENABLED(CONFIG_OSDP_PACKET_TRACE)) {
if (pd->cmd_id != CMD_POLL) {
osdp_dump("PD sent", pd->rx_buf, len);
}
}
return (ret == len) ? 0 : -1;
}
/**
* pd_receve_packet - received buffer from serial stream handling partials
* Returns:
* 0: success
* 1: no data yet
* -1: fatal errors
* -2: phy layer errors that need to reply with NAK
*/
static int pd_receve_packet(struct osdp_pd *pd)
{
uint8_t *buf;
int rec_bytes, ret, was_empty, max_len;
was_empty = pd->rx_buf_len == 0;
buf = pd->rx_buf + pd->rx_buf_len;
max_len = sizeof(pd->rx_buf) - pd->rx_buf_len;
rec_bytes = pd->channel.recv(pd->channel.data, buf, max_len);
if (rec_bytes <= 0) {
return 1;
}
if (was_empty && rec_bytes > 0) {
/* Start of message */
pd->tstamp = osdp_millis_now();
}
pd->rx_buf_len += rec_bytes;
if (IS_ENABLED(CONFIG_OSDP_PACKET_TRACE)) {
/**
* A crude way of identifying and not printing poll messages
* when CONFIG_OSDP_PACKET_TRACE is enabled. This is an early
* print to catch errors so keeping it simple.
*/
if (pd->rx_buf_len > 8 &&
pd->rx_buf[6] != CMD_POLL && pd->rx_buf[8] != CMD_POLL) {
osdp_dump("PD received", pd->rx_buf, pd->rx_buf_len);
}
}
pd->reply_id = 0; /* reset past reply ID so phy can send NAK */
pd->cmd_data[0] = 0; /* reset past NAK reason */
ret = osdp_phy_decode_packet(pd, pd->rx_buf, pd->rx_buf_len);
if (ret == OSDP_ERR_PKT_FMT) {
if (pd->reply_id != 0) {
return -2; /* Send a NAK */
}
return -1; /* fatal errors */
} else if (ret == OSDP_ERR_PKT_WAIT) {
/* rx_buf_len != pkt->len; wait for more data */
return 1;
} else if (ret == OSDP_ERR_PKT_SKIP) {
/* soft fail - discard this message */
pd->rx_buf_len = 0;
if (pd->channel.flush) {
pd->channel.flush(pd->channel.data);
}
return 1;
}
pd->rx_buf_len = ret;
return 0;
}
void osdp_update(struct osdp *ctx)
{
int ret;
struct osdp_pd *pd = TO_PD(ctx, 0);
switch (pd->pd_state) {
case OSDP_PD_STATE_IDLE:
ret = pd_receve_packet(pd);
if (ret == 1) {
break;
}
if (ret == -1 || (pd->rx_buf_len > 0 &&
osdp_millis_since(pd->tstamp) > OSDP_RESP_TOUT_MS)) {
/**
* When we receive a command from PD after a timeout,
* any established secure channel must be discarded.
*/
LOG_ERR(TAG "receive errors/timeout");
pd->pd_state = OSDP_PD_STATE_ERR;
break;
}
if (ret == 0) {
pd_decode_command(pd, pd->rx_buf, pd->rx_buf_len);
}
pd->pd_state = OSDP_PD_STATE_SEND_REPLY;
/* FALLTHRU */
case OSDP_PD_STATE_SEND_REPLY:
if (pd_send_reply(pd) == -1) {
pd->pd_state = OSDP_PD_STATE_ERR;
break;
}
pd->rx_buf_len = 0;
pd->pd_state = OSDP_PD_STATE_IDLE;
break;
case OSDP_PD_STATE_ERR:
/**
* PD error state is momentary as it doesn't maintain any state
* between commands. We just clean up secure channel status and
* go back to idle state.
*/
CLEAR_FLAG(pd, PD_FLAG_SC_ACTIVE);
pd->rx_buf_len = 0;
if (pd->channel.flush) {
pd->channel.flush(pd->channel.data);
}
pd->pd_state = OSDP_PD_STATE_IDLE;
break;
}
}
static void osdp_pd_set_attributes(struct osdp_pd *pd, struct osdp_pd_cap *cap,
struct osdp_pd_id *id)
{
int fc;
while (cap && ((fc = cap->function_code) > 0)) {
if (fc >= OSDP_PD_CAP_SENTINEL) {
break;
}
pd->cap[fc].function_code = cap->function_code;
pd->cap[fc].compliance_level = cap->compliance_level;
pd->cap[fc].num_items = cap->num_items;
cap++;
}
if (id != NULL) {
memcpy(&pd->id, id, sizeof(struct osdp_pd_id));
}
}
int osdp_setup(struct osdp *ctx, struct osdp_channel *channel)
{
struct osdp_pd *pd;
if (ctx->cp->num_pd != 1) {
return -1;
}
pd = TO_PD(ctx, 0);
pd->seq_number = -1;
pd->address = CONFIG_OSDP_PD_ADDRESS;
pd->baud_rate = CONFIG_OSDP_UART_BAUD_RATE;
memcpy(&pd->channel, channel, sizeof(struct osdp_channel));
osdp_pd_set_attributes(pd, osdp_pd_cap, &osdp_pd_id);
SET_FLAG(pd, PD_FLAG_PD_MODE);
return 0;
}
int osdp_pd_get_cmd(struct osdp_cmd *cmd)
{
sys_snode_t *node;
struct osdp_cmd *c;
struct osdp_pd *pd = TO_PD(osdp_get_ctx(), 0);
node = sys_slist_peek_head(&pd->cmd.queue);
if (node == NULL) {
return -1;
}
sys_slist_remove(&pd->cmd.queue, NULL, node);
c = CONTAINER_OF(node, struct osdp_cmd, node);
memcpy(cmd, c, sizeof(struct osdp_cmd));
osdp_cmd_free(pd, c);
return 0;
}

View File

@@ -0,0 +1,277 @@
/*
* Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_DECLARE(osdp, CONFIG_OSDP_LOG_LEVEL);
#include <string.h>
#include "osdp_common.h"
#define TAG "PHY: "
#define OSDP_PKT_MARK 0xFF
#define OSDP_PKT_SOM 0x53
#define PKT_CONTROL_SQN 0x03
#define PKT_CONTROL_CRC 0x04
#define PKT_CONTROL_SCB 0x08
struct osdp_packet_header {
uint8_t mark;
uint8_t som;
uint8_t pd_address;
uint8_t len_lsb;
uint8_t len_msb;
uint8_t control;
uint8_t data[];
} __packed;
uint8_t osdp_compute_checksum(uint8_t *msg, int length)
{
uint8_t checksum = 0;
int i, whole_checksum;
whole_checksum = 0;
for (i = 0; i < length; i++) {
whole_checksum += msg[i];
checksum = ~(0xff & whole_checksum) + 1;
}
return checksum;
}
static int osdp_phy_get_seq_number(struct osdp_pd *pd, int do_inc)
{
/* pd->seq_num is set to -1 to reset phy cmd state */
if (do_inc) {
pd->seq_number += 1;
if (pd->seq_number > 3) {
pd->seq_number = 1;
}
}
return pd->seq_number & PKT_CONTROL_SQN;
}
int osdp_phy_packet_get_data_offset(struct osdp_pd *pd, const uint8_t *buf)
{
int sb_len = 0;
struct osdp_packet_header *pkt;
ARG_UNUSED(pd);
pkt = (struct osdp_packet_header *)buf;
if (pkt->control & PKT_CONTROL_SCB) {
sb_len = pkt->data[0];
}
return sizeof(struct osdp_packet_header) + sb_len;
}
uint8_t *osdp_phy_packet_get_smb(struct osdp_pd *pd, const uint8_t *buf)
{
struct osdp_packet_header *pkt;
ARG_UNUSED(pd);
pkt = (struct osdp_packet_header *)buf;
if (pkt->control & PKT_CONTROL_SCB) {
return pkt->data;
}
return NULL;
}
int osdp_phy_in_sc_handshake(int is_reply, int id)
{
if (is_reply) {
return (id == REPLY_CCRYPT || id == REPLY_RMAC_I);
} else {
return (id == CMD_CHLNG || id == CMD_SCRYPT);
}
}
int osdp_phy_packet_init(struct osdp_pd *pd, uint8_t *buf, int max_len)
{
int exp_len, pd_mode, sb_len, id;
struct osdp_packet_header *pkt;
pd_mode = ISSET_FLAG(pd, PD_FLAG_PD_MODE);
exp_len = sizeof(struct osdp_packet_header) + 64; /* 64 is estimated */
if (max_len < exp_len) {
LOG_INF(TAG "packet_init: out of space! CMD: %02x", pd->cmd_id);
return OSDP_ERR_PKT_FMT;
}
/* Fill packet header */
pkt = (struct osdp_packet_header *)buf;
pkt->mark = OSDP_PKT_MARK;
pkt->som = OSDP_PKT_SOM;
pkt->pd_address = pd->address & 0x7F; /* Use only the lower 7 bits */
if (pd_mode) {
/* PD must reply with MSB of it's address set */
pkt->pd_address |= 0x80;
id = pd->reply_id;
} else {
id = pd->cmd_id;
}
pkt->control = osdp_phy_get_seq_number(pd, !pd_mode);
pkt->control |= PKT_CONTROL_CRC;
sb_len = 0;
if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) {
pkt->control |= PKT_CONTROL_SCB;
pkt->data[0] = sb_len = 2;
pkt->data[1] = SCS_15;
} else if (osdp_phy_in_sc_handshake(pd_mode, id)) {
pkt->control |= PKT_CONTROL_SCB;
pkt->data[0] = sb_len = 3;
pkt->data[1] = SCS_11;
}
return sizeof(struct osdp_packet_header) + sb_len;
}
int osdp_phy_packet_finalize(struct osdp_pd *pd, uint8_t *buf,
int len, int max_len)
{
uint16_t crc16;
struct osdp_packet_header *pkt;
/* Do a sanity check only as we expect expect header to be prefilled */
if (buf[0] != OSDP_PKT_MARK || buf[1] != OSDP_PKT_SOM) {
LOG_ERR(TAG "packet_finalize: header validation failed! "
"CMD: %02x", pd->cmd_id);
return OSDP_ERR_PKT_FMT;
}
pkt = (struct osdp_packet_header *)buf;
/* len: with 2 byte CRC; without 1 byte mark */
pkt->len_lsb = BYTE_0(len - 1 + 2);
pkt->len_msb = BYTE_1(len - 1 + 2);
/* fill crc16 */
if (len + 2 > max_len) {
goto out_of_space_error;
}
crc16 = osdp_compute_crc16(buf + 1, len - 1); /* excluding mark byte */
buf[len + 0] = BYTE_0(crc16);
buf[len + 1] = BYTE_1(crc16);
len += 2;
return len;
out_of_space_error:
LOG_ERR(TAG "packet_finalize: Out of buffer space! "
"CMD: %02x", pd->cmd_id);
return OSDP_ERR_PKT_FMT;
}
int osdp_phy_decode_packet(struct osdp_pd *pd, uint8_t *buf, int len)
{
uint8_t *data;
uint16_t comp, cur;
int pkt_len, pd_mode, pd_addr, mac_offset;
struct osdp_packet_header *pkt;
pd_mode = ISSET_FLAG(pd, PD_FLAG_PD_MODE);
pkt = (struct osdp_packet_header *)buf;
/* wait till we have the header */
if ((unsigned long)len < sizeof(struct osdp_packet_header)) {
/* incomplete data */
return OSDP_ERR_PKT_WAIT;
}
/* validate packet header */
if (pkt->mark != OSDP_PKT_MARK || pkt->som != OSDP_PKT_SOM) {
LOG_ERR(TAG "invalid MARK/SOM");
return OSDP_ERR_PKT_FMT;
}
if (!pd_mode && !(pkt->pd_address & 0x80)) {
LOG_ERR(TAG "reply without MSB set 0x%02x", pkt->pd_address);
return OSDP_ERR_PKT_FMT;
}
/* validate packet length */
pkt_len = (pkt->len_msb << 8) | pkt->len_lsb;
if (pkt_len != len - 1) {
/* wait for more data? */
return OSDP_ERR_PKT_WAIT;
}
/* validate PD address */
pd_addr = pkt->pd_address & 0x7F;
if (pd_addr != pd->address && pd_addr != 0x7F) {
/* not addressed to us and was not broadcasted */
if (!pd_mode) {
LOG_ERR(TAG "invalid pd address %d", pd_addr);
return OSDP_ERR_PKT_FMT;
}
LOG_DBG(TAG "cmd for PD[%d] discarded", pd_addr);
return OSDP_ERR_PKT_SKIP;
}
/* validate sequence number */
cur = pkt->control & PKT_CONTROL_SQN;
if (pd_mode && cur == 0) {
/**
* CP is trying to restart communication by sending a 0. The
* current PD implementation does not hold any state between
* commands so we can just set seq_number to -1 (so it gets
* incremented to 0 with a call to phy_get_seq_number()) and
* invalidate any established secure channels.
*/
pd->seq_number = -1;
CLEAR_FLAG(pd, PD_FLAG_SC_ACTIVE);
}
if (pd_mode && cur == pd->seq_number) {
/**
* TODO: PD must resend the last response if CP send the same
* sequence number again.
*/
LOG_ERR(TAG "seq-repeat reply-resend feature not supported!");
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_SEQ_NUM;
return OSDP_ERR_PKT_FMT;
}
comp = osdp_phy_get_seq_number(pd, pd_mode);
if (comp != cur && !ISSET_FLAG(pd, PD_FLAG_SKIP_SEQ_CHECK)) {
LOG_ERR(TAG "packet seq mismatch %d/%d", comp, cur);
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_SEQ_NUM;
return OSDP_ERR_PKT_FMT;
}
len -= sizeof(struct osdp_packet_header); /* consume header */
/* validate CRC/checksum */
if (pkt->control & PKT_CONTROL_CRC) {
cur = (buf[pkt_len] << 8) | buf[pkt_len - 1];
comp = osdp_compute_crc16(buf + 1, pkt_len - 2);
if (comp != cur) {
LOG_ERR(TAG "invalid crc 0x%04x/0x%04x", comp, cur);
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_MSG_CHK;
return OSDP_ERR_PKT_FMT;
}
mac_offset = pkt_len - 4 - 2;
len -= 2; /* consume CRC */
} else {
comp = osdp_compute_checksum(buf + 1, pkt_len - 1);
if (comp != buf[len - 1]) {
LOG_ERR(TAG "invalid checksum %02x/%02x", comp, cur);
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_MSG_CHK;
return OSDP_ERR_PKT_FMT;
}
mac_offset = pkt_len - 4 - 1;
len -= 1; /* consume checksum */
}
data = pkt->data;
memmove(buf, data, len);
return len;
}
void osdp_phy_state_reset(struct osdp_pd *pd)
{
pd->seq_number = -1;
pd->rx_buf_len = 0;
}