| /* |
| * Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "modlog/modlog.h" |
| |
| #include "osdp/osdp_common.h" |
| |
| #if MYNEWT_VAL(OSDP_MODE_CP) /* compile flag based on mode */ |
| |
| #define TAG "CP: " |
| |
| #define OSDP_PD_POLL_TIMEOUT_MS (1000 / MYNEWT_VAL(OSDP_PD_POLL_RATE)) |
| #define OSDP_CMD_RETRY_WAIT_MS (MYNEWT_VAL(OSDP_CMD_RETRY_WAIT_SEC) * 1000) |
| #define OSDP_PD_SC_RETRY_MS (MYNEWT_VAL(OSDP_SC_RETRY_WAIT_SEC) * 1000) |
| #define OSDP_ONLINE_RETRY_WAIT_MAX_MS (MYNEWT_VAL(OSDP_ONLINE_RETRY_WAIT_MAX_SEC) * 1000) |
| |
| #define CMD_POLL_LEN 1 |
| #define CMD_LSTAT_LEN 1 |
| #define CMD_ISTAT_LEN 1 |
| #define CMD_OSTAT_LEN 1 |
| #define CMD_RSTAT_LEN 1 |
| #define CMD_ID_LEN 2 |
| #define CMD_CAP_LEN 2 |
| #define CMD_DIAG_LEN 2 |
| #define CMD_OUT_LEN 5 |
| #define CMD_LED_LEN 15 |
| #define CMD_BUZ_LEN 6 |
| #define CMD_TEXT_LEN 7 /* variable length command */ |
| #define CMD_COMSET_LEN 6 |
| #define CMD_MFG_LEN 4 /* variable length command */ |
| #define CMD_KEYSET_LEN 19 |
| #define CMD_CHLNG_LEN 9 |
| #define CMD_SCRYPT_LEN 17 |
| |
| #define REPLY_ACK_DATA_LEN 0 |
| #define REPLY_PDID_DATA_LEN 12 |
| #define REPLY_PDCAP_ENTITY_LEN 3 |
| #define REPLY_LSTATR_DATA_LEN 2 |
| #define REPLY_RSTATR_DATA_LEN 1 |
| #define REPLY_COM_DATA_LEN 5 |
| #define REPLY_NAK_DATA_LEN 1 |
| #define REPLY_MFGREP_LEN 4 /* variable length command */ |
| #define REPLY_CCRYPT_DATA_LEN 32 |
| #define REPLY_RMAC_I_DATA_LEN 16 |
| #define REPLY_KEYPPAD_DATA_LEN 2 /* variable length command */ |
| #define REPLY_RAW_DATA_LEN 4 /* variable length command */ |
| #define REPLY_FMT_DATA_LEN 3 /* variable length command */ |
| #define REPLY_BUSY_DATA_LEN 0 |
| |
| #define OSDP_CP_ERR_NONE 0 |
| #define OSDP_CP_ERR_GENERIC -1 |
| #define OSDP_CP_ERR_EMPTY_Q -2 |
| #define OSDP_CP_ERR_NO_DATA 1 |
| #define OSDP_CP_ERR_RETRY_CMD 2 |
| #define OSDP_CP_ERR_CAN_YIELD 3 |
| #define OSDP_CP_ERR_INPROG 4 |
| |
| #define POOL_NAME_COMMON "cp_cmd_pool" |
| |
| /* Enough to hold POOL_NAME_COMMON + digits in 16bit number */ |
| static char pool_names[MYNEWT_VAL(OSDP_PD_COMMAND_QUEUE_SIZE)][sizeof(POOL_NAME_COMMON) + U16_STR_SZ - 1]; |
| |
| static int |
| cp_cmd_queue_init(struct osdp_pd *pd, uint16_t num) |
| { |
| int rc; |
| char buf[U16_STR_SZ] = {0}; |
| |
| char *num_ptr = u16_to_str(num, buf); |
| memcpy(pool_names[num], POOL_NAME_COMMON, sizeof(POOL_NAME_COMMON)); |
| strcat(pool_names[num], num_ptr); |
| |
| rc = os_mempool_init(&pd->cmd.pool, |
| MYNEWT_VAL(OSDP_PD_COMMAND_QUEUE_SIZE), |
| sizeof(struct cp_cmd_node), |
| pd->cmd.pool_buf, pool_names[num]); |
| |
| if (rc != OS_OK) { |
| OSDP_LOG_ERROR("osdp: cp: Failed to initialize command pool\n"); |
| return rc; |
| } |
| |
| pd->cmd.queue.tqh_first = NULL; |
| pd->cmd.queue.tqh_last = &pd->cmd.queue.tqh_first; |
| |
| os_mutex_init(&pd->lock); |
| |
| return rc; |
| } |
| |
| static struct osdp_cmd * |
| cp_cmd_alloc(struct osdp_pd *pd) |
| { |
| struct cp_cmd_node *cmd = NULL; |
| |
| cmd = os_memblock_get(&pd->cmd.pool); |
| if (cmd == NULL) { |
| OSDP_LOG_ERROR("osdp: cp: Command pool allocation failed\n"); |
| return NULL; |
| } |
| |
| return &cmd->object; |
| } |
| |
| static void |
| cp_cmd_free(struct osdp_pd *pd, struct osdp_cmd *cmd) |
| { |
| struct cp_cmd_node *node; |
| |
| node = CONTAINER_OF(cmd, struct cp_cmd_node, object); |
| os_memblock_put(&pd->cmd.pool, node); |
| } |
| |
| static void |
| cp_cmd_enqueue(struct osdp_pd *pd, struct osdp_cmd *cmd) |
| { |
| struct cp_cmd_node *node; |
| |
| node = CONTAINER_OF(cmd, struct cp_cmd_node, object); |
| TAILQ_INSERT_HEAD(&pd->cmd.queue, node, cp_node); |
| } |
| |
| static int |
| cp_cmd_dequeue(struct osdp_pd *pd, struct osdp_cmd **cmd) |
| { |
| struct cp_cmd_node *node; |
| |
| node = TAILQ_LAST(&pd->cmd.queue, queue); |
| if (node == NULL) { |
| return OSDP_CP_ERR_EMPTY_Q; |
| } |
| TAILQ_REMOVE(&pd->cmd.queue, node, cp_node); |
| |
| *cmd = &node->object; |
| return 0; |
| } |
| |
| static void |
| cp_flush_command_queue(struct osdp_pd *pd) |
| { |
| struct osdp_cmd *cmd; |
| |
| while (cp_cmd_dequeue(pd, &cmd) == 0) { |
| cp_cmd_free(pd, cmd); |
| } |
| } |
| |
| static void |
| cp_cmd_queue_del(struct osdp_pd *pd) |
| { |
| /* Empty the queue and put back blocks */ |
| cp_flush_command_queue(pd); |
| |
| os_mempool_clear(&pd->cmd.pool); |
| os_mempool_unregister(&pd->cmd.pool); |
| } |
| |
| static int |
| cp_cmd_get(struct osdp_pd *pd, struct osdp_cmd **cmd, int *ret) |
| { |
| int rc = 0; |
| |
| rc = osdp_device_lock(&pd->lock); |
| if (rc) { |
| *ret = OSDP_CP_ERR_NONE; /* no error for now, retry later */ |
| return rc; |
| } |
| |
| rc = cp_cmd_dequeue(pd, cmd); |
| if (rc) { |
| *ret = OSDP_CP_ERR_NONE; /* command queue is empty */ |
| goto err; |
| } |
| |
| pd->cmd_id = (*cmd)->id; |
| memcpy(pd->ephemeral_data, *cmd, sizeof(struct osdp_cmd)); |
| cp_cmd_free(pd, *cmd); |
| |
| err: |
| osdp_device_unlock(&pd->lock); |
| return rc; |
| } |
| |
| static int |
| cp_cmd_put(struct osdp_pd *pd, struct osdp_cmd *in, int cmd_id) |
| { |
| int rc = 0; |
| struct osdp_cmd *cmd; |
| |
| rc = osdp_device_lock(&pd->lock); |
| if (rc) { |
| return rc; |
| } |
| |
| cmd = cp_cmd_alloc(pd); |
| if (cmd == NULL) { |
| rc = OS_ENOMEM; |
| goto err; |
| } |
| |
| memcpy(cmd, in, sizeof(struct osdp_cmd)); |
| cmd->id = cmd_id; /* translate to internal */ |
| cp_cmd_enqueue(pd, cmd); |
| |
| err: |
| osdp_device_unlock(&pd->lock); |
| return rc; |
| } |
| |
| |
| static int |
| cp_channel_acquire(struct osdp_pd *pd, int *owner) |
| { |
| int i; |
| struct osdp *ctx = TO_CTX(pd); |
| |
| if (ctx->cp->channel_lock[pd->offset] == pd->channel.id) { |
| return 0; /* already acquired! by current PD */ |
| } |
| assert(ctx->cp->channel_lock[pd->offset] == 0); |
| for (i = 0; i < NUM_PD(ctx); i++) { |
| if (ctx->cp->channel_lock[i] == pd->channel.id) { |
| /* some other PD has locked this channel */ |
| if (owner != NULL) { |
| *owner = i; |
| } |
| return -1; |
| } |
| } |
| ctx->cp->channel_lock[pd->offset] = pd->channel.id; |
| |
| return 0; |
| } |
| |
| static int |
| cp_channel_release(struct osdp_pd *pd) |
| { |
| struct osdp *ctx = TO_CTX(pd); |
| |
| if (ctx->cp->channel_lock[pd->offset] != pd->channel.id) { |
| OSDP_LOG_ERROR("osdp: cp: Attempt to release another PD's channel lock\n"); |
| return -1; |
| } |
| ctx->cp->channel_lock[pd->offset] = 0; |
| |
| return 0; |
| } |
| |
| /** |
| * Returns: |
| * +ve: length of command |
| * -ve: error |
| */ |
| static int |
| cp_build_command(struct osdp_pd *pd, uint8_t *buf, int max_len) |
| { |
| uint8_t *smb; |
| struct osdp_cmd *cmd = NULL; |
| int data_off, i, ret = -1, len = 0; |
| |
| data_off = osdp_phy_packet_get_data_offset(pd, buf); |
| smb = osdp_phy_packet_get_smb(pd, buf); |
| |
| buf += data_off; |
| max_len -= data_off; |
| |
| #define ASSERT_BUF_LEN(need) \ |
| if (max_len < need) { \ |
| OSDP_LOG_ERROR("osdp: cp: OOM at build CMD(%02x) - have:%d, need:%d\n", \ |
| pd->cmd_id, max_len, need); \ |
| return OSDP_CP_ERR_GENERIC; \ |
| } |
| |
| switch (pd->cmd_id) { |
| case CMD_POLL: |
| ASSERT_BUF_LEN(CMD_POLL_LEN); |
| buf[len++] = pd->cmd_id; |
| ret = 0; |
| break; |
| case CMD_LSTAT: |
| ASSERT_BUF_LEN(CMD_LSTAT_LEN); |
| buf[len++] = pd->cmd_id; |
| ret = 0; |
| break; |
| case CMD_ISTAT: |
| ASSERT_BUF_LEN(CMD_ISTAT_LEN); |
| buf[len++] = pd->cmd_id; |
| ret = 0; |
| break; |
| case CMD_OSTAT: |
| ASSERT_BUF_LEN(CMD_OSTAT_LEN); |
| buf[len++] = pd->cmd_id; |
| ret = 0; |
| break; |
| case CMD_RSTAT: |
| ASSERT_BUF_LEN(CMD_RSTAT_LEN); |
| buf[len++] = pd->cmd_id; |
| ret = 0; |
| break; |
| case CMD_ID: |
| ASSERT_BUF_LEN(CMD_ID_LEN); |
| buf[len++] = pd->cmd_id; |
| buf[len++] = 0x00; |
| ret = 0; |
| break; |
| case CMD_CAP: |
| ASSERT_BUF_LEN(CMD_CAP_LEN); |
| buf[len++] = pd->cmd_id; |
| buf[len++] = 0x00; |
| ret = 0; |
| break; |
| case CMD_DIAG: |
| ASSERT_BUF_LEN(CMD_DIAG_LEN); |
| buf[len++] = pd->cmd_id; |
| buf[len++] = 0x00; |
| ret = 0; |
| break; |
| case CMD_OUT: |
| ASSERT_BUF_LEN(CMD_OUT_LEN); |
| cmd = (struct osdp_cmd *)pd->ephemeral_data; |
| buf[len++] = pd->cmd_id; |
| buf[len++] = cmd->output.output_no; |
| buf[len++] = cmd->output.control_code; |
| buf[len++] = BYTE_0(cmd->output.timer_count); |
| buf[len++] = BYTE_1(cmd->output.timer_count); |
| ret = 0; |
| break; |
| case CMD_LED: |
| ASSERT_BUF_LEN(CMD_LED_LEN); |
| cmd = (struct osdp_cmd *)pd->ephemeral_data; |
| buf[len++] = pd->cmd_id; |
| buf[len++] = cmd->led.reader; |
| buf[len++] = cmd->led.led_number; |
| |
| buf[len++] = cmd->led.temporary.control_code; |
| buf[len++] = cmd->led.temporary.on_count; |
| buf[len++] = cmd->led.temporary.off_count; |
| buf[len++] = cmd->led.temporary.on_color; |
| buf[len++] = cmd->led.temporary.off_color; |
| buf[len++] = BYTE_0(cmd->led.temporary.timer_count); |
| buf[len++] = BYTE_1(cmd->led.temporary.timer_count); |
| |
| buf[len++] = cmd->led.permanent.control_code; |
| buf[len++] = cmd->led.permanent.on_count; |
| buf[len++] = cmd->led.permanent.off_count; |
| buf[len++] = cmd->led.permanent.on_color; |
| buf[len++] = cmd->led.permanent.off_color; |
| ret = 0; |
| break; |
| case CMD_BUZ: |
| ASSERT_BUF_LEN(CMD_BUZ_LEN); |
| cmd = (struct osdp_cmd *)pd->ephemeral_data; |
| buf[len++] = pd->cmd_id; |
| buf[len++] = cmd->buzzer.reader; |
| buf[len++] = cmd->buzzer.control_code; |
| buf[len++] = cmd->buzzer.on_count; |
| buf[len++] = cmd->buzzer.off_count; |
| buf[len++] = cmd->buzzer.rep_count; |
| ret = 0; |
| break; |
| case CMD_TEXT: |
| cmd = (struct osdp_cmd *)pd->ephemeral_data; |
| ASSERT_BUF_LEN(CMD_TEXT_LEN + cmd->text.length); |
| buf[len++] = pd->cmd_id; |
| buf[len++] = cmd->text.reader; |
| buf[len++] = cmd->text.control_code; |
| buf[len++] = cmd->text.temp_time; |
| buf[len++] = cmd->text.offset_row; |
| buf[len++] = cmd->text.offset_col; |
| buf[len++] = cmd->text.length; |
| for (i = 0; i < cmd->text.length; i++) { |
| buf[len++] = cmd->text.data[i]; |
| } |
| ret = 0; |
| break; |
| case CMD_COMSET: |
| ASSERT_BUF_LEN(CMD_COMSET_LEN); |
| cmd = (struct osdp_cmd *)pd->ephemeral_data; |
| buf[len++] = pd->cmd_id; |
| buf[len++] = cmd->comset.address; |
| buf[len++] = BYTE_0(cmd->comset.baud_rate); |
| buf[len++] = BYTE_1(cmd->comset.baud_rate); |
| buf[len++] = BYTE_2(cmd->comset.baud_rate); |
| buf[len++] = BYTE_3(cmd->comset.baud_rate); |
| ret = 0; |
| break; |
| case CMD_MFG: |
| cmd = (struct osdp_cmd *)pd->ephemeral_data; |
| ASSERT_BUF_LEN(CMD_MFG_LEN + cmd->mfg.length); |
| buf[len++] = pd->cmd_id; |
| buf[len++] = BYTE_0(cmd->mfg.vendor_code); |
| buf[len++] = BYTE_1(cmd->mfg.vendor_code); |
| buf[len++] = BYTE_2(cmd->mfg.vendor_code); |
| buf[len++] = cmd->mfg.command; |
| for (i = 0; i < cmd->mfg.length; i++) { |
| buf[len++] = cmd->mfg.data[i]; |
| } |
| ret = 0; |
| break; |
| case CMD_KEYSET: |
| if (!ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) { |
| OSDP_LOG_ERROR("osdp: cp: Can not perform a KEYSET without SC!\n"); |
| return -1; |
| } |
| cmd = (struct osdp_cmd *)pd->ephemeral_data; |
| ASSERT_BUF_LEN(CMD_KEYSET_LEN); |
| buf[len++] = pd->cmd_id; |
| buf[len++] = 1; /* key type (1: SCBK) */ |
| buf[len++] = 16; /* key length in bytes */ |
| osdp_compute_scbk(pd, cmd->keyset.data, buf + len); |
| len += 16; |
| ret = 0; |
| break; |
| case CMD_CHLNG: |
| ASSERT_BUF_LEN(CMD_CHLNG_LEN); |
| if (smb == NULL) { |
| break; |
| } |
| smb[0] = 3; /* length */ |
| smb[1] = SCS_11; /* type */ |
| smb[2] = ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD) ? 0 : 1; |
| buf[len++] = pd->cmd_id; |
| for (i = 0; i < 8; i++) { |
| buf[len++] = pd->sc.cp_random[i]; |
| } |
| ret = 0; |
| break; |
| case CMD_SCRYPT: |
| ASSERT_BUF_LEN(CMD_SCRYPT_LEN); |
| if (smb == NULL) { |
| break; |
| } |
| osdp_compute_cp_cryptogram(pd); |
| smb[0] = 3; /* length */ |
| smb[1] = SCS_13; /* type */ |
| smb[2] = ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD) ? 0 : 1; |
| buf[len++] = pd->cmd_id; |
| for (i = 0; i < 16; i++) { |
| buf[len++] = pd->sc.cp_cryptogram[i]; |
| } |
| ret = 0; |
| break; |
| default: |
| OSDP_LOG_ERROR("osdp: cp: Unknown/Unsupported CMD(%02x)\n", pd->cmd_id); |
| return OSDP_CP_ERR_GENERIC; |
| } |
| |
| if (smb && (smb[1] > SCS_14) && ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) { |
| /** |
| * When SC active and current cmd is not a handshake (<= SCS_14) |
| * then we must set SCS type to 17 if this message has data |
| * bytes and 15 otherwise. |
| */ |
| smb[0] = 2; |
| smb[1] = (len > 1) ? SCS_17 : SCS_15; |
| } |
| |
| if (ret < 0) { |
| OSDP_LOG_ERROR("osdp: cp: Unable to build CMD(%02x)\n", pd->cmd_id); |
| return OSDP_CP_ERR_GENERIC; |
| } |
| |
| return len; |
| } |
| |
| static int |
| cp_decode_response(struct osdp_pd *pd, uint8_t *buf, int len) |
| { |
| uint32_t temp32; |
| struct osdp_cp *cp = TO_CTX(pd)->cp; |
| int i, ret = OSDP_CP_ERR_GENERIC, pos = 0, t1, t2; |
| struct osdp_event event; |
| |
| pd->reply_id = buf[pos++]; |
| len--; /* consume reply id from the head */ |
| |
| #define ASSERT_LENGTH(got, exp) \ |
| if (got != exp) { \ |
| OSDP_LOG_ERROR("osdp: cp: REPLY(%02x) length error! Got:%d, Exp:%d\n", \ |
| pd->reply_id, got, exp); \ |
| return OSDP_CP_ERR_GENERIC; \ |
| } |
| |
| switch (pd->reply_id) { |
| case REPLY_ACK: |
| ASSERT_LENGTH(len, REPLY_ACK_DATA_LEN); |
| ret = OSDP_CP_ERR_NONE; |
| break; |
| case REPLY_NAK: |
| ASSERT_LENGTH(len, REPLY_NAK_DATA_LEN); |
| OSDP_LOG_WARN("osdp: cp: PD replied with NAK(%d) for CMD(%02x)", |
| buf[pos], pd->cmd_id); |
| ret = OSDP_CP_ERR_NONE; |
| break; |
| case REPLY_PDID: |
| ASSERT_LENGTH(len, REPLY_PDID_DATA_LEN); |
| pd->id.vendor_code = buf[pos++]; |
| pd->id.vendor_code |= buf[pos++] << 8; |
| pd->id.vendor_code |= buf[pos++] << 16; |
| |
| pd->id.model = buf[pos++]; |
| pd->id.version = buf[pos++]; |
| |
| pd->id.serial_number = buf[pos++]; |
| pd->id.serial_number |= buf[pos++] << 8; |
| pd->id.serial_number |= buf[pos++] << 16; |
| pd->id.serial_number |= buf[pos++] << 24; |
| |
| pd->id.firmware_version = buf[pos++] << 16; |
| pd->id.firmware_version |= buf[pos++] << 8; |
| pd->id.firmware_version |= buf[pos++]; |
| ret = OSDP_CP_ERR_NONE; |
| break; |
| case REPLY_PDCAP: |
| if ((len % REPLY_PDCAP_ENTITY_LEN) != 0) { |
| OSDP_LOG_ERROR("osdp: cp: PDCAP response length is not a multiple of 3", |
| buf[pos], pd->cmd_id); |
| return OSDP_CP_ERR_GENERIC; |
| } |
| while (pos < len) { |
| t1 = buf[pos++]; /* func_code */ |
| if (t1 > OSDP_PD_CAP_SENTINEL) { |
| break; |
| } |
| pd->cap[t1].function_code = t1; |
| pd->cap[t1].compliance_level = buf[pos++]; |
| pd->cap[t1].num_items = buf[pos++]; |
| } |
| /* post-capabilities hooks */ |
| t2 = OSDP_PD_CAP_COMMUNICATION_SECURITY; |
| if (pd->cap[t2].compliance_level & 0x01) { |
| SET_FLAG(pd, PD_FLAG_SC_CAPABLE); |
| } else { |
| CLEAR_FLAG(pd, PD_FLAG_SC_CAPABLE); |
| } |
| ret = OSDP_CP_ERR_NONE; |
| break; |
| case REPLY_LSTATR: |
| ASSERT_LENGTH(len, REPLY_LSTATR_DATA_LEN); |
| if (buf[pos++]) { |
| SET_FLAG(pd, PD_FLAG_TAMPER); |
| } else { |
| CLEAR_FLAG(pd, PD_FLAG_TAMPER); |
| } |
| if (buf[pos++]) { |
| SET_FLAG(pd, PD_FLAG_POWER); |
| } else { |
| CLEAR_FLAG(pd, PD_FLAG_POWER); |
| } |
| ret = OSDP_CP_ERR_NONE; |
| break; |
| case REPLY_RSTATR: |
| ASSERT_LENGTH(len, REPLY_RSTATR_DATA_LEN); |
| if (buf[pos++]) { |
| SET_FLAG(pd, PD_FLAG_R_TAMPER); |
| } else { |
| CLEAR_FLAG(pd, PD_FLAG_R_TAMPER); |
| } |
| ret = OSDP_CP_ERR_NONE; |
| break; |
| case REPLY_COM: |
| ASSERT_LENGTH(len, REPLY_COM_DATA_LEN); |
| t1 = buf[pos++]; |
| temp32 = buf[pos++]; |
| temp32 |= buf[pos++] << 8; |
| temp32 |= buf[pos++] << 16; |
| temp32 |= buf[pos++] << 24; |
| OSDP_LOG_WARN("osdp: cp: COMSET responded with ID:%d Baud:%d\n", t1, temp32); |
| pd->address = t1; |
| pd->baud_rate = temp32; |
| ret = OSDP_CP_ERR_NONE; |
| break; |
| case REPLY_KEYPPAD: |
| if (len < REPLY_KEYPPAD_DATA_LEN || !cp->event_callback) { |
| break; |
| } |
| event.type = OSDP_EVENT_KEYPRESS; |
| event.keypress.reader_no = buf[pos++]; |
| event.keypress.length = buf[pos++]; /* key length */ |
| if ((len - REPLY_KEYPPAD_DATA_LEN) != event.keypress.length) { |
| break; |
| } |
| for (i = 0; i < event.keypress.length; i++) { |
| event.keypress.data[i] = buf[pos + i]; |
| } |
| cp->event_callback(cp->event_callback_arg, pd->offset, &event); |
| ret = OSDP_CP_ERR_NONE; |
| break; |
| case REPLY_RAW: |
| if (len < REPLY_RAW_DATA_LEN || !cp->event_callback) { |
| break; |
| } |
| event.type = OSDP_EVENT_CARDREAD; |
| event.cardread.reader_no = buf[pos++]; |
| event.cardread.format = buf[pos++]; |
| event.cardread.length = buf[pos++]; /* bits LSB */ |
| event.cardread.length |= buf[pos++] << 8; /* bits MSB */ |
| event.cardread.direction = 0; /* un-specified */ |
| t1 = (event.cardread.length + 7) / 8; /* len: bytes */ |
| if (t1 != (len - REPLY_RAW_DATA_LEN)) { |
| break; |
| } |
| for (i = 0; i < t1; i++) { |
| event.cardread.data[i] = buf[pos + i]; |
| } |
| cp->event_callback(cp->event_callback_arg, pd->offset, &event); |
| ret = OSDP_CP_ERR_NONE; |
| break; |
| case REPLY_FMT: |
| if (len < REPLY_FMT_DATA_LEN || !cp->event_callback) { |
| break; |
| } |
| event.type = OSDP_EVENT_CARDREAD; |
| event.cardread.reader_no = buf[pos++]; |
| event.cardread.direction = buf[pos++]; |
| event.cardread.length = buf[pos++]; |
| event.cardread.format = OSDP_CARD_FMT_ASCII; |
| if (event.cardread.length != (len - REPLY_FMT_DATA_LEN) || |
| event.cardread.length > OSDP_EVENT_MAX_DATALEN) { |
| break; |
| } |
| for (i = 0; i < event.cardread.length; i++) { |
| event.cardread.data[i] = buf[pos + i]; |
| } |
| cp->event_callback(cp->event_callback_arg, pd->offset, &event); |
| ret = OSDP_CP_ERR_NONE; |
| break; |
| case REPLY_BUSY: |
| /* PD busy; signal upper layer to retry command */ |
| ASSERT_LENGTH(len, REPLY_BUSY_DATA_LEN); |
| ret = OSDP_CP_ERR_RETRY_CMD; |
| break; |
| case REPLY_MFGREP: |
| if (len < REPLY_MFGREP_LEN || !cp->event_callback) { |
| break; |
| } |
| event.type = OSDP_EVENT_MFGREP; |
| event.mfgrep.vendor_code = buf[pos++]; |
| event.mfgrep.vendor_code |= buf[pos++] << 8; |
| event.mfgrep.vendor_code |= buf[pos++] << 16; |
| event.mfgrep.command = buf[pos++]; |
| event.mfgrep.length = len - REPLY_MFGREP_LEN; |
| if (event.mfgrep.length > OSDP_EVENT_MAX_DATALEN) { |
| break; |
| } |
| for (i = 0; i < event.mfgrep.length; i++) { |
| event.mfgrep.data[i] = buf[pos + i]; |
| } |
| cp->event_callback(cp->event_callback_arg, pd->offset, &event); |
| ret = OSDP_CP_ERR_NONE; |
| break; |
| case REPLY_CCRYPT: |
| ASSERT_LENGTH(len, REPLY_CCRYPT_DATA_LEN); |
| for (i = 0; i < 8; i++) { |
| pd->sc.pd_client_uid[i] = buf[pos++]; |
| } |
| for (i = 0; i < 8; i++) { |
| pd->sc.pd_random[i] = buf[pos++]; |
| } |
| for (i = 0; i < 16; i++) { |
| pd->sc.pd_cryptogram[i] = buf[pos++]; |
| } |
| osdp_compute_session_keys(TO_CTX(pd)); |
| if (osdp_verify_pd_cryptogram(pd) != 0) { |
| OSDP_LOG_ERROR("osdp: cp: Failed to verify PD cryptogram\n"); |
| return OSDP_CP_ERR_GENERIC; |
| } |
| ret = OSDP_CP_ERR_NONE; |
| break; |
| case REPLY_RMAC_I: |
| ASSERT_LENGTH(len, REPLY_RMAC_I_DATA_LEN); |
| for (i = 0; i < 16; i++) { |
| pd->sc.r_mac[i] = buf[pos++]; |
| } |
| SET_FLAG(pd, PD_FLAG_SC_ACTIVE); |
| ret = OSDP_CP_ERR_NONE; |
| break; |
| default: |
| OSDP_LOG_DEBUG("osdp: cp: Unexpected REPLY(%02x)\n", pd->reply_id); |
| return OSDP_CP_ERR_GENERIC; |
| } |
| |
| if (ret == OSDP_CP_ERR_GENERIC) { |
| OSDP_LOG_ERROR("osdp: cp: Format error in REPLY(%02x) for CMD(%02x)", |
| pd->reply_id, pd->cmd_id); |
| return OSDP_CP_ERR_GENERIC; |
| } |
| |
| if (pd->cmd_id != CMD_POLL) { |
| OSDP_LOG_DEBUG("osdp: cp: CMD(%02x) REPLY(%02x)\n", pd->cmd_id, pd->reply_id); |
| } |
| |
| return ret; |
| } |
| |
| static int |
| cp_send_command(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 OSDP_CP_ERR_GENERIC; |
| } |
| |
| /* fill command data */ |
| ret = cp_build_command(pd, pd->rx_buf, sizeof(pd->rx_buf)); |
| if (ret < 0) { |
| return OSDP_CP_ERR_GENERIC; |
| } |
| len += ret; |
| |
| /* finalize packet */ |
| len = osdp_phy_packet_finalize(pd, pd->rx_buf, len, sizeof(pd->rx_buf)); |
| if (len < 0) { |
| return OSDP_CP_ERR_GENERIC; |
| } |
| |
| /* flush rx to remove any invalid data. */ |
| if (pd->channel.flush) { |
| pd->channel.flush(pd->channel.data); |
| } |
| |
| ret = pd->channel.send(pd->channel.data, pd->rx_buf, len); |
| if (ret != len) { |
| OSDP_LOG_ERROR("osdp: cp: Channel send for %d bytes failed! ret: %d\n", len, ret); |
| return OSDP_CP_ERR_GENERIC; |
| } |
| |
| if (MYNEWT_VAL(OSDP_PACKET_TRACE)) { |
| if (pd->cmd_id != CMD_POLL) { |
| osdp_dump(pd->rx_buf, len, |
| "OSDP: PD[%d]: Sent\n", pd->offset); |
| } |
| } |
| |
| return OSDP_CP_ERR_NONE; |
| } |
| |
| static int |
| cp_process_reply(struct osdp_pd *pd) |
| { |
| uint8_t *buf; |
| int err, len, remaining; |
| |
| buf = pd->rx_buf + pd->rx_buf_len; |
| remaining = sizeof(pd->rx_buf) - pd->rx_buf_len; |
| |
| len = pd->channel.recv(pd->channel.data, buf, remaining); |
| if (len <= 0) { /* No data received */ |
| return OSDP_CP_ERR_NO_DATA; |
| } |
| pd->rx_buf_len += len; |
| |
| if (MYNEWT_VAL(OSDP_PACKET_TRACE)) { |
| if (pd->cmd_id != CMD_POLL) { |
| osdp_dump(pd->rx_buf, pd->rx_buf_len, |
| "OSDP: PD[%d]: Received\n", pd->offset); |
| } |
| } |
| |
| err = osdp_phy_check_packet(pd, pd->rx_buf, pd->rx_buf_len, &len); |
| if (err == OSDP_ERR_PKT_WAIT) { |
| /* rx_buf_len < pkt->len; wait for more data */ |
| return OSDP_CP_ERR_NO_DATA; |
| } |
| if (err == OSDP_ERR_PKT_NONE) { |
| /* Valid OSDP packet in buffer */ |
| len = osdp_phy_decode_packet(pd, pd->rx_buf, len, &buf); |
| if (len <= 0) { |
| return OSDP_CP_ERR_GENERIC; |
| } |
| err = cp_decode_response(pd, buf, len); |
| } |
| |
| /* We are done with the packet (error or not). Remove processed bytes */ |
| remaining = pd->rx_buf_len - len; |
| if (remaining) { |
| memmove(pd->rx_buf, pd->rx_buf + len, remaining); |
| pd->rx_buf_len = remaining; |
| } |
| |
| return err; |
| } |
| |
| static inline void |
| cp_set_state(struct osdp_pd *pd, enum osdp_state_e state) |
| { |
| pd->state = state; |
| CLEAR_FLAG(pd, PD_FLAG_AWAIT_RESP); |
| } |
| |
| static inline void |
| cp_set_online(struct osdp_pd *pd) |
| { |
| cp_set_state(pd, OSDP_CP_STATE_ONLINE); |
| pd->wait_ms = 0; |
| } |
| |
| static inline void |
| cp_set_offline(struct osdp_pd *pd) |
| { |
| CLEAR_FLAG(pd, PD_FLAG_SC_ACTIVE); |
| pd->state = OSDP_CP_STATE_OFFLINE; |
| pd->tstamp = osdp_millis_now(); |
| if (pd->wait_ms == 0) { |
| pd->wait_ms = 1000; /* retry after 1 second initially */ |
| } else { |
| pd->wait_ms <<= 1; |
| if (pd->wait_ms > OSDP_ONLINE_RETRY_WAIT_MAX_MS) { |
| pd->wait_ms = OSDP_ONLINE_RETRY_WAIT_MAX_MS; |
| } |
| } |
| } |
| |
| static int |
| cp_phy_state_update(struct osdp_pd *pd) |
| { |
| int64_t elapsed; |
| int rc, ret = OSDP_CP_ERR_CAN_YIELD; |
| struct osdp_cmd *cmd = NULL; |
| |
| switch (pd->phy_state) { |
| case OSDP_CP_PHY_STATE_WAIT: |
| elapsed = osdp_millis_since(pd->phy_tstamp); |
| if (elapsed < OSDP_CMD_RETRY_WAIT_MS) { |
| break; |
| } |
| pd->phy_state = OSDP_CP_PHY_STATE_SEND_CMD; |
| break; |
| case OSDP_CP_PHY_STATE_ERR: |
| ret = OSDP_CP_ERR_GENERIC; |
| break; |
| case OSDP_CP_PHY_STATE_IDLE: |
| if (cp_cmd_get(pd, &cmd, &ret)) { |
| break; |
| } |
| /* fall-thru */ |
| case OSDP_CP_PHY_STATE_SEND_CMD: |
| if ((cp_send_command(pd)) < 0) { |
| OSDP_LOG_ERROR("osdp: cp: Failed to send CMD(%d)\n", pd->cmd_id); |
| pd->phy_state = OSDP_CP_PHY_STATE_ERR; |
| ret = OSDP_CP_ERR_GENERIC; |
| break; |
| } |
| ret = OSDP_CP_ERR_INPROG; |
| pd->phy_state = OSDP_CP_PHY_STATE_REPLY_WAIT; |
| pd->rx_buf_len = 0; /* reset buf_len for next use */ |
| pd->phy_tstamp = osdp_millis_now(); |
| break; |
| case OSDP_CP_PHY_STATE_REPLY_WAIT: |
| rc = cp_process_reply(pd); |
| if (rc == OSDP_CP_ERR_NONE) { |
| pd->phy_state = OSDP_CP_PHY_STATE_IDLE; |
| break; |
| } |
| if (rc == OSDP_CP_ERR_RETRY_CMD) { |
| OSDP_LOG_INFO("osdp: cp: PD busy; retry last command\n"); |
| pd->phy_tstamp = osdp_millis_now(); |
| pd->phy_state = OSDP_CP_PHY_STATE_WAIT; |
| break; |
| } |
| if (rc == OSDP_CP_ERR_GENERIC || |
| osdp_millis_since(pd->phy_tstamp) > MYNEWT_VAL(OSDP_RESP_TOUT_MS)) { |
| if (rc != OSDP_CP_ERR_GENERIC) { |
| OSDP_LOG_ERROR("osdp: cp: Response timeout for CMD(%02x)", |
| pd->cmd_id); |
| } |
| pd->rx_buf_len = 0; |
| if (pd->channel.flush) { |
| pd->channel.flush(pd->channel.data); |
| } |
| cp_flush_command_queue(pd); |
| pd->phy_state = OSDP_CP_PHY_STATE_ERR; |
| ret = OSDP_CP_ERR_GENERIC; |
| break; |
| } |
| ret = OSDP_CP_ERR_INPROG; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int |
| cp_cmd_dispatcher(struct osdp_pd *pd, int cmd) |
| { |
| int rc; |
| struct osdp *ctx = TO_CTX(pd); |
| struct osdp_cmd *c; |
| |
| if (ISSET_FLAG(pd, PD_FLAG_AWAIT_RESP)) { |
| CLEAR_FLAG(pd, PD_FLAG_AWAIT_RESP); |
| return OSDP_CP_ERR_NONE; /* nothing to be done here */ |
| } |
| rc = osdp_device_lock(&pd->lock); |
| if (rc) { |
| return rc; |
| } |
| |
| c = cp_cmd_alloc(pd); |
| if (c == NULL) { |
| rc = OS_ENOMEM; |
| goto err; |
| } |
| |
| c->id = cmd; |
| switch (cmd) { |
| case CMD_KEYSET: |
| c->keyset.length = 16; |
| memcpy(c->keyset.data, ctx->sc_master_key, 16); |
| break; |
| } |
| cp_cmd_enqueue(pd, c); |
| SET_FLAG(pd, PD_FLAG_AWAIT_RESP); |
| rc = OSDP_CP_ERR_INPROG; |
| |
| err: |
| osdp_device_unlock(&pd->lock); |
| return rc; |
| } |
| |
| static int |
| state_update(struct osdp_pd *pd) |
| { |
| int phy_state, soft_fail; |
| struct osdp *ctx = TO_CTX(pd); |
| |
| phy_state = cp_phy_state_update(pd); |
| if (phy_state == OSDP_CP_ERR_INPROG || |
| phy_state == OSDP_CP_ERR_CAN_YIELD) { |
| return phy_state; |
| } |
| |
| /* Certain states can fail without causing PD offline */ |
| soft_fail = (pd->state == OSDP_CP_STATE_SC_CHLNG); |
| |
| /* phy state error -- cleanup */ |
| if (pd->state != OSDP_CP_STATE_OFFLINE && |
| phy_state == OSDP_CP_ERR_GENERIC && soft_fail == 0) { |
| cp_set_offline(pd); |
| return OSDP_CP_ERR_CAN_YIELD; |
| } |
| |
| /* command queue is empty and last command was successful */ |
| |
| switch (pd->state) { |
| case OSDP_CP_STATE_ONLINE: |
| if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE) == false && |
| ISSET_FLAG(pd, PD_FLAG_SC_CAPABLE) == true && |
| ISSET_FLAG(ctx, FLAG_SC_DISABLED) == false && |
| osdp_millis_since(pd->sc_tstamp) > OSDP_PD_SC_RETRY_MS) { |
| OSDP_LOG_INFO("osdp: cp: Retry SC after retry timeout\n"); |
| cp_set_state(pd, OSDP_CP_STATE_SC_INIT); |
| break; |
| } |
| if (osdp_millis_since(pd->tstamp) < OSDP_PD_POLL_TIMEOUT_MS) { |
| break; |
| } |
| if (cp_cmd_dispatcher(pd, CMD_POLL) == 0) { |
| pd->tstamp = osdp_millis_now(); |
| } |
| break; |
| case OSDP_CP_STATE_OFFLINE: |
| if (osdp_millis_since(pd->tstamp) > pd->wait_ms) { |
| cp_set_state(pd, OSDP_CP_STATE_INIT); |
| osdp_phy_state_reset(pd); |
| } |
| break; |
| case OSDP_CP_STATE_INIT: |
| cp_set_state(pd, OSDP_CP_STATE_IDREQ); |
| __fallthrough; |
| case OSDP_CP_STATE_IDREQ: |
| if (cp_cmd_dispatcher(pd, CMD_ID) != 0) { |
| break; |
| } |
| if (pd->reply_id != REPLY_PDID) { |
| OSDP_LOG_ERROR("osdp: cp: Unexpected REPLY(%02x) for cmd " |
| STR(CMD_CAP), pd->reply_id); |
| cp_set_offline(pd); |
| break; |
| } |
| cp_set_state(pd, OSDP_CP_STATE_CAPDET); |
| __fallthrough; |
| case OSDP_CP_STATE_CAPDET: |
| if (cp_cmd_dispatcher(pd, CMD_CAP) != 0) { |
| break; |
| } |
| if (pd->reply_id != REPLY_PDCAP) { |
| OSDP_LOG_ERROR("osdp: cp: Unexpected REPLY(%02x) for cmd " |
| STR(CMD_CAP), pd->reply_id); |
| cp_set_offline(pd); |
| break; |
| } |
| if (ISSET_FLAG(pd, PD_FLAG_SC_CAPABLE) && |
| !ISSET_FLAG(ctx, FLAG_SC_DISABLED)) { |
| CLEAR_FLAG(pd, PD_FLAG_SC_SCBKD_DONE); |
| CLEAR_FLAG(pd, PD_FLAG_SC_USE_SCBKD); |
| cp_set_state(pd, OSDP_CP_STATE_SC_INIT); |
| break; |
| } |
| if (ISSET_FLAG(pd, OSDP_FLAG_ENFORCE_SECURE)) { |
| OSDP_LOG_INFO("osdp: cp: SC disabled or not capable. Set PD offline due " |
| "to ENFORCE_SECURE\n"); |
| cp_set_offline(pd); |
| } else { |
| cp_set_online(pd); |
| } |
| break; |
| case OSDP_CP_STATE_SC_INIT: |
| osdp_sc_init(pd); |
| cp_set_state(pd, OSDP_CP_STATE_SC_CHLNG); |
| __fallthrough; |
| case OSDP_CP_STATE_SC_CHLNG: |
| if (cp_cmd_dispatcher(pd, CMD_CHLNG) != 0) { |
| break; |
| } |
| if (phy_state < 0) { |
| if (ISSET_FLAG(pd, OSDP_FLAG_ENFORCE_SECURE)) { |
| OSDP_LOG_INFO("osdp: cp: SC Failed. Set PD offline due to " |
| "ENFORCE_SECURE\n"); |
| cp_set_offline(pd); |
| break; |
| } |
| if (ISSET_FLAG(pd, PD_FLAG_SC_SCBKD_DONE)) { |
| OSDP_LOG_INFO("osdp: cp: SC Failed. Online without SC\n"); |
| pd->sc_tstamp = osdp_millis_now(); |
| cp_set_online(pd); |
| break; |
| } |
| SET_FLAG(pd, PD_FLAG_SC_USE_SCBKD); |
| SET_FLAG(pd, PD_FLAG_SC_SCBKD_DONE); |
| cp_set_state(pd, OSDP_CP_STATE_SC_INIT); |
| pd->phy_state = 0; /* soft reset phy state */ |
| OSDP_LOG_WARN("osdp: cp: SC Failed. Retry with SCBK-D\n"); |
| break; |
| } |
| if (pd->reply_id != REPLY_CCRYPT) { |
| if (ISSET_FLAG(pd, OSDP_FLAG_ENFORCE_SECURE)) { |
| OSDP_LOG_ERROR("osdp: cp: CHLNG failed. Set PD offline due to " |
| "ENFORCE_SECURE\n"); |
| cp_set_offline(pd); |
| } else { |
| OSDP_LOG_ERROR("osdp: cp: CHLNG failed. Online without SC\n"); |
| pd->sc_tstamp = osdp_millis_now(); |
| osdp_phy_state_reset(pd); |
| cp_set_online(pd); |
| } |
| break; |
| } |
| cp_set_state(pd, OSDP_CP_STATE_SC_SCRYPT); |
| __fallthrough; |
| case OSDP_CP_STATE_SC_SCRYPT: |
| if (cp_cmd_dispatcher(pd, CMD_SCRYPT) != 0) { |
| break; |
| } |
| if (pd->reply_id != REPLY_RMAC_I) { |
| if (ISSET_FLAG(pd, OSDP_FLAG_ENFORCE_SECURE)) { |
| OSDP_LOG_ERROR("osdp: cp: SCRYPT failed. Set PD offline due to " |
| "ENFORCE_SECURE\n"); |
| cp_set_offline(pd); |
| } else { |
| OSDP_LOG_ERROR("osdp: cp: SCRYPT failed. Online without SC\n"); |
| osdp_phy_state_reset(pd); |
| pd->sc_tstamp = osdp_millis_now(); |
| cp_set_online(pd); |
| } |
| break; |
| } |
| if (ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD)) { |
| OSDP_LOG_WARN("osdp: cp: SC ACtive with SCBK-D. Set SCBK\n"); |
| cp_set_state(pd, OSDP_CP_STATE_SET_SCBK); |
| break; |
| } |
| OSDP_LOG_INFO("osdp: cp: SC Active\n"); |
| pd->sc_tstamp = osdp_millis_now(); |
| cp_set_online(pd); |
| break; |
| case OSDP_CP_STATE_SET_SCBK: |
| if (cp_cmd_dispatcher(pd, CMD_KEYSET) != 0) { |
| break; |
| } |
| if (pd->reply_id == REPLY_NAK) { |
| if (ISSET_FLAG(pd, OSDP_FLAG_ENFORCE_SECURE)) { |
| OSDP_LOG_ERROR("osdp: cp: Failed to set SCBK; " |
| "Set PD offline due to ENFORCE_SECURE\n"); |
| cp_set_offline(pd); |
| } else { |
| OSDP_LOG_WARN("osdp: cp: Failed to set SCBK; " |
| "Continue with SCBK-D\n"); |
| cp_set_state(pd, OSDP_CP_STATE_ONLINE); |
| } |
| break; |
| } |
| OSDP_LOG_INFO("osdp: cp: SCBK set; restarting SC to verify new SCBK\n"); |
| CLEAR_FLAG(pd, PD_FLAG_SC_USE_SCBKD); |
| CLEAR_FLAG(pd, PD_FLAG_SC_ACTIVE); |
| cp_set_state(pd, OSDP_CP_STATE_SC_INIT); |
| pd->seq_number = -1; |
| break; |
| default: |
| break; |
| } |
| |
| return OSDP_CP_ERR_CAN_YIELD; |
| } |
| |
| static int |
| osdp_cp_send_command_keyset(osdp_t *ctx, struct osdp_cmd_keyset *p) |
| { |
| int i, rc; |
| struct osdp_pd *pd; |
| |
| if (osdp_get_sc_status_mask(ctx) != PD_MASK(ctx)) { |
| OSDP_LOG_WARN("osdp: cp: CMD_KEYSET can be sent only when all PDs are " |
| "ONLINE and SC_ACTIVE.\n"); |
| return 1; |
| } |
| |
| for (i = 0; i < NUM_PD(ctx); i++) { |
| pd = TO_PD(ctx, i); |
| struct osdp_cmd *cmd; |
| cmd = CONTAINER_OF(p, struct osdp_cmd, keyset); |
| rc = cp_cmd_put(pd, cmd, CMD_KEYSET); |
| if (rc) { |
| return rc; |
| } |
| } |
| |
| return 0; |
| } |
| |
| osdp_t * |
| osdp_cp_setup(struct osdp_ctx *osdp_ctx, int num_pd, osdp_pd_info_t *info, |
| uint8_t *master_key) |
| { |
| uint16_t i; |
| int owner; |
| struct osdp_pd *pd; |
| struct osdp_cp *cp; |
| struct osdp *ctx; |
| |
| assert(info); |
| assert(num_pd > 0); |
| |
| ctx = &osdp_ctx->ctx; |
| ctx->magic = 0xDEADBEAF; |
| SET_FLAG(ctx, FLAG_CP_MODE); |
| |
| if (master_key != NULL) { |
| memcpy(ctx->sc_master_key, master_key, 16); |
| } else { |
| OSDP_LOG_WARN("osdp: cp: Master key not available! SC Disabled.\n"); |
| SET_FLAG(ctx, FLAG_SC_DISABLED); |
| } |
| |
| ctx->cp = &osdp_ctx->cp_ctx; |
| cp = TO_CP(ctx); |
| cp->__parent = ctx; |
| cp->channel_lock = &osdp_ctx->ch_locks_ctx[0]; |
| |
| ctx->pd = &osdp_ctx->pd_ctx[0]; |
| cp->num_pd = num_pd; |
| |
| for (i = 0; i < num_pd; i++) { |
| osdp_pd_info_t *p = info + i; |
| pd = TO_PD(ctx, i); |
| pd->offset = i; |
| pd->__parent = ctx; |
| pd->baud_rate = p->baud_rate; |
| pd->address = p->address; |
| pd->flags = p->flags; |
| pd->seq_number = -1; |
| if (cp_cmd_queue_init(pd, i)) { |
| goto error; |
| } |
| memcpy(&pd->channel, &p->channel, sizeof(struct osdp_channel)); |
| if (cp_channel_acquire(pd, &owner) == -1) { |
| SET_FLAG(TO_PD(ctx, owner), PD_FLAG_CHN_SHARED); |
| SET_FLAG(pd, PD_FLAG_CHN_SHARED); |
| } |
| if (IS_ENABLED(CONFIG_OSDP_SKIP_MARK_BYTE)) { |
| SET_FLAG(pd, PD_FLAG_PKT_SKIP_MARK); |
| } |
| osdp_cp_set_event_callback(ctx, p->cp_cb, NULL); |
| } |
| memset(cp->channel_lock, 0, sizeof(int) * num_pd); |
| SET_CURRENT_PD(ctx, 0); |
| OSDP_LOG_INFO("osdp: cp: CP setup complete\n"); |
| return (osdp_t *) ctx; |
| |
| error: |
| osdp_cp_teardown((osdp_t *)ctx); |
| return NULL; |
| } |
| |
| void |
| osdp_cp_teardown(osdp_t *ctx) |
| { |
| int i; |
| |
| if (ctx == NULL || TO_CP(ctx) == NULL) { |
| return; |
| } |
| |
| for (i = 0; i < NUM_PD(ctx); i++) { |
| cp_cmd_queue_del(TO_PD(ctx, i)); |
| } |
| } |
| |
| void |
| osdp_refresh(osdp_t *ctx) |
| { |
| int i, rc; |
| struct osdp_pd *pd; |
| |
| assert(ctx); |
| |
| for (i = 0; i < NUM_PD(ctx); i++) { |
| SET_CURRENT_PD(ctx, i); |
| /* |
| osdp_log_ctx_set(i); |
| */ |
| pd = TO_PD(ctx, i); |
| |
| if (ISSET_FLAG(pd, PD_FLAG_CHN_SHARED) && |
| cp_channel_acquire(pd, NULL)) { |
| /* failed to lock shared channel */ |
| continue; |
| } |
| |
| rc = state_update(pd); |
| |
| if (ISSET_FLAG(pd, PD_FLAG_CHN_SHARED) && |
| rc == OSDP_CP_ERR_CAN_YIELD) { |
| cp_channel_release(pd); |
| } |
| } |
| } |
| |
| /* --- Exported Methods --- */ |
| |
| void |
| osdp_cp_set_event_callback(osdp_t *ctx, cp_event_callback_t cb, void *arg) |
| { |
| assert(ctx); |
| |
| TO_CP(ctx)->event_callback = cb; |
| TO_CP(ctx)->event_callback_arg = arg; |
| } |
| |
| int |
| osdp_cp_send_command(osdp_t *ctx, int pd, struct osdp_cmd *p) |
| { |
| assert(ctx); |
| int cmd_id; |
| |
| if (pd < 0 || pd >= NUM_PD(ctx)) { |
| OSDP_LOG_ERROR("osdp: cp: Invalid PD number\n"); |
| return -1; |
| } |
| if (TO_PD(ctx, pd)->state != OSDP_CP_STATE_ONLINE) { |
| OSDP_LOG_WARN("osdp: cp: PD not online\n"); |
| return -1; |
| } |
| |
| switch (p->id) { |
| case OSDP_CMD_OUTPUT: |
| cmd_id = CMD_OUT; |
| break; |
| case OSDP_CMD_LED: |
| cmd_id = CMD_LED; |
| break; |
| case OSDP_CMD_BUZZER: |
| cmd_id = CMD_BUZ; |
| break; |
| case OSDP_CMD_TEXT: |
| cmd_id = CMD_TEXT; |
| break; |
| case OSDP_CMD_COMSET: |
| cmd_id = CMD_COMSET; |
| break; |
| case OSDP_CMD_MFG: |
| cmd_id = CMD_MFG; |
| break; |
| case OSDP_CMD_KEYSET: |
| OSDP_LOG_INFO("osdp: cp: Master KEYSET is a global command; " |
| "all connected PDs will be affected.\n"); |
| return osdp_cp_send_command_keyset(ctx, &p->keyset); |
| default: |
| OSDP_LOG_ERROR("osdp: cp: Invalid CMD_ID:%d\n", p->id); |
| return -1; |
| } |
| |
| return cp_cmd_put(TO_PD(ctx, pd), p, cmd_id); |
| } |
| |
| #endif /* OSDP_MODE_CP */ |