blob: ef3f50a761d8366cd462f9b45c0f94add8b88af2 [file] [log] [blame]
/*
* Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/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);
#ifdef CONFIG_OSDP_SC_ENABLED
uint8_t *data;
int i, is_cmd, data_len;
if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE) &&
pkt->control & PKT_CONTROL_SCB &&
pkt->data[1] >= SCS_15) {
is_cmd = !ISSET_FLAG(pd, PD_FLAG_PD_MODE);
if (pkt->data[1] == SCS_17 || pkt->data[1] == SCS_18) {
/**
* Only the data portion of message (after id byte)
* is encrypted. While (en/de)crypting, we must skip
* header, security block, and cmd/reply ID byte.
*
* Note: if cmd/reply has no data, we must set type to
* SCS_15/SCS_16 and send them.
*/
data = pkt->data + pkt->data[0] + 1;
data_len = len - (sizeof(struct osdp_packet_header)
+ pkt->data[0] + 1);
len -= data_len;
/**
* check if the passed buffer can hold the encrypted
* data where length may be rounded up to the nearest
* 16 byte block bondary.
*/
if (AES_PAD_LEN(data_len + 1) > max_len) {
/* data_len + 1 for OSDP_SC_EOM_MARKER */
goto out_of_space_error;
}
len += osdp_encrypt_data(pd, is_cmd, data, data_len);
}
/* len: with 4bytes MAC; with 2 byte CRC; without 1 byte mark */
if (len + 4 > max_len) {
goto out_of_space_error;
}
/* len: without 1 mark byte; with 2 byte CRC; with 4 byte MAC */
pkt->len_lsb = BYTE_0(len - 1 + 2 + 4);
pkt->len_msb = BYTE_1(len - 1 + 2 + 4);
/* compute and extend the buf with 4 MAC bytes */
osdp_compute_mac(pd, is_cmd, buf + 1, len - 1);
data = is_cmd ? pd->sc.c_mac : pd->sc.r_mac;
for (i = 0; i < 4; i++) {
buf[len + i] = data[i];
}
len += 4;
}
#endif /* CONFIG_OSDP_SC_ENABLED */
/* 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;
#ifdef CONFIG_OSDP_SC_ENABLED
uint8_t *mac;
int is_cmd;
if (pkt->control & PKT_CONTROL_SCB) {
if (pd_mode && !ISSET_FLAG(pd, PD_FLAG_SC_CAPABLE)) {
LOG_ERR(TAG "PD is not SC capable");
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_SC_UNSUP;
return OSDP_ERR_PKT_FMT;
}
if (pkt->data[1] < SCS_11 || pkt->data[1] > SCS_18) {
LOG_ERR(TAG "invalid SB Type");
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_SC_COND;
return OSDP_ERR_PKT_FMT;
}
if (pkt->data[1] == SCS_11 || pkt->data[1] == SCS_13) {
/**
* CP signals PD to use SCBKD by setting SB data byte
* to 0. In CP, PD_FLAG_SC_USE_SCBKD comes from FSM; on
* PD we extract it from the command itself. But this
* usage of SCBKD is allowed only when the PD is in
* install mode (indicated by PD_FLAG_INSTALL_MODE).
*/
if (ISSET_FLAG(pd, PD_FLAG_INSTALL_MODE) &&
pkt->data[2] == 0) {
SET_FLAG(pd, PD_FLAG_SC_USE_SCBKD);
}
}
data = pkt->data + pkt->data[0];
len -= pkt->data[0]; /* consume security block */
} else {
if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) {
LOG_ERR(TAG "Received plain-text message in SC");
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_SC_COND;
return OSDP_ERR_PKT_FMT;
}
}
if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE) &&
pkt->control & PKT_CONTROL_SCB &&
pkt->data[1] >= SCS_15) {
/* validate MAC */
is_cmd = ISSET_FLAG(pd, PD_FLAG_PD_MODE);
osdp_compute_mac(pd, is_cmd, buf + 1, mac_offset);
mac = is_cmd ? pd->sc.c_mac : pd->sc.r_mac;
if (memcmp(buf + 1 + mac_offset, mac, 4) != 0) {
LOG_ERR(TAG "invalid MAC");
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_SC_COND;
return OSDP_ERR_PKT_FMT;
}
len -= 4; /* consume MAC */
/* decrypt data block */
if (pkt->data[1] == SCS_17 || pkt->data[1] == SCS_18) {
/**
* Only the data portion of message (after id byte)
* is encrypted. While (en/de)crypting, we must skip
* header (6), security block (2) and cmd/reply id (1)
* bytes if cmd/reply has no data, use SCS_15/SCS_16.
*
* At this point, the header and security block is
* already consumed. So we can just skip the cmd/reply
* ID (data[0]) when calling osdp_decrypt_data().
*/
len = osdp_decrypt_data(pd, is_cmd, data + 1, len - 1);
if (len <= 0) {
LOG_ERR(TAG "failed at decrypt");
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_SC_COND;
return OSDP_ERR_PKT_FMT;
}
len += 1; /* put back cmd/reply ID */
}
}
#endif /* CONFIG_OSDP_SC_ENABLED */
memmove(buf, data, len);
return len;
}
void osdp_phy_state_reset(struct osdp_pd *pd)
{
#ifdef CONFIG_OSDP_MODE_CP
pd->phy_state = 0;
#endif
pd->seq_number = -1;
pd->rx_buf_len = 0;
}