You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by vi...@apache.org on 2021/02/04 16:46:32 UTC
[mynewt-core] branch master updated: net/osdp: Add OSDP library -
Add sample application
This is an automated email from the ASF dual-hosted git repository.
vipulrahane pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-core.git
The following commit(s) were added to refs/heads/master by this push:
new 25a44ef net/osdp: Add OSDP library - Add sample application
new ef0f504 Merge pull request #2459 from vikrant-proxy/osdp-library
25a44ef is described below
commit 25a44ef5f99ad3106fe89981260eff47079d8f2d
Author: Vikrant More <vi...@proxy.com>
AuthorDate: Fri Jan 22 16:16:02 2021 -0800
net/osdp: Add OSDP library
- Add sample application
---
apps/osdp_sample/pkg.yml | 34 +
apps/osdp_sample/src/main.c | 395 ++++++++++
apps/osdp_sample/syscfg.yml | 39 +
net/osdp/include/osdp/circular_buffer.h | 209 ++++++
net/osdp/include/osdp/list.h | 64 ++
net/osdp/include/osdp/osdp.h | 708 ++++++++++++++++++
net/osdp/include/osdp/osdp_common.h | 394 ++++++++++
net/osdp/include/osdp/osdp_hooks.h | 31 +
net/osdp/include/osdp/osdp_utils.h | 144 ++++
net/osdp/include/osdp/queue.h | 25 +
net/osdp/include/osdp/slab.h | 79 ++
net/osdp/pkg.yml | 28 +
net/osdp/src/circular_buffer.c | 278 +++++++
net/osdp/src/list.c | 328 +++++++++
net/osdp/src/osdp.c | 195 +++++
net/osdp/src/osdp_common.c | 203 +++++
net/osdp/src/osdp_cp.c | 1227 +++++++++++++++++++++++++++++++
net/osdp/src/osdp_hooks.c | 33 +
net/osdp/src/osdp_pd.c | 1137 ++++++++++++++++++++++++++++
net/osdp/src/osdp_phy.c | 475 ++++++++++++
net/osdp/src/osdp_sc.c | 259 +++++++
net/osdp/src/osdp_utils.c | 201 +++++
net/osdp/src/queue.c | 48 ++
net/osdp/src/slab.c | 69 ++
net/osdp/syscfg.yml | 281 +++++++
25 files changed, 6884 insertions(+)
diff --git a/apps/osdp_sample/pkg.yml b/apps/osdp_sample/pkg.yml
new file mode 100644
index 0000000..207b1ed
--- /dev/null
+++ b/apps/osdp_sample/pkg.yml
@@ -0,0 +1,34 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+pkg.name: apps/osdp_sample
+pkg.type: app
+pkg.description: Sample app for OSDP in PD mode
+pkg.author: ""
+pkg.homepage: ""
+pkg.keywords:
+
+pkg.deps:
+ - "@apache-mynewt-core/kernel/os"
+ - "@apache-mynewt-core/hw/hal"
+ - "@apache-mynewt-core/sys/log/full"
+ - "@apache-mynewt-core/sys/console/full"
+ - "@apache-mynewt-core/crypto/tinycrypt"
+ - "@apache-mynewt-core/hw/drivers/trng"
+ - "@apache-mynewt-core/net/osdp"
diff --git a/apps/osdp_sample/src/main.c b/apps/osdp_sample/src/main.c
new file mode 100644
index 0000000..54dc97c
--- /dev/null
+++ b/apps/osdp_sample/src/main.c
@@ -0,0 +1,395 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "sysinit/sysinit.h"
+#include "os/os.h"
+#include "bsp/bsp.h"
+#include "console/console.h"
+#include "hal/hal_gpio.h"
+#include "tinycrypt/aes.h"
+#include "osdp/osdp.h"
+#include "osdp/osdp_common.h"
+#ifdef ARCH_sim
+#include "mcu/mcu_sim.h"
+#endif
+
+#define COMMAND_HANDLER_INTERVAL (OS_TICKS_PER_SEC)
+
+#if MYNEWT_VAL(OSDP_MODE_PD)
+#define OSDP_KEY_STRING MYNEWT_VAL(OSDP_PD_SCBK)
+#else
+#define OSDP_KEY_STRING MYNEWT_VAL(OSDP_MASTER_KEY)
+#endif
+
+/* Use the first "per user" log module slot for test module. */
+#define LOG_MODULE (LOG_MODULE_PERUSER + 0)
+/* Convenience macro for logging to the app module. */
+#define LOG(lvl, ...) LOG_ ## lvl(&g_logger, LOG_MODULE, __VA_ARGS__)
+
+extern struct log g_logger;
+struct log g_logger;
+
+static struct os_callout cmd_timer;
+
+static int create_keypress_event(struct osdp_event *event);
+static int create_cardreader_event(struct osdp_event *event);
+static int create_mfgreply_event(struct osdp_cmd *oc);
+static int pd_command_handler(void *arg, struct osdp_cmd *cmd);
+
+/* From osdp.h */
+static const char *const osdp_cmd_str[] = {
+ "OSDP_CMD_OUTPUT",
+ "OSDP_CMD_LED",
+ "OSDP_CMD_BUZZER",
+ "OSDP_CMD_TEXT",
+ "OSDP_CMD_KEYSET",
+ "OSDP_CMD_COMSET",
+ "OSDP_CMD_MFG",
+ "OSDP_CMD_SENTINEL"
+};
+
+/*
+ * Callback function registered with the library
+ * Commands received from CP will be reflected here
+ */
+static int
+pd_command_handler(void *arg, struct osdp_cmd *cmd)
+{
+ (void)(arg);
+ int ret = 0;
+
+ LOG(INFO, "CMD [%d] = %s\n", cmd->id, osdp_cmd_str[cmd->id - 1]);
+
+ switch (cmd->id) {
+ case OSDP_CMD_COMSET:
+ break;
+ case OSDP_CMD_BUZZER:
+ LOG(INFO, "\n\trdr: %d,\n\tctrl_code: %d,\n\ton_ct: %d,\n\toff_ct: %d,\n\trep_count: %d\n",
+ cmd->buzzer.reader,
+ cmd->buzzer.control_code,
+ cmd->buzzer.on_count,
+ cmd->buzzer.off_count,
+ cmd->buzzer.rep_count
+ );
+ break;
+ case OSDP_CMD_LED:
+ LOG(INFO,
+ "\n\trdr: %d,\n\tctrl_code: %d,\n\tled_num: %d,\n\ton_ct: %d,\n\toff_ct: %d,\n\ton_clr: %d,\n\toff_clr: %d,\n\ttmr_ct: %d\n",
+ cmd->led.reader,
+ cmd->led.temporary.control_code,
+ cmd->led.led_number,
+ cmd->led.temporary.on_count,
+ cmd->led.temporary.off_count,
+ cmd->led.temporary.on_color,
+ cmd->led.temporary.off_color,
+ cmd->led.temporary.timer_count
+ );
+ break;
+ case OSDP_CMD_TEXT:
+ LOG(INFO,
+ "\n\trdr: %d,\n\tctrl_code: %d,\n\ttemp_time: %d,\n\toffset_row: %d,\n\toffset_col: %d,\n\tdata: %s\n",
+ cmd->text.reader,
+ cmd->text.control_code,
+ cmd->text.temp_time,
+ cmd->text.offset_row,
+ cmd->text.offset_col,
+ cmd->text.data
+ );
+ break;
+ case OSDP_CMD_MFG:
+ LOG(INFO, "\n\tv_code: 0x%04x,\n\tcmd: %d,\n\tlen: %d,\n\tdata: ",
+ cmd->mfg.vendor_code,
+ cmd->mfg.command,
+ cmd->mfg.length
+ );
+ for (int i = 0; i < cmd->mfg.length; ++i) {
+ printf("%02x ", cmd->mfg.data[i]);
+ }
+ LOG(INFO, "\n");
+ /* Send manufacturer specific reply. Indicate ret > 0 to send reply */
+ LOG(INFO, "Sending manufacturer specific reply\n");
+ create_mfgreply_event(cmd);
+ ret = 1;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ *
+ */
+static int
+create_mfgreply_event(struct osdp_cmd *oc)
+{
+ int i, data_length, vendor_code, mfg_command;
+ struct osdp_cmd_mfg *cmd = &oc->mfg;
+
+ /* Sample reply */
+ uint8_t data_bytes[] = {"ManufacturerReply"};
+ data_length = sizeof(data_bytes);
+ vendor_code = 0x00030201;
+ mfg_command = 14;
+
+ cmd->vendor_code = (uint32_t)vendor_code;
+ cmd->command = mfg_command;
+ cmd->length = data_length;
+ for (i = 0; i < cmd->length; i++) {
+ cmd->data[i] = data_bytes[i];
+ }
+
+ return 0;
+}
+
+static int
+create_cardreader_event(struct osdp_event *event)
+{
+ int i, data_length, reader_no, format, direction, len_bytes;
+ struct osdp_event_cardread *ev = &event->cardread;
+
+ /* Sample parameters */
+ uint8_t data_bytes[] = {"CardCredentials"};
+ data_length = sizeof(data_bytes);
+ reader_no = 1;
+ format = OSDP_CARD_FMT_ASCII;
+ direction = 1;
+ event->type = OSDP_EVENT_CARDREAD;
+
+ if (format == OSDP_CARD_FMT_RAW_WIEGAND ||
+ format == OSDP_CARD_FMT_RAW_UNSPECIFIED) {
+ len_bytes = (data_length + 7) / 8; /* bits to bytes */
+ } else {
+ len_bytes = data_length;
+ }
+
+ if (len_bytes > OSDP_EVENT_MAX_DATALEN) {
+ return -1;
+ }
+
+ ev->reader_no = (uint8_t)reader_no;
+ ev->format = (uint8_t)format;
+ ev->direction = (uint8_t)direction;
+ ev->length = len_bytes;
+ for (i = 0; i < len_bytes; i++) {
+ ev->data[i] = data_bytes[i];
+ }
+
+ return 0;
+}
+
+static int
+create_keypress_event(struct osdp_event *event)
+{
+ int i, data_length, reader_no;
+ struct osdp_event_keypress *ev = &event->keypress;
+
+ /* Sample parameters */
+ event->type = OSDP_EVENT_KEYPRESS;
+ uint8_t data_bytes[] = {"KeyPress"};
+ data_length = sizeof(data_bytes);
+ reader_no = 1;
+
+ ev->reader_no = (uint8_t)reader_no;
+ ev->length = data_length;
+ for (i = 0; i < ev->length; i++) {
+ ev->data[i] = data_bytes[i];
+ }
+
+ return 0;
+}
+
+/*
+ * Handler called after cmd_handler timer timeout
+ */
+static void
+cmd_handler(struct os_event *ev)
+{
+ assert(ev != NULL);
+ struct osdp *ctx = osdp_get_ctx();
+ static int cmd = 0;
+
+ uint32_t sc_active = osdp_get_sc_status_mask(ctx);
+
+ if (sc_active) {
+ struct osdp_event event;
+ if (cmd == 0) {
+ LOG(INFO, "Sending Card Read\n");
+ create_cardreader_event(&event);
+ } else if (cmd == 1) {
+ LOG(INFO, "Sending Key Press\n");
+ create_keypress_event(&event);
+ }
+ osdp_pd_notify_event(ctx, &event);
+ cmd++;
+ if (cmd > 1) {
+ cmd = 0;
+ }
+ }
+
+ /* Heartbeat blink */
+ hal_gpio_toggle(LED_BLINK_PIN);
+
+ /* Restart */
+ os_callout_reset(&cmd_timer, COMMAND_HANDLER_INTERVAL);
+}
+
+/*
+ * Initialize all timers
+ */
+static int
+timers_init(void)
+{
+ /* Configure and reset timer */
+ os_callout_init(&cmd_timer, os_eventq_dflt_get(),
+ cmd_handler, NULL);
+
+ os_callout_reset(&cmd_timer, COMMAND_HANDLER_INTERVAL);
+
+ return 0;
+}
+
+/*
+ * Simple test for OSDP encryption routine
+ */
+static int
+test_encryption_wrappers(uint8_t *key, uint8_t len)
+{
+ int rc;
+ uint8_t plaintext_ref[TC_AES_BLOCK_SIZE] =
+ {0xff, 0x53, 0x65, 0x13, 0x00, 0x0e, 0x03, 0x11, 0x00, 0x01, 0x76, 0x3b, 0x24, 0xdf, 0x92, 0x5b};
+ uint8_t data_buffer[TC_AES_BLOCK_SIZE];
+ uint8_t iv_buffer[TC_AES_BLOCK_SIZE];
+
+ /* Test AES-CBC */
+ memcpy(data_buffer, plaintext_ref, TC_AES_BLOCK_SIZE);
+ osdp_get_rand(iv_buffer, TC_AES_BLOCK_SIZE); /* Create initial vector */
+ osdp_encrypt(key, iv_buffer, data_buffer,
+ TC_AES_BLOCK_SIZE); /* Encrypt using initial vector in CBC mode */
+ osdp_decrypt(key, iv_buffer, data_buffer, TC_AES_BLOCK_SIZE); /* Decrypt */
+
+ rc = memcmp(data_buffer, plaintext_ref, TC_AES_BLOCK_SIZE);
+ if (rc) {
+ return rc;
+ }
+
+ /* Test AES-ECB */
+ memcpy(data_buffer, plaintext_ref, TC_AES_BLOCK_SIZE);
+ osdp_encrypt(key, NULL, data_buffer,
+ TC_AES_BLOCK_SIZE); /* Encrypt without initial vector, i.e ECB mode */
+ osdp_decrypt(key, NULL, data_buffer, TC_AES_BLOCK_SIZE); /* Decrypt */
+
+ rc = memcmp(data_buffer, plaintext_ref, TC_AES_BLOCK_SIZE);
+ if (rc) {
+ return rc;
+ }
+
+ return rc;
+}
+
+int
+main(int argc, char **argv)
+{
+ int rc, len;
+ uint8_t key_buf[16];
+ uint8_t *key = NULL;
+
+ log_register("osdp_main_log", &g_logger, &log_console_handler, NULL, LOG_SYSLEVEL);
+
+ sysinit();
+
+ hal_gpio_init_out(LED_BLINK_PIN, 1);
+
+ /* List capabilities of this PD */
+ struct osdp_pd_cap cap[] = {
+ {
+ .function_code = OSDP_PD_CAP_READER_LED_CONTROL,
+ .compliance_level = 1,
+ .num_items = 1
+ },
+ {
+ .function_code = OSDP_PD_CAP_READER_AUDIBLE_OUTPUT,
+ .compliance_level = 1,
+ .num_items = 1
+ },
+ {
+ .function_code = OSDP_PD_CAP_OUTPUT_CONTROL,
+ .compliance_level = 1,
+ .num_items = 1
+ },
+ {
+ .function_code = OSDP_PD_CAP_READER_TEXT_OUTPUT,
+ .compliance_level = 1,
+ .num_items = 1
+ },
+ { -1, 0, 0 }
+ };
+
+ osdp_pd_info_t info_pd = {
+ .address = MYNEWT_VAL(OSDP_PD_ADDRESS),
+ .baud_rate = MYNEWT_VAL(OSDP_UART_BAUD_RATE),
+ .flags = 0,
+ .channel.send = NULL, /* Managed by the library */
+ .channel.recv = NULL, /* Managed by the library */
+ .channel.flush = NULL, /* Managed by the library */
+ .channel.data = NULL, /* Managed by the library */
+ .id = {
+ .version = MYNEWT_VAL(OSDP_PD_ID_VERSION),
+ .model = MYNEWT_VAL(OSDP_PD_ID_MODEL),
+ .vendor_code = MYNEWT_VAL(OSDP_PD_ID_VENDOR_CODE),
+ .serial_number = MYNEWT_VAL(OSDP_PD_ID_SERIAL_NUMBER),
+ .firmware_version = MYNEWT_VAL(OSDP_PD_ID_FIRMWARE_VERSION),
+ },
+ .cap = cap,
+ .pd_cb = pd_command_handler
+ };
+
+ /* Validate and assign key */
+ if (MYNEWT_VAL(OSDP_SC_ENABLED) && strcmp(OSDP_KEY_STRING, "NONE") != 0) {
+
+ len = strlen(OSDP_KEY_STRING);
+ assert(len == 32);
+
+ len = hex2bin(OSDP_KEY_STRING, 32, key_buf, 16);
+ assert(len == 16);
+
+ rc = test_encryption_wrappers(key_buf, 16);
+ assert(rc == 0);
+
+ key = key_buf;
+ }
+
+ /* Initialize the library module */
+ osdp_init(&info_pd, key);
+
+ timers_init();
+
+ while (1) {
+ os_eventq_run(os_eventq_dflt_get());
+ }
+
+ assert(0);
+
+ return 0;
+}
diff --git a/apps/osdp_sample/syscfg.yml b/apps/osdp_sample/syscfg.yml
new file mode 100644
index 0000000..c4a75c3
--- /dev/null
+++ b/apps/osdp_sample/syscfg.yml
@@ -0,0 +1,39 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+syscfg.vals:
+ # OSDP specific configuration
+ OSDP_MODE_PD: 1 # set in peripheral device mode
+ OSDP_SC_ENABLED: 1 # enable secure channel
+ OSDP_UART_DEV_NAME: '"uart0"' # set uart device for OSDP communication
+ TRNG: 1 # for SC mode
+
+# PD mode config
+syscfg.vals.OSDP_MODE_PD:
+ OSDP_PD_ADDRESS: 101
+ OSDP_PD_ID_VENDOR_CODE: 0xCAFEBABE
+ OSDP_PD_ID_MODEL: 1
+ OSDP_PD_ID_VERSION: 1
+ OSDP_PD_ID_SERIAL_NUMBER: 0xDEADBEAF
+ OSDP_PD_ID_FIRMWARE_VERSION: 0x0000F00D
+ # PD mode key set to none, i.e. in install mode
+ OSDP_PD_SCBK: '"NONE"'
+
+# CP mode config
+syscfg.vals.OSDP_MODE_CP:
+ OSDP_MASTER_KEY: '"01020304050607080910111213141516"'
diff --git a/net/osdp/include/osdp/circular_buffer.h b/net/osdp/include/osdp/circular_buffer.h
new file mode 100644
index 0000000..fe5da5c
--- /dev/null
+++ b/net/osdp/include/osdp/circular_buffer.h
@@ -0,0 +1,209 @@
+/*
+ CC0 1.0 Universal
+
+ Statement of Purpose
+
+ The laws of most jurisdictions throughout the world automatically confer
+ exclusive Copyright and Related Rights (defined below) upon the creator and
+ subsequent owner(s) (each and all, an "owner") of an original work of
+ authorship and/or a database (each, a "Work").
+
+ Certain owners wish to permanently relinquish those rights to a Work for the
+ purpose of contributing to a commons of creative, cultural and scientific
+ works ("Commons") that the public can reliably and without fear of later
+ claims of infringement build upon, modify, incorporate in other works, reuse
+ and redistribute as freely as possible in any form whatsoever and for any
+ purposes, including without limitation commercial purposes. These owners may
+ contribute to the Commons to promote the ideal of a free culture and the
+ further production of creative, cultural and scientific works, or to gain
+ reputation or greater distribution for their Work in part through the use and
+ efforts of others.
+
+ For these and/or other purposes and motivations, and without any expectation
+ of additional consideration or compensation, the person associating CC0 with a
+ Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
+ and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
+ and publicly distribute the Work under its terms, with knowledge of his or her
+ Copyright and Related Rights in the Work and the meaning and intended legal
+ effect of CC0 on those rights.
+
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
+ protected by copyright and related or neighboring rights ("Copyright and
+ Related Rights"). Copyright and Related Rights include, but are not limited
+ to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display, communicate,
+ and translate a Work;
+
+ ii. moral rights retained by the original author(s) and/or performer(s);
+
+ iii. publicity and privacy rights pertaining to a person's image or likeness
+ depicted in a Work;
+
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+
+ v. rights protecting the extraction, dissemination, use and reuse of data in
+ a Work;
+
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation thereof,
+ including any amended or successor version of such directive); and
+
+ vii. other similar, equivalent or corresponding rights throughout the world
+ based on applicable law or treaty, and any national implementations thereof.
+
+ 2. Waiver. To the greatest extent permitted by, but not in contravention of,
+ applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+ unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
+ and Related Rights and associated claims and causes of action, whether now
+ known or unknown (including existing as well as future claims and causes of
+ action), in the Work (i) in all territories worldwide, (ii) for the maximum
+ duration provided by applicable law or treaty (including future time
+ extensions), (iii) in any current or future medium and for any number of
+ copies, and (iv) for any purpose whatsoever, including without limitation
+ commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
+ the Waiver for the benefit of each member of the public at large and to the
+ detriment of Affirmer's heirs and successors, fully intending that such Waiver
+ shall not be subject to revocation, rescission, cancellation, termination, or
+ any other legal or equitable action to disrupt the quiet enjoyment of the Work
+ by the public as contemplated by Affirmer's express Statement of Purpose.
+
+ 3. Public License Fallback. Should any part of the Waiver for any reason be
+ judged legally invalid or ineffective under applicable law, then the Waiver
+ shall be preserved to the maximum extent permitted taking into account
+ Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
+ is so judged Affirmer hereby grants to each affected person a royalty-free,
+ non transferable, non sublicensable, non exclusive, irrevocable and
+ unconditional license to exercise Affirmer's Copyright and Related Rights in
+ the Work (i) in all territories worldwide, (ii) for the maximum duration
+ provided by applicable law or treaty (including future time extensions), (iii)
+ in any current or future medium and for any number of copies, and (iv) for any
+ purpose whatsoever, including without limitation commercial, advertising or
+ promotional purposes (the "License"). The License shall be deemed effective as
+ of the date CC0 was applied by Affirmer to the Work. Should any part of the
+ License for any reason be judged legally invalid or ineffective under
+ applicable law, such partial invalidity or ineffectiveness shall not
+ invalidate the remainder of the License, and in such case Affirmer hereby
+ affirms that he or she will not (i) exercise any of his or her remaining
+ Copyright and Related Rights in the Work or (ii) assert any associated claims
+ and causes of action with respect to the Work, in either case contrary to
+ Affirmer's express Statement of Purpose.
+
+ 4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+
+ b. Affirmer offers the Work as-is and makes no representations or warranties
+ of any kind concerning the Work, express, implied, statutory or otherwise,
+ including without limitation warranties of title, merchantability, fitness
+ for a particular purpose, non infringement, or the absence of latent or
+ other defects, accuracy, or the present or absence of errors, whether or not
+ discoverable, all to the greatest extent permissible under applicable law.
+
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without limitation
+ any person's Copyright and Related Rights in the Work. Further, Affirmer
+ disclaims responsibility for obtaining any necessary consents, permissions
+ or other rights required for any use of the Work.
+
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to this
+ CC0 or use of the Work.
+
+ For more information, please see
+ <http://creativecommons.org/publicdomain/zero/1.0/>
+ */
+
+#ifndef _UTILS_CIRCULAR_BUFFER_H_
+#define _UTILS_CIRCULAR_BUFFER_H_
+
+#include <stdbool.h>
+
+/*
+ * Opaque circular buffer structure
+ */
+typedef struct circular_buf_t circular_buf_t;
+
+/*
+ * Handle type, the way users interact with the API
+ */
+typedef circular_buf_t *cbuf_handle_t;
+
+/*
+ * Pass in a storage buffer and size, returns a circular buffer handle
+ * Requires: buffer is not NULL, size > 0
+ * Ensures: cbuf has been created and is returned in an empty state
+ */
+cbuf_handle_t circular_buf_init(uint8_t *buffer, size_t size);
+
+/*
+ * Free a circular buffer structure
+ * Requires: cbuf is valid and created by circular_buf_init
+ * Does not free data buffer; owner is responsible for that
+ */
+void circular_buf_free(cbuf_handle_t cbuf);
+
+/*
+ * Reset the circular buffer to empty, head == tail. Data not cleared
+ * Requires: cbuf is valid and created by circular_buf_init
+ */
+void circular_buf_reset(cbuf_handle_t cbuf);
+
+/*
+ * Put version 1 continues to add data if the buffer is full
+ * Old data is overwritten
+ * Requires: cbuf is valid and created by circular_buf_init
+ */
+void circular_buf_put(cbuf_handle_t cbuf, uint8_t data);
+
+/*
+ * Put Version 2 rejects new data if the buffer is full
+ * Requires: cbuf is valid and created by circular_buf_init
+ * Returns 0 on success, -1 if buffer is full
+ */
+int circular_buf_put2(cbuf_handle_t cbuf, uint8_t data);
+
+/*
+ * Retrieve a value from the buffer
+ * Requires: cbuf is valid and created by circular_buf_init
+ * Returns 0 on success, -1 if the buffer is empty
+ */
+int circular_buf_get(cbuf_handle_t cbuf, uint8_t *data);
+
+/*
+ * CHecks if the buffer is empty
+ * Requires: cbuf is valid and created by circular_buf_init
+ * Returns true if the buffer is empty
+ */
+bool circular_buf_empty(cbuf_handle_t cbuf);
+
+/*
+ * Checks if the buffer is full
+ * Requires: cbuf is valid and created by circular_buf_init
+ * Returns true if the buffer is full
+ */
+bool circular_buf_full(cbuf_handle_t cbuf);
+
+/*
+ * Check the capacity of the buffer
+ * Requires: cbuf is valid and created by circular_buf_init
+ * Returns the maximum capacity of the buffer
+ */
+size_t circular_buf_capacity(cbuf_handle_t cbuf);
+
+/*
+ * Check the number of elements stored in the buffer
+ * Requires: cbuf is valid and created by circular_buf_init
+ * Returns the current number of elements in the buffer
+ */
+size_t circular_buf_size(cbuf_handle_t cbuf);
+
+/*
+ TODO: int circular_buf_get_range(circular_buf_t cbuf, uint8_t *data, size_t len);
+ TODO: int circular_buf_put_range(circular_buf_t cbuf, uint8_t * data, size_t len);
+ */
+
+#endif /* _UTILS_CIRCULAR_BUFFER_H_ */
diff --git a/net/osdp/include/osdp/list.h b/net/osdp/include/osdp/list.h
new file mode 100644
index 0000000..45e2aba
--- /dev/null
+++ b/net/osdp/include/osdp/list.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+#ifndef _UTILS_LIST_H_
+#define _UTILS_LIST_H_
+
+#include <stddef.h>
+
+typedef struct node_s node_t;
+
+struct node_s {
+ node_t *next;
+ node_t *prev;
+};
+
+struct list_s {
+ node_t *head;
+ node_t *tail;
+};
+
+typedef struct list_s list_t;
+
+#define OSDP_LIST_FOREACH(list, node) \
+ for (node_t *node = (list)->head; node != NULL; node = node->next)
+
+void list_init(list_t *list);
+void list_append(list_t *list, node_t *node);
+void list_appendleft(list_t *list, node_t *node);
+int list_pop(list_t *list, node_t **node);
+int list_popleft(list_t *list, node_t **node);
+
+void list_remove_node(list_t *list, node_t *node);
+int list_remove_nodes(list_t *list, node_t *start, node_t *end);
+void list_insert_node(list_t *list, node_t *after, node_t *new);
+int list_insert_nodes(list_t *list, node_t *after, node_t *start, node_t *end);
+
+/*--- singly-linked list ---*/
+
+typedef struct snode_s snode_t;
+
+struct snode_s {
+ snode_t *next;
+};
+
+struct slist_s {
+ snode_t *head;
+};
+
+typedef struct slist_s slist_t;
+
+void slist_init(slist_t *list);
+void slist_append(slist_t *list, snode_t *after, snode_t *node);
+void slist_appendleft(slist_t *list, snode_t *node);
+int slist_pop(slist_t *list, snode_t *after, snode_t **node);
+int slist_popleft(slist_t *list, snode_t **node);
+
+int slist_remove_node(slist_t *list, snode_t *node);
+void slist_insert_node(slist_t *list, snode_t *after, snode_t *new);
+
+#endif /* _UTILS_LIST_H_ */
diff --git a/net/osdp/include/osdp/osdp.h b/net/osdp/include/osdp/osdp.h
new file mode 100644
index 0000000..04f06cc
--- /dev/null
+++ b/net/osdp/include/osdp/osdp.h
@@ -0,0 +1,708 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef _OSDP_H_
+#define _OSDP_H_
+
+#include <stdint.h>
+#include "osdp/list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define OSDP_CMD_TEXT_MAX_LEN 32
+#define OSDP_CMD_KEYSET_KEY_MAX_LEN 16
+#define OSDP_CMD_MFG_MAX_DATALEN 64
+#define OSDP_EVENT_MAX_DATALEN 64
+
+/**
+ * @brief OSDP setup flags. See osdp_pd_info_t::flags
+ */
+
+/**
+ * @brief ENFORCE_SECURE: Make security conscious assumptions (see below) where
+ * possible. Fail where these assumptions don't hold.
+ * - Don't allow use of SCBK-D.
+ * - Assume that a KEYSET was successful at an earlier time.
+ *
+ * @note This flag is recommended in production use.
+ */
+#define OSDP_FLAG_ENFORCE_SECURE 0x00010000
+
+/**
+ * @brief When set, the PD would allow one session of secure channel to be
+ * setup with SCBK-D.
+ *
+ * @note In this mode, the PD is in a vulnerable state, the application is
+ * responsible for making sure that the device enters this mode only during
+ * controlled/provisioning-time environments.
+ */
+#define OSDP_FLAG_INSTALL_MODE 0x00020000
+
+/**
+ * @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 on multi-drop networks, more than one PD can share the same
+ * channel (read/write/flush pointers). On such networks, the channel_id
+ * is used to lock a PD to a channel. On multi-drop networks, this `id`
+ * must non-zero and be unique for each bus.
+ */
+ int id;
+
+ /**
+ * @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);
+};
+
+/**
+ * The keep the OSDP internal data strucutres from polluting the exposed
+ * headers, they are typedefed to void before sending them to the upper layers.
+ * This level of abstaction looked reasonable as _technically_ no one should
+ * attempt to modify it outside fo the LibOSDP and their definition may change
+ * at any time.
+ */
+typedef void osdp_t;
+
+/* ------------------------------- */
+/* OSDP Commands */
+/* ------------------------------- */
+
+/**
+ * @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 timer_count Time in units of 100 ms
+ */
+struct osdp_cmd_output {
+ uint8_t output_no;
+ uint8_t control_code;
+ uint16_t timer_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_count 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_count;
+};
+
+/**
+ * @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 control_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 control_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 control_code 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 control_code;
+ 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 address Unit ID to which this PD will respond after the change takes
+ * effect.
+ * @param baud_rate baud rate value 9600/38400/115200
+ */
+struct osdp_cmd_comset {
+ uint8_t address;
+ uint32_t baud_rate;
+};
+
+/**
+ * @brief This command transfers an encryption key from the CP to a PD.
+ *
+ * @param type Type of keys:
+ * - 0x00 - Master Key (This is not part of OSDP Spec, see gh-issue:#42)
+ * - 0x01 – Secure Channel Base Key
+ * @param length Number of bytes of key data - (Key Length in bits + 7) / 8
+ * @param data Key data
+ */
+struct osdp_cmd_keyset {
+ uint8_t type;
+ uint8_t length;
+ uint8_t data[OSDP_CMD_KEYSET_KEY_MAX_LEN];
+};
+
+/**
+ * @brief Manufacturer Specific Commands
+ *
+ * @param vendor_code 3-byte IEEE assigned OUI. Most Significat 8-bits are
+ * unused.
+ * @param command 1-byte manufacturer defined osdp command
+ * @param length Length of command data
+ * @param data Command data
+ */
+struct osdp_cmd_mfg {
+ uint32_t vendor_code;
+ uint8_t command;
+ uint8_t length;
+ uint8_t data[OSDP_CMD_MFG_MAX_DATALEN];
+};
+
+/**
+ * @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_MFG,
+ 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 {
+ enum osdp_cmd_e 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;
+ struct osdp_cmd_mfg mfg;
+ };
+};
+
+/* ------------------------------- */
+/* OSDP Events */
+/* ------------------------------- */
+
+/**
+ * @brief Various card formats that a PD can support. This is sent to CP
+ * when a PD must report a card read.
+ */
+enum osdp_event_cardread_format_e {
+ OSDP_CARD_FMT_RAW_UNSPECIFIED,
+ OSDP_CARD_FMT_RAW_WIEGAND,
+ OSDP_CARD_FMT_ASCII,
+ OSDP_CARD_FMT_SENTINEL
+};
+
+/**
+ * @brief OSDP event cardread
+ *
+ * @param reader_no In context of readers attached to current PD, this number
+ * indicated this number. This is not supported by LibOSDP.
+ * @param format Format of the card being read.
+ * see `enum osdp_event_cardread_format_e`
+ * @param direction Card read direction of PD 0 - Forward; 1 - Backward
+ * @param length Length of card data in bytes or bits depending on `format`
+ * (see note).
+ * @param data Card data of `length` bytes or bits bits depending on `format`
+ * (see note).
+ *
+ * @note When `format` is set to OSDP_CARD_FMT_RAW_UNSPECIFIED or
+ * OSDP_CARD_FMT_RAW_WIEGAND, the length is expressed in bits. OTOH, when it is
+ * set to OSDP_CARD_FMT_ASCII, the length is in bytes. The number of bytes to
+ * read from the `data` field must be interpreted accordingly.
+ */
+struct osdp_event_cardread {
+ int reader_no;
+ enum osdp_event_cardread_format_e format;
+ int direction;
+ int length;
+ uint8_t data[OSDP_EVENT_MAX_DATALEN];
+};
+
+/**
+ * @brief OSDP Event Keypad
+ *
+ * @param reader_no In context of readers attached to current PD, this number
+ * indicated this number. This is not supported by LibOSDP.
+ * @param length Length of keypress data in bytes
+ * @param data keypress data of `length` bytes
+ */
+struct osdp_event_keypress {
+ int reader_no;
+ int length;
+ uint8_t data[OSDP_EVENT_MAX_DATALEN];
+};
+
+/**
+ * @brief OSDP Event Manufacturer Specific Command
+ *
+ * @param vendor_code 3-bytes IEEE assigned OUI of manufacturer
+ * @param length Length of manufacturer data in bytes
+ * @param data manufacturer data of `length` bytes
+ */
+struct osdp_event_mfgrep {
+ uint32_t vendor_code;
+ int command;
+ int length;
+ uint8_t data[OSDP_EVENT_MAX_DATALEN];
+};
+
+/**
+ * @brief OSDP PD Events
+ */
+enum osdp_event_type {
+ OSDP_EVENT_CARDREAD,
+ OSDP_EVENT_KEYPRESS,
+ OSDP_EVENT_MFGREP,
+ OSDP_EVENT_SENTINEL
+};
+
+/**
+ * @brief OSDP Event structure.
+ *
+ * @param id used to select specific event in union. See: enum osdp_event_type
+ * @param keypress keypress event structure
+ * @param cardread cardread event structure
+ * @param mfgrep mfgrep event structure
+ */
+struct osdp_event {
+ enum osdp_event_type type;
+ union {
+ struct osdp_event_keypress keypress;
+ struct osdp_event_cardread cardread;
+ struct osdp_event_mfgrep mfgrep;
+ };
+};
+
+typedef int (*pd_command_callback_t)(void *arg, struct osdp_cmd *c);
+typedef int (*cp_event_callback_t)(void *arg, int addr, struct osdp_event *ev);
+
+/**
+ * @brief OSDP PD Information. This struct is used to describe a PD to LibOSDP.
+ *
+ * @param baud_rate Can be one of 9600/38400/115200
+ * @param address 7 bit PD address. the rest of the bits are ignored. The
+ * special address 0x7F is used for broadcast. So there can be 2^7-1
+ * devices on a multi-drop channel
+ * @param flags Used to modify the way the context is setup. See `OSDP_FLAG_XXX`
+ * @param id Static information that the PD reports to the CP when it received a
+ * `CMD_ID`. These information must be populated by a PD appliication.
+ * @param cap This is a pointer to an array of structures containing the PD'
+ * capabilities. Use { -1, 0, 0 } to terminate the array. This is used
+ * only PD mode of operation
+ * @param channel Communication channel ops structure, containing send/recv
+ * function pointers.
+ */
+typedef struct {
+ int baud_rate;
+ int address;
+ int flags;
+ struct osdp_pd_id id;
+ struct osdp_pd_cap *cap;
+ struct osdp_channel channel;
+ pd_command_callback_t pd_cb;
+ cp_event_callback_t cp_cb;
+} osdp_pd_info_t;
+
+/**
+ * @brief Periodic refresh method. Must be called by the application at least
+ * once every 50ms to meet OSDP timing requirements.
+ *
+ * @param ctx OSDP context
+ */
+void osdp_refresh(osdp_t *ctx);
+
+/* ------------------------------- */
+/* CP Methods */
+/* ------------------------------- */
+
+/**
+ * @brief Periodic refresh method. Must be called by the application at least
+ * once every 50ms to meet OSDP timing requirements.
+ *
+ * @param ctx OSDP context
+ */
+void osdp_cp_refresh(osdp_t *ctx);
+
+/**
+ * @brief Cleanup all osdp resources. The context pointer is no longer valid
+ * after this call.
+ *
+ * @param ctx OSDP context
+ */
+void osdp_cp_teardown(osdp_t *ctx);
+
+/**
+ * @brief Generic command enqueue API.
+ *
+ * @param ctx OSDP context
+ * @param pd PD offset number as in `pd_info_t *`.
+ * @param cmd command pointer. Must be filled by application.
+ *
+ * @retval 0 on success
+ * @retval -1 on failure
+ *
+ * @note This method only adds the command on to a particular PD's command
+ * queue. The command itself can fail due to various reasons.
+ */
+int osdp_cp_send_command(osdp_t *ctx, int pd, struct osdp_cmd *cmd);
+
+/**
+ * @brief Set callback method for CP event notification. This callback is
+ * invoked when the CP receives an event from the PD.
+ *
+ * @param ctx OSDP context
+ * @param cb The callback function's pointer
+ * @param arg A pointer that will be passed as the first argument of `cb`
+ */
+void osdp_cp_set_event_callback(osdp_t *ctx, cp_event_callback_t cb, void *arg);
+
+/* ------------------------------- */
+/* PD Methods */
+/* ------------------------------- */
+
+/**
+ * @brief Periodic refresh method. Must be called by the application at least
+ * once every 50ms to meet OSDP timing requirements.
+ *
+ * @param ctx OSDP context
+ */
+void osdp_pd_refresh(osdp_t *ctx);
+
+/**
+ * @brief Cleanup all osdp resources. The context pointer is no longer valid
+ * after this call.
+ *
+ * @param ctx OSDP context
+ */
+void osdp_pd_teardown(osdp_t *ctx);
+
+/**
+ * @brief Set PD's capabilities
+ *
+ * @param ctx OSDP context
+ * @param cap pointer to array of cap (`struct osdp_pd_cap`) terminated by a
+ * capability with cap->function_code set to 0.
+ */
+void osdp_pd_set_capabilities(osdp_t *ctx, struct osdp_pd_cap *cap);
+
+/**
+ * @brief Set callback method for PD command notification. This callback is
+ * invoked when the PD receives a command from the CP. This function must
+ * return:
+ * - 0 if LibOSDP must send a `osdp_ACK` response
+ * - -ve if LibOSDP must send a `osdp_NAK` response
+ * - +ve and modify the passed `struct osdp_cmd *cmd` if LibOSDP must send a
+ * specific response. This is useful for sending manufacturer specific reply
+ * ``osdp_MFGREP``.
+ *
+ * @param ctx OSDP context
+ * @param cb The callback function's pointer
+ * @param arg A pointer that will be passed as the first argument of `cb`
+ */
+void osdp_pd_set_command_callback(osdp_t *ctx, pd_command_callback_t cb,
+ void *arg);
+
+/**
+ * @brief API to notify PD events to CP. These events are sent to the CP as an
+ * alternate response to a POLL command.
+ *
+ * @param ctx OSDP context
+ * @param event pointer to event struct. Must be filled by application.
+ *
+ * @retval 0 on success
+ * @retval -1 on failure
+ */
+int osdp_pd_notify_event(osdp_t *ctx, struct osdp_event *event);
+
+/* ------------------------------- */
+/* Common Methods */
+/* ------------------------------- */
+
+/**
+ * @brief Get a bit mask of number of PD that are online currently.
+ *
+ * @return Bit-Mask (max 32 PDs)
+ */
+uint32_t osdp_get_status_mask(osdp_t *ctx);
+
+/**
+ * @brief Get a bit mask of number of PD that are online and have an active
+ * secure channel currently.
+ *
+ * @return Bit-Mask (max 32 PDs)
+ */
+uint32_t osdp_get_sc_status_mask(osdp_t *ctx);
+
+/**
+ * @brief Initialize context for OSDP library.
+ */
+void osdp_init(osdp_pd_info_t *info, uint8_t *scbk);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _OSDP_H_ */
diff --git a/net/osdp/include/osdp/osdp_common.h b/net/osdp/include/osdp/osdp_common.h
new file mode 100644
index 0000000..8fee694
--- /dev/null
+++ b/net/osdp/include/osdp/osdp_common.h
@@ -0,0 +1,394 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef _OSDP_COMMON_H_
+#define _OSDP_COMMON_H_
+
+#include "osdp/osdp.h"
+#include "osdp/queue.h"
+#include "osdp/slab.h"
+#include "osdp/osdp_utils.h"
+
+#ifndef NULL
+#define NULL ((void *)0)
+#endif
+
+#define ARG_UNUSED(x) (void)(x)
+
+#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)
+#define AES_PAD_LEN(x) ((x + 16 - 1) & (~(16 - 1)))
+#define NUM_PD(ctx) (TO_CP(ctx)->num_pd)
+
+/* Unused type only to estmate ephemeral_data size */
+union osdp_ephemeral_data {
+ struct osdp_cmd cmd;
+ struct osdp_event event;
+};
+#define OSDP_EPHEMERAL_DATA_MAX_LEN sizeof(union osdp_ephemeral_data)
+
+/**
+ * @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*/
+
+/* Global flags */
+#define FLAG_CP_MODE 0x00000001 /* Set when initialized as CP */
+#define FLAG_SC_DISABLED 0x00000002 /* cp_setup with master_key=NULL */
+
+/* PD State Flags */
+#define PD_FLAG_MASK 0x0000FFFF /* only 16 bits are for 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_AWAIT_RESP 0x00000010 /* set after command is sent */
+#define PD_FLAG_SKIP_SEQ_CHECK 0x00000020 /* disable seq checks (debug) */
+#define PD_FLAG_SC_USE_SCBKD 0x00000040 /* in this SC attempt, use SCBKD */
+#define PD_FLAG_SC_ACTIVE 0x00000080 /* secure channel is active */
+#define PD_FLAG_SC_SCBKD_DONE 0x00000100 /* SCBKD check is done */
+#define PD_FLAG_PD_MODE 0x00000200 /* device is setup as PD */
+#define PD_FLAG_CHN_SHARED 0x00000400 /* PD's channel is shared */
+#define PD_FLAG_PKT_SKIP_MARK 0x00000800 /* CONFIG_OSDP_SKIP_MARK_BYTE */
+#define PD_FLAG_PKT_HAS_MARK 0x00001000 /* Packet has mark byte */
+
+#define osdp_dump(b, l, f, ...) hexdump(b, l, f, __VA_ARGS__)
+
+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 This PD does not support the security block that was received
+ */
+ OSDP_PD_NAK_SEQ_NUM,
+ /**
+ * @brief Secure Channel is not supported by PD
+ */
+ 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_PROCESS_CMD,
+ OSDP_PD_STATE_SEND_REPLY,
+ OSDP_PD_STATE_ERR,
+};
+
+enum osdp_cp_phy_state_e {
+ OSDP_CP_PHY_STATE_IDLE,
+ OSDP_CP_PHY_STATE_SEND_CMD,
+ OSDP_CP_PHY_STATE_REPLY_WAIT,
+ OSDP_CP_PHY_STATE_WAIT,
+ OSDP_CP_PHY_STATE_ERR,
+};
+
+enum osdp_state_e {
+ OSDP_CP_STATE_INIT,
+ OSDP_CP_STATE_IDREQ,
+ OSDP_CP_STATE_CAPDET,
+ OSDP_CP_STATE_SC_INIT,
+ OSDP_CP_STATE_SC_CHLNG,
+ OSDP_CP_STATE_SC_SCRYPT,
+ OSDP_CP_STATE_SET_SCBK,
+ OSDP_CP_STATE_ONLINE,
+ OSDP_CP_STATE_OFFLINE
+};
+
+enum osdp_pkt_errors_e {
+ OSDP_ERR_PKT_NONE = 0,
+ OSDP_ERR_PKT_FMT = -1,
+ OSDP_ERR_PKT_WAIT = -2,
+ OSDP_ERR_PKT_SKIP = -3,
+ OSDP_ERR_PKT_CHECK = -4
+};
+
+struct osdp_slab {
+ int block_size;
+ int num_blocks;
+ int free_blocks;
+ uint8_t *blob;
+};
+
+struct osdp_secure_channel {
+ uint8_t scbk[16];
+ uint8_t s_enc[16];
+ uint8_t s_mac1[16];
+ uint8_t s_mac2[16];
+ uint8_t r_mac[16];
+ uint8_t c_mac[16];
+ uint8_t cp_random[8];
+ uint8_t pd_random[8];
+ uint8_t pd_client_uid[8];
+ uint8_t cp_cryptogram[16];
+ uint8_t pd_cryptogram[16];
+};
+
+struct osdp_queue {
+ queue_t queue;
+ slab_t slab;
+};
+
+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 */
+ int state;
+ int phy_state;
+
+ int64_t tstamp;
+ int64_t sc_tstamp;
+ uint8_t rx_buf[MYNEWT_VAL(OSDP_UART_BUFFER_LENGTH)];
+ int rx_buf_len;
+ int64_t phy_tstamp;
+
+ int cmd_id;
+ int reply_id;
+ uint8_t ephemeral_data[OSDP_EPHEMERAL_DATA_MAX_LEN];
+
+ union {
+ struct osdp_queue cmd;
+ struct osdp_queue event;
+ };
+
+ struct osdp_channel channel;
+ struct osdp_secure_channel sc;
+ void *command_callback_arg;
+ pd_command_callback_t command_callback;
+};
+
+struct osdp_cp {
+ void *__parent;
+ int num_pd;
+ struct osdp_pd *current_pd; /* current operational pd's pointer */
+ int pd_offset; /* current pd's offset into ctx->pd */
+ int *channel_lock;
+ void *event_callback_arg;
+ cp_event_callback_t event_callback;
+};
+
+struct osdp {
+ int magic;
+ uint32_t flags;
+ struct osdp_cp *cp;
+ struct osdp_pd *pd;
+ uint8_t sc_master_key[16];
+};
+
+struct osdp_ctx {
+ struct osdp ctx;
+ struct osdp_cp cp_ctx;
+ struct osdp_pd pd_ctx[MYNEWT_VAL(OSDP_NUM_CONNECTED_PD)];
+ int ch_locks_ctx[MYNEWT_VAL(OSDP_NUM_CONNECTED_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_check_packet(struct osdp_pd *pd, uint8_t *buf, int len,
+ int *one_pkt_len);
+int osdp_phy_decode_packet(struct osdp_pd *p, uint8_t *buf, int len,
+ uint8_t **pkt_start);
+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);
+
+/* from osdp.c */
+struct osdp *osdp_get_ctx();
+
+/* from osdp_cp.c */
+#if MYNEWT_VAL(OSDP_MODE_CP)
+int osdp_extract_address(int *address);
+#endif
+
+void osdp_encrypt(uint8_t *key, uint8_t *iv, uint8_t *data, int len);
+void osdp_decrypt(uint8_t *key, uint8_t *iv, uint8_t *data, int len);
+
+/* from osdp_sc.c */
+void osdp_compute_scbk(struct osdp_pd *pd, uint8_t *master_key, uint8_t *scbk);
+void osdp_compute_session_keys(struct osdp *ctx);
+void osdp_compute_cp_cryptogram(struct osdp_pd *pd);
+int osdp_verify_cp_cryptogram(struct osdp_pd *pd);
+void osdp_compute_pd_cryptogram(struct osdp_pd *pd);
+int osdp_verify_pd_cryptogram(struct osdp_pd *pd);
+void osdp_compute_rmac_i(struct osdp_pd *pd);
+int osdp_decrypt_data(struct osdp_pd *pd, int is_cmd, uint8_t *data, int len);
+int osdp_encrypt_data(struct osdp_pd *pd, int is_cmd, uint8_t *data, int len);
+int osdp_compute_mac(struct osdp_pd *pd, int is_cmd,
+ const uint8_t *data, int len);
+void osdp_sc_init(struct osdp_pd *pd);
+void osdp_get_rand(uint8_t *buf, int len);
+
+/**
+ * @brief This method is used to setup a device in PD mode. Application must
+ * store the returned context poiter and pass it back to all OSDP functions
+ * intact.
+ *
+ * @param info Pointer to iinfo struct populated by application.
+ * @param scbk 16 byte Secure Channel Base Key. If this field is NULL PD is set
+ * to "Install Mode".
+ *
+ * @return OSDP Context on success
+ * @return NULL on errors
+ */
+osdp_t *osdp_pd_setup(struct osdp_ctx *osdp_ctx, osdp_pd_info_t *info, uint8_t *scbk);
+
+/**
+ * @brief This method is used to setup a device in CP mode. Application must
+ * store the returned context poiter and pass it back to all OSDP functions
+ * intact.
+ *
+ * @param num_pd Number of PDs connected to this CP. The `osdp_pd_info_t *` is
+ * treated as an array of length num_pd.
+ * @param info Pointer to info struct populated by application.
+ * @param scbk 16 byte Secure Channel Base Key. If this field is NULL PD is set
+ * to "Install Mode".
+ *
+ * @return OSDP Context on success
+ * @return NULL on errors
+ */
+osdp_t *osdp_cp_setup(struct osdp_ctx *osdp_ctx, int num_pd, osdp_pd_info_t *info,
+ uint8_t *master_key);
+
+#endif /* _OSDP_COMMON_H_ */
diff --git a/net/osdp/include/osdp/osdp_hooks.h b/net/osdp/include/osdp/osdp_hooks.h
new file mode 100644
index 0000000..d9192ad
--- /dev/null
+++ b/net/osdp/include/osdp/osdp_hooks.h
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * @brief Hook function to supply random bytes.
+ *
+ * @param out Output buffer for random data.
+ * @param out_len Output buffer length.
+ *
+ * @return number of bytes read, could be less than requested based on implementation.
+ */
+__attribute__((weak)) size_t osdp_hook_crypto_random_bytes(uint8_t *out, size_t out_len);
diff --git a/net/osdp/include/osdp/osdp_utils.h b/net/osdp/include/osdp/osdp_utils.h
new file mode 100644
index 0000000..7ef599f
--- /dev/null
+++ b/net/osdp/include/osdp/osdp_utils.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef _OSDP_UTILS_H_
+#define _OSDP_UTILS_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifndef NULL
+#define NULL ((void *)0)
+#endif
+
+#ifndef TRUE
+#define TRUE (1)
+#endif
+
+#ifndef FALSE
+#define FALSE (0)
+#endif
+
+#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)
+
+#define ARG_UNUSED(x) (void)(x)
+
+#define STR(x) #x
+#define XSTR(x) STR(x)
+
+#define MATH_MOD(a, b) (((a % b) + b) % b)
+
+#define IS_POW2(n) ((n) & ((n) - 1))
+
+#define ARRAY_SIZEOF(x) \
+ (sizeof(x) / sizeof(x[0]))
+
+#define ARRAY_BASE(ptr, type, offset) \
+ ((char *)(ptr)) - ((sizeof(type)) * offset)
+
+#define OFFSET_OF(type, field) (size_t)(&((type *)(0))->field)
+
+#define OSDP_CONTAINER_OF(ptr, type, field) \
+ ((type *)(((char *)(ptr)) - OFFSET_OF(type, field)))
+
+/*
+ #define MAX(a,b) ({ \
+ __typeof__ (a) _a = (a); \
+ __typeof__ (b) _b = (b); \
+ _a > _b ? _a : _b; \
+ })
+
+ #define MIN(a,b) ({ \
+ __typeof__ (a) _a = (a); \
+ __typeof__ (b) _b = (b); \
+ _a > _b ? _b : _a; \
+ })
+ */
+
+/* config_enabled() from the kernel */
+#define __IS_ENABLED1(x) __IS_ENABLED2(__XXXX ## x)
+#define __XXXX1 __YYYY,
+#define __IS_ENABLED2(y) __IS_ENABLED3(y 1, 0)
+#define __IS_ENABLED3(_i, val, ...) val
+
+#define IS_ENABLED(x) __IS_ENABLED1(x)
+
+/* gcc attribute shorthands */
+#ifndef __fallthrough
+#if __GNUC__ >= 7
+#define __fallthrough __attribute__((fallthrough))
+#else
+#define __fallthrough
+#endif
+#endif
+
+#ifndef __packed
+#define __packed __attribute__((__packed__))
+#endif
+
+/**
+ * @brief Check p to be not NULL before calling safe_free()
+ *
+ * @param p pointer to be free-ed
+ */
+void safe_free(void *p);
+
+/**
+ * @brief safe_* variants of the standard alloc methods do a check on the
+ * returned pointer and will call exit() if any of the returned NULL. The
+ * safe_free() method will check if pointer is NULL before calling safe_free().
+ */
+void safe_free(void *p);
+void *safe_malloc(size_t size);
+void *safe_calloc(size_t count, size_t size);
+void *safe_realloc(void *data, size_t size);
+void *safe_strdup(const char *s);
+void *safe_realloc_zero(void *data, size_t old_size, size_t new_size);
+
+/**
+ * @brief Rounds up 32-bit v to nearest power of 2. If v is already a power
+ * of 2 it is returned unmodified.
+ */
+uint32_t round_up_pow2(uint32_t v);
+
+/**
+ * @brief Dumps an array of bytes in HEX and ASCII formats for debugging. `head`
+ * is string that is printed before the actual bytes are dumped.
+ *
+ * Example:
+ * int len;
+ * uint8_t data[MAX_LEN];
+ * len = get_data_from_somewhere(data, MAX_LEN);
+ * hexdump(data, len, "Data From Somewhere");
+ */
+void hexdump(const uint8_t *data, size_t len, const char *fmt, ...);
+
+/**
+ * @brief Convert a single character into a hexadecimal nibble.
+ *
+ * @param c The character to convert
+ * @param x The address of storage for the converted number.
+ *
+ * @return Zero on success or (negative) error code otherwise.
+ */
+int char2hex(char c, uint8_t *x);
+
+/**
+ * @brief Convert a hexadecimal string into a binary array.
+ *
+ * @param hex The hexadecimal string to convert
+ * @param hexlen The length of the hexadecimal string to convert.
+ * @param buf Address of where to store the binary data
+ * @param buflen Size of the storage area for binary data
+ *
+ * @return The length of the binary array, or 0 if an error occurred.
+ */
+size_t hex2bin(const char *hex, size_t hexlen, uint8_t *buf, size_t buflen);
+
+#endif /* _OSDP_UTILS_H_ */
diff --git a/net/osdp/include/osdp/queue.h b/net/osdp/include/osdp/queue.h
new file mode 100644
index 0000000..f12ae7f
--- /dev/null
+++ b/net/osdp/include/osdp/queue.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+#ifndef _UTILS_QUEUE_H_
+#define _UTILS_QUEUE_H_
+
+#include "osdp/list.h"
+
+#define queue_node_t node_t
+
+typedef struct {
+ list_t list;
+} queue_t;
+
+void queue_init(queue_t *queue);
+void queue_enqueue(queue_t *queue, queue_node_t *node);
+int queue_dequeue(queue_t *queue, queue_node_t **node);
+int queue_peek_last(queue_t *queue, queue_node_t **node);
+int queue_peek_first(queue_t *queue, queue_node_t **node);
+
+#endif /* _UTILS_QUEUE_H_ */
diff --git a/net/osdp/include/osdp/slab.h b/net/osdp/include/osdp/slab.h
new file mode 100644
index 0000000..e9e325e
--- /dev/null
+++ b/net/osdp/include/osdp/slab.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+#ifndef _UTILS_SLAB_H_
+#define _UTILS_SLAB_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "osdp/osdp_utils.h"
+
+typedef struct {
+ uint8_t *blob;
+ size_t size;
+ size_t count;
+ uint32_t *alloc_map;
+} slab_t;
+
+/**
+ * @brief Macro helper for seting up global/local non-alloced instance of
+ * stab_t. This is useful when dynamic allocation as done by slab_init() is
+ * not possible/acceptable. A stab_t instance created with this macro, does
+ * not need to slab_del().
+ *
+ * @note When using this macro, the blocks are not aligned and the user must
+ * make sure the `type` param is properly aligned.
+ */
+#define SLAB_DEF(name, type, num) \
+ uint8_t name ## _slab_blob[sizeof(type) * num]; \
+ uint32_t name ## _slab_alloc_map[(num + 31) / 32] = { 0 }; \
+ slab_t name = { \
+ .blob = name ## _slab_blob, \
+ .size = sizeof(type), \
+ .count = num, \
+ .alloc_map = name ## _slab_alloc_map, \
+ };
+
+/**
+ * @brief Initializes a resource pool of `count` slabs of at-least
+ * `size` bytes long. This method dynamically allocates resources
+ * and must be paired with a call to slab_del() when the slab_t is
+ * no longer needed.
+ *
+ * @return -1 on errors
+ * @return 0 on success
+ */
+int slab_init(slab_t *slab, size_t size, size_t count);
+
+/**
+ * @brief Releases the entire allocation pool held by slab_t. All
+ * previously issued slabs are invalid after this call.
+ */
+void slab_del(slab_t *slab);
+
+/**
+ * @brief Allocates a slab of memory from the resource pool held at
+ * slab_t. The allocated block is at least `size` bytes large is
+ * guaranteed to be aligned to a power the nearest power of 2.
+ *
+ * @return -1 on errors
+ * @return 0 on success
+ */
+int slab_alloc(slab_t *slab, void **p);
+
+/**
+ * @brief Releases a slab that was previously allocated by a call
+ * to slab_alloc(). This method can fail if the pointer passed did
+ * not belong to the slab pool of slab_t.
+ *
+ * @return -1 on errors
+ * @return 0 on success
+ */
+int slab_free(slab_t *slab, void *block);
+
+#endif /* _UTILS_SLAB_H_ */
diff --git a/net/osdp/pkg.yml b/net/osdp/pkg.yml
new file mode 100644
index 0000000..1872c5a
--- /dev/null
+++ b/net/osdp/pkg.yml
@@ -0,0 +1,28 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+pkg.name: net/osdp
+pkg.description: "OSDP implementation"
+pkg.author: ""
+pkg.homepage: ""
+pkg.keywords:
+ - osdp
+
+pkg.deps:
+ - "@apache-mynewt-core/sys/log/modlog"
+ - "@apache-mynewt-core/util/crc"
diff --git a/net/osdp/src/circular_buffer.c b/net/osdp/src/circular_buffer.c
new file mode 100644
index 0000000..d6b3ccf
--- /dev/null
+++ b/net/osdp/src/circular_buffer.c
@@ -0,0 +1,278 @@
+/*
+ CC0 1.0 Universal
+
+ Statement of Purpose
+
+ The laws of most jurisdictions throughout the world automatically confer
+ exclusive Copyright and Related Rights (defined below) upon the creator and
+ subsequent owner(s) (each and all, an "owner") of an original work of
+ authorship and/or a database (each, a "Work").
+
+ Certain owners wish to permanently relinquish those rights to a Work for the
+ purpose of contributing to a commons of creative, cultural and scientific
+ works ("Commons") that the public can reliably and without fear of later
+ claims of infringement build upon, modify, incorporate in other works, reuse
+ and redistribute as freely as possible in any form whatsoever and for any
+ purposes, including without limitation commercial purposes. These owners may
+ contribute to the Commons to promote the ideal of a free culture and the
+ further production of creative, cultural and scientific works, or to gain
+ reputation or greater distribution for their Work in part through the use and
+ efforts of others.
+
+ For these and/or other purposes and motivations, and without any expectation
+ of additional consideration or compensation, the person associating CC0 with a
+ Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
+ and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
+ and publicly distribute the Work under its terms, with knowledge of his or her
+ Copyright and Related Rights in the Work and the meaning and intended legal
+ effect of CC0 on those rights.
+
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
+ protected by copyright and related or neighboring rights ("Copyright and
+ Related Rights"). Copyright and Related Rights include, but are not limited
+ to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display, communicate,
+ and translate a Work;
+
+ ii. moral rights retained by the original author(s) and/or performer(s);
+
+ iii. publicity and privacy rights pertaining to a person's image or likeness
+ depicted in a Work;
+
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+
+ v. rights protecting the extraction, dissemination, use and reuse of data in
+ a Work;
+
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation thereof,
+ including any amended or successor version of such directive); and
+
+ vii. other similar, equivalent or corresponding rights throughout the world
+ based on applicable law or treaty, and any national implementations thereof.
+
+ 2. Waiver. To the greatest extent permitted by, but not in contravention of,
+ applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+ unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
+ and Related Rights and associated claims and causes of action, whether now
+ known or unknown (including existing as well as future claims and causes of
+ action), in the Work (i) in all territories worldwide, (ii) for the maximum
+ duration provided by applicable law or treaty (including future time
+ extensions), (iii) in any current or future medium and for any number of
+ copies, and (iv) for any purpose whatsoever, including without limitation
+ commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
+ the Waiver for the benefit of each member of the public at large and to the
+ detriment of Affirmer's heirs and successors, fully intending that such Waiver
+ shall not be subject to revocation, rescission, cancellation, termination, or
+ any other legal or equitable action to disrupt the quiet enjoyment of the Work
+ by the public as contemplated by Affirmer's express Statement of Purpose.
+
+ 3. Public License Fallback. Should any part of the Waiver for any reason be
+ judged legally invalid or ineffective under applicable law, then the Waiver
+ shall be preserved to the maximum extent permitted taking into account
+ Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
+ is so judged Affirmer hereby grants to each affected person a royalty-free,
+ non transferable, non sublicensable, non exclusive, irrevocable and
+ unconditional license to exercise Affirmer's Copyright and Related Rights in
+ the Work (i) in all territories worldwide, (ii) for the maximum duration
+ provided by applicable law or treaty (including future time extensions), (iii)
+ in any current or future medium and for any number of copies, and (iv) for any
+ purpose whatsoever, including without limitation commercial, advertising or
+ promotional purposes (the "License"). The License shall be deemed effective as
+ of the date CC0 was applied by Affirmer to the Work. Should any part of the
+ License for any reason be judged legally invalid or ineffective under
+ applicable law, such partial invalidity or ineffectiveness shall not
+ invalidate the remainder of the License, and in such case Affirmer hereby
+ affirms that he or she will not (i) exercise any of his or her remaining
+ Copyright and Related Rights in the Work or (ii) assert any associated claims
+ and causes of action with respect to the Work, in either case contrary to
+ Affirmer's express Statement of Purpose.
+
+ 4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+
+ b. Affirmer offers the Work as-is and makes no representations or warranties
+ of any kind concerning the Work, express, implied, statutory or otherwise,
+ including without limitation warranties of title, merchantability, fitness
+ for a particular purpose, non infringement, or the absence of latent or
+ other defects, accuracy, or the present or absence of errors, whether or not
+ discoverable, all to the greatest extent permissible under applicable law.
+
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without limitation
+ any person's Copyright and Related Rights in the Work. Further, Affirmer
+ disclaims responsibility for obtaining any necessary consents, permissions
+ or other rights required for any use of the Work.
+
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to this
+ CC0 or use of the Work.
+
+ For more information, please see
+ <http://creativecommons.org/publicdomain/zero/1.0/>
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <assert.h>
+
+#include "osdp/circular_buffer.h"
+
+/* The definition of our circular buffer structure is hidden from the user */
+struct circular_buf_t {
+ uint8_t *buffer;
+ size_t head;
+ size_t tail;
+ size_t max; /*of the buffer */
+ bool full;
+};
+
+static void
+advance_pointer(cbuf_handle_t cbuf)
+{
+ assert(cbuf);
+
+ if (cbuf->full) {
+ cbuf->tail = (cbuf->tail + 1) % cbuf->max;
+ }
+
+ cbuf->head = (cbuf->head + 1) % cbuf->max;
+
+ /* We mark full because we will advance tail on the next time around */
+ cbuf->full = (cbuf->head == cbuf->tail);
+}
+
+static void
+retreat_pointer(cbuf_handle_t cbuf)
+{
+ assert(cbuf);
+
+ cbuf->full = false;
+ cbuf->tail = (cbuf->tail + 1) % cbuf->max;
+}
+
+cbuf_handle_t
+circular_buf_init(uint8_t *buffer, size_t size)
+{
+ assert(buffer && size);
+
+ cbuf_handle_t cbuf = malloc(sizeof(circular_buf_t));
+ assert(cbuf);
+
+ cbuf->buffer = buffer;
+ cbuf->max = size;
+ circular_buf_reset(cbuf);
+
+ assert(circular_buf_empty(cbuf));
+
+ return cbuf;
+}
+
+void
+circular_buf_free(cbuf_handle_t cbuf)
+{
+ assert(cbuf);
+ free(cbuf);
+}
+
+void
+circular_buf_reset(cbuf_handle_t cbuf)
+{
+ assert(cbuf);
+
+ cbuf->head = 0;
+ cbuf->tail = 0;
+ cbuf->full = false;
+}
+
+size_t
+circular_buf_size(cbuf_handle_t cbuf)
+{
+ assert(cbuf);
+
+ size_t size = cbuf->max;
+
+ if (!cbuf->full) {
+ if (cbuf->head >= cbuf->tail) {
+ size = (cbuf->head - cbuf->tail);
+ } else {
+ size = (cbuf->max + cbuf->head - cbuf->tail);
+ }
+
+ }
+
+ return size;
+}
+
+size_t
+circular_buf_capacity(cbuf_handle_t cbuf)
+{
+ assert(cbuf);
+
+ return cbuf->max;
+}
+
+void
+circular_buf_put(cbuf_handle_t cbuf, uint8_t data)
+{
+ assert(cbuf && cbuf->buffer);
+
+ cbuf->buffer[cbuf->head] = data;
+
+ advance_pointer(cbuf);
+}
+
+int
+circular_buf_put2(cbuf_handle_t cbuf, uint8_t data)
+{
+ int r = -1;
+
+ assert(cbuf && cbuf->buffer);
+
+ if (!circular_buf_full(cbuf)) {
+ cbuf->buffer[cbuf->head] = data;
+ advance_pointer(cbuf);
+ r = 0;
+ }
+
+ return r;
+}
+
+int
+circular_buf_get(cbuf_handle_t cbuf, uint8_t *data)
+{
+ assert(cbuf && data && cbuf->buffer);
+
+ int r = -1;
+
+ if (!circular_buf_empty(cbuf)) {
+ *data = cbuf->buffer[cbuf->tail];
+ retreat_pointer(cbuf);
+
+ r = 0;
+ }
+
+ return r;
+}
+
+bool
+circular_buf_empty(cbuf_handle_t cbuf)
+{
+ assert(cbuf);
+
+ return (!cbuf->full && (cbuf->head == cbuf->tail));
+}
+
+bool
+circular_buf_full(cbuf_handle_t cbuf)
+{
+ assert(cbuf);
+
+ return cbuf->full;
+}
diff --git a/net/osdp/src/list.c b/net/osdp/src/list.c
new file mode 100644
index 0000000..6529111
--- /dev/null
+++ b/net/osdp/src/list.c
@@ -0,0 +1,328 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdbool.h>
+
+#include "osdp/list.h"
+
+void
+list_init(list_t *list)
+{
+ list->head = NULL;
+ list->tail = NULL;
+}
+
+void
+list_append(list_t *list, node_t *node)
+{
+ node->prev = list->tail;
+ node->next = NULL;
+ if (list->tail) {
+ list->tail->next = node;
+ }
+ list->tail = node;
+ if (!list->head) {
+ list->head = node;
+ }
+}
+
+void
+list_appendleft(list_t *list, node_t *node)
+{
+ node->prev = NULL;
+ node->next = list->head;
+ if (list->head) {
+ list->head->prev = node;
+ }
+ list->head = node;
+ if (!list->tail) {
+ list->tail = node;
+ }
+}
+
+int
+list_pop(list_t *list, node_t **node)
+{
+ if (!list->tail) {
+ return -1;
+ }
+ *node = list->tail;
+ list->tail = list->tail->prev;
+ if (list->tail) {
+ list->tail->next = NULL;
+ } else {
+ list->head = NULL;
+ }
+ return 0;
+}
+
+int
+list_popleft(list_t *list, node_t **node)
+{
+ if (!list->head) {
+ return -1;
+ }
+ *node = list->head;
+ list->head = list->head->next;
+ if (list->head) {
+ list->head->prev = NULL;
+ } else {
+ list->tail = NULL;
+ }
+ return 0;
+}
+
+void
+list_remove_node(list_t *list, node_t *node)
+{
+ if (node->prev == NULL) {
+ /* remove head */
+ list->head = node->next;
+ if (list->head == NULL) {
+ list->tail = NULL;
+ } else {
+ list->head->prev = NULL;
+ }
+ } else if (node->next == NULL) {
+ /* remove tail */
+ list->tail = node->prev;
+ if (list->tail == NULL) {
+ list->head = NULL;
+ } else {
+ list->tail->next = NULL;
+ }
+ } else {
+ /* remove in-between */
+ node->next->prev = node->prev;
+ node->prev->next = node->next;
+ }
+}
+
+node_t *
+list_find_node(list_t *list, node_t *node)
+{
+ node_t *p;
+
+ p = list->head;
+ while (p && p != node) {
+ p = p->next;
+ }
+ return p;
+}
+
+bool
+list_check_links(node_t *p1, node_t *p2)
+{
+ node_t *p1_prev, *p2_next;
+
+ if (p1 == NULL || p2 == NULL) {
+ return false;
+ }
+
+ if (p1 == p2) {
+ return true;
+ }
+
+ p1_prev = p1->prev;
+ p2_next = p2->next;
+
+ while (p1 && p2 && p1 != p2 && p1->next != p2->prev &&
+ p1->prev == p1_prev && p2->next == p2_next) {
+ p1_prev = p1;
+ p1 = p1->next;
+
+ p2_next = p2;
+ p2 = p2->prev;
+ }
+
+ return (p1 && p2) && (p1 == p2 || p1->next == p2->prev);
+}
+
+int
+list_remove_nodes(list_t *list, node_t *start, node_t *end)
+{
+ /* check if start in list and the range [start:end] is a valid list */
+ if (list_find_node(list, start) == NULL ||
+ list_check_links(start, end) == 0) {
+ return -1;
+ }
+
+ if (start->prev == NULL) {
+ /* remove from head */
+ list->head = end->next;
+ if (list->head == NULL) {
+ list->head = NULL;
+ } else {
+ list->head->prev = NULL;
+ }
+ } else if (end->next == NULL) {
+ /* remove upto tail */
+ start->prev->next = NULL;
+ list->tail = start->prev;
+ } else {
+ /* remove in-between */
+ start->prev->next = end->next;
+ end->next->prev = start->prev;
+ }
+
+ return 0;
+}
+
+void
+list_insert_node(list_t *list, node_t *after, node_t *new)
+{
+ node_t *next;
+
+ if (after == NULL) {
+ /* insert at head */
+ next = list->head;
+ list->head = new;
+ } else {
+ next = after->next;
+ after->next = new;
+ }
+ new->prev = after;
+ new->next = next;
+ next->prev = new;
+}
+
+int
+list_insert_nodes(list_t *list, node_t *after, node_t *start, node_t *end)
+{
+ node_t *next;
+
+ /* check if nodes is a valid list */
+ if (list_check_links(start, end) == 0) {
+ return -1;
+ }
+
+ if (list->head == NULL) {
+ /* If list is empty, the nodes becomes the list */
+ list->head = start;
+ list->tail = end;
+ } else if (after == NULL) {
+ /* if 'after' node is not given prepend the nodes to list */
+ end->next = list->head;
+ list->head = start;
+ list->head->prev = NULL;
+ } else {
+ /* insert nodes into list after the 'after' node */
+ next = after->next;
+ after->next = start;
+ start->prev = after;
+ end->next = next;
+ if (next != NULL) {
+ next->prev = end;
+ } else {
+ list->tail = end;
+ }
+ }
+
+ return 0;
+}
+
+/*--- singly-linked list ---*/
+
+void
+slist_init(slist_t *list)
+{
+ list->head = NULL;
+}
+
+void
+slist_append(slist_t *list, snode_t *after, snode_t *node)
+{
+ snode_t *p;
+
+ p = after ? after : list->head;
+ if (p == NULL) {
+ list->head = node;
+ } else {
+ while (p && p->next) {
+ p = p->next;
+ }
+ p->next = node;
+ }
+ node->next = NULL;
+}
+
+void
+slist_appendleft(slist_t *list, snode_t *node)
+{
+ node->next = list->head;
+ list->head = node;
+}
+
+int
+slist_pop(slist_t *list, snode_t *after, snode_t **node)
+{
+ snode_t *n1, *n2;
+
+ if (list->head == NULL) {
+ return -1;
+ }
+ if (list->head->next == NULL) {
+ *node = list->head;
+ list->head = NULL;
+ } else {
+ n1 = after ? after : list->head;
+ n2 = n1->next;
+ while (n2 && n2->next) {
+ n1 = n1->next;
+ n2 = n2->next;
+ }
+ n1->next = NULL;
+ *node = n2;
+ }
+ return 0;
+}
+
+int
+slist_popleft(slist_t *list, snode_t **node)
+{
+ if (list->head == NULL) {
+ return -1;
+ }
+
+ *node = list->head;
+ list->head = list->head->next;
+ return 0;
+}
+
+int
+slist_remove_node(slist_t *list, snode_t *node)
+{
+ snode_t *prev = NULL, *cur;
+
+ cur = list->head;
+ while (cur && cur != node) {
+ prev = cur;
+ cur = cur->next;
+ }
+ if (cur == NULL) {
+ return -1;
+ }
+ if (prev == NULL) {
+ list->head = cur->next;
+ } else {
+ prev->next = cur->next;
+ }
+ return 0;
+}
+
+void
+slist_insert_node(slist_t *list, snode_t *after, snode_t *new)
+{
+ if (after == NULL) {
+ /* same as append left */
+ new->next = list->head;
+ list->head = new;
+ } else {
+ /* assert after in list here? */
+ new->next = after->next;
+ after->next = new;
+ }
+}
diff --git a/net/osdp/src/osdp.c b/net/osdp/src/osdp.c
new file mode 100644
index 0000000..6acb9a3
--- /dev/null
+++ b/net/osdp/src/osdp.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "modlog/modlog.h"
+#include "uart/uart.h"
+#include "tinycrypt/aes.h"
+
+#include "osdp/circular_buffer.h"
+#include "osdp/osdp_common.h"
+
+#define OSDP_REFRESH_INTERVAL (OS_TICKS_PER_SEC / 20)
+
+struct osdp_device {
+ cbuf_handle_t rx_buf;
+ cbuf_handle_t tx_buf;
+ uint8_t rx_fbuf[MYNEWT_VAL(OSDP_UART_BUFFER_LENGTH)];
+ uint8_t tx_fbuf[MYNEWT_VAL(OSDP_UART_BUFFER_LENGTH)];
+ struct uart_dev *uart;
+};
+
+static struct osdp_ctx osdp_ctx;
+static struct osdp_device osdp_device;
+static struct os_callout osdp_refresh_timer;
+
+/*
+ * Handle incoming byte
+ */
+static void
+osdp_handle_in_byte(struct osdp_device *od, uint8_t *buf, int len)
+{
+ for (int i = 0; i < len; ++i) {
+ circular_buf_put(od->rx_buf, buf[i]);
+ }
+}
+
+/*
+ * ISR handler for tx
+ */
+static int
+osdp_uart_tx(void *arg)
+{
+ uint8_t ch = 0;
+ struct osdp_device *od = arg;
+
+ if (circular_buf_get(od->tx_buf, &ch) == -1) {
+ return -1;
+ }
+
+ return ch;
+}
+
+/*
+ * ISR handler for rx
+ */
+static int
+osdp_uart_rx(void *arg, uint8_t ch)
+{
+ struct osdp_device *od = arg;
+
+ osdp_handle_in_byte(od, &ch, 1);
+
+ return 0;
+}
+
+/*
+ * Retrieve characters from rx buffer
+ * Called from refresh handler
+ */
+static int
+osdp_uart_receive(void *data, uint8_t *buf, int len)
+{
+ struct osdp_device *od = data;
+ int i;
+
+ /* Get characters from buffer to parse */
+ for (i = 0; i < len; ++i) {
+ if (circular_buf_get(od->rx_buf, &buf[i]) == -1) {
+ break;
+ }
+ }
+
+ return i;
+}
+
+/*
+ * Place characters in the tx buffer
+ * Called from the refresh handler
+ */
+static int
+osdp_uart_send(void *data, uint8_t *buf, int len)
+{
+ int i;
+ struct osdp_device *od = data;
+
+ /* Place characters in the tx buffer */
+ for (i = 0; i < len; ++i) {
+ if (circular_buf_put2(od->tx_buf, buf[i]) == -1) {
+ break;
+ }
+ }
+
+ /* Start transmission */
+ uart_start_tx(od->uart);
+
+ /* Return total characters to be sent */
+ return i;
+}
+
+/*
+ * Reset tx/rx buffers
+ */
+static void
+osdp_uart_flush(void *data)
+{
+ struct osdp_device *od = data;
+
+ circular_buf_reset(od->tx_buf);
+ circular_buf_reset(od->rx_buf);
+}
+
+/*
+ * Get context handle
+ */
+struct osdp *
+osdp_get_ctx()
+{
+ return &osdp_ctx.ctx;
+}
+
+/*
+ * Timer handler
+ */
+void
+osdp_refresh_handler(struct os_event *ev)
+{
+ struct osdp *ctx = osdp_get_ctx();
+ osdp_refresh(ctx);
+
+ /* Restart */
+ os_callout_reset(&osdp_refresh_timer, OSDP_REFRESH_INTERVAL);
+}
+
+/*
+ * Start OSDP. Called by application
+ */
+void
+osdp_init(osdp_pd_info_t *info, uint8_t *scbk)
+{
+ struct osdp *ctx;
+ struct osdp_device *od = &osdp_device;
+
+ /* Assign remaining function handlers not managed by the application layer */
+ info->channel.send = osdp_uart_send;
+ info->channel.recv = osdp_uart_receive;
+ info->channel.flush = osdp_uart_flush;
+ info->channel.data = &osdp_device;
+
+ od->tx_buf = circular_buf_init(od->tx_fbuf, sizeof(od->tx_fbuf));
+ od->rx_buf = circular_buf_init(od->rx_fbuf, sizeof(od->rx_fbuf));
+
+ struct uart_conf uc = {
+ .uc_speed = info->baud_rate,
+ .uc_databits = 8,
+ .uc_stopbits = 1,
+ .uc_parity = UART_PARITY_NONE,
+ .uc_flow_ctl = UART_FLOW_CTL_NONE,
+ .uc_tx_char = osdp_uart_tx,
+ .uc_rx_char = osdp_uart_rx,
+ .uc_cb_arg = od,
+ };
+
+ od->uart = (struct uart_dev *)os_dev_open(MYNEWT_VAL(OSDP_UART_DEV_NAME),
+ OS_TIMEOUT_NEVER, &uc);
+ assert(od->uart != NULL);
+
+ /* Setup OSDP */
+#if MYNEWT_VAL(OSDP_MODE_PD)
+ ctx = osdp_pd_setup(&osdp_ctx, info, scbk);
+ assert(ctx != NULL);
+#else
+ ctx = osdp_cp_setup(&osdp_ctx, MYNEWT_VAL(OSDP_NUM_CONNECTED_PD), info, scbk);
+ assert(ctx != NULL);
+#endif
+
+ /* Configure and reset timer */
+ os_callout_init(&osdp_refresh_timer, os_eventq_dflt_get(),
+ osdp_refresh_handler, NULL);
+
+ os_callout_reset(&osdp_refresh_timer, OSDP_REFRESH_INTERVAL);
+
+ OSDP_LOG_INFO("OSDP OK\n");
+}
diff --git a/net/osdp/src/osdp_common.c b/net/osdp/src/osdp_common.c
new file mode 100644
index 0000000..102f507
--- /dev/null
+++ b/net/osdp/src/osdp_common.c
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <ctype.h>
+
+#include <crc/crc16.h>
+#include "modlog/modlog.h"
+
+#include <os/os_time.h>
+
+#include "tinycrypt/cbc_mode.h"
+#include "tinycrypt/aes.h"
+#include "tinycrypt/constants.h"
+#if MYNEWT_VAL(TRNG) && !MYNEWT_VAL(OSDP_USE_CRYPTO_HOOK)
+#include "trng/trng.h"
+#endif /* MYNEWT_VAL(TRNG) && !MYNEWT_VAL(OSDP_USE_CRYPTO_HOOK) */
+
+#include "osdp/osdp_common.h"
+#include "osdp/osdp_hooks.h"
+#include "osdp/slab.h"
+
+/*
+ * Set this to size of the UART buffer length and add block size
+ * If the entire buffer were to be encrypted, account for the
+ * the fact that the output buffer comes with IV appended
+ */
+#define MAX_SCRATCH_LEN (MYNEWT_VAL(OSDP_UART_BUFFER_LENGTH) + TC_AES_BLOCK_SIZE)
+static uint8_t scratch_buf1[MAX_SCRATCH_LEN];
+#if MYNEWT_VAL(TRNG) && !MYNEWT_VAL(OSDP_USE_CRYPTO_HOOK)
+static struct trng_dev *trng = NULL;
+#endif /* MYNEWT_VAL(TRNG) && !MYNEWT_VAL(OSDP_USE_CRYPTO_HOOK) */
+
+#if 0
+/* see macro in osdp_common.h */
+void
+osdp_dump(const char *head, uint8_t *buf, int len)
+{
+
+}
+#endif
+
+uint16_t
+osdp_compute_crc16(const uint8_t *buf, size_t len)
+{
+ return crc16_ccitt(0x1D0F, buf, len);
+}
+
+int64_t
+osdp_millis_now(void)
+{
+ return (os_get_uptime_usec() / 1000);
+}
+
+int64_t
+osdp_millis_since(int64_t last)
+{
+ return osdp_millis_now() - last;
+}
+
+void
+osdp_encrypt(uint8_t *key, uint8_t *iv, uint8_t *data, int len)
+{
+ struct tc_aes_key_sched_struct s;
+ (void)tc_aes128_set_encrypt_key(&s, key);
+
+ if (iv != NULL) {
+ /* Check if there is enough space for IV + len */
+ assert(len <= MAX_SCRATCH_LEN - TC_AES_BLOCK_SIZE);
+ if (tc_cbc_mode_encrypt(scratch_buf1, /* out: output buffer */
+ len + TC_AES_BLOCK_SIZE, /* outlen: max output size, including size of IV */
+ data, /* in: input buffer */
+ len, /* inlen: input len */
+ iv, /* iv: IV start */
+ &s) == TC_CRYPTO_FAIL) {
+ OSDP_LOG_ERROR("CBC ENCRYPT - Failed");
+ }
+
+ /* copy ciphertext from offset */
+ memcpy(data, scratch_buf1 + TC_AES_BLOCK_SIZE, len);
+
+ } else {
+ if (tc_aes_encrypt(data, data, &s) == TC_CRYPTO_FAIL) {
+ OSDP_LOG_ERROR("ECB ENCRYPT - Failed\n");
+ }
+ }
+}
+
+void
+osdp_decrypt(uint8_t *key, uint8_t *iv, uint8_t *data, int len)
+{
+ struct tc_aes_key_sched_struct s;
+ (void)tc_aes128_set_decrypt_key(&s, key);
+
+ if (iv != NULL) {
+ /* Check if there is enough space for IV + len */
+ assert(len <= MAX_SCRATCH_LEN - TC_AES_BLOCK_SIZE);
+ /* Create input buffer. IV and cipher text in contiguous buffer per tinycrypt */
+ memcpy(scratch_buf1, iv, TC_AES_BLOCK_SIZE);
+ memcpy(scratch_buf1 + TC_AES_BLOCK_SIZE, data, len);
+ /*
+ note commit da923ca upstream(tinycrypt)
+ Commit message has this nugget:-
+ When decrypting with CBC mode, the in and out buffers should be the same size.
+ Even though the IV and ciphertext are contiguous, the in buffer points to the first byte of the ciphertext.
+ */
+ if (tc_cbc_mode_decrypt(scratch_buf1, /* out: output buffer */
+ len + TC_AES_BLOCK_SIZE, /* outlen: same as inlen */
+ scratch_buf1 + TC_AES_BLOCK_SIZE, /* in: offset to ciphertext in input buffer */
+ len + TC_AES_BLOCK_SIZE, /* inlen: total length of input, iv + buffer length */
+ scratch_buf1, /* iv: offset to IV in input buffer */
+ &s) == TC_CRYPTO_FAIL) {
+ OSDP_LOG_ERROR("CBC DECRYPT - Failed\n");
+ }
+ /* Copy decrypted data into output buffer */
+ memcpy(data, scratch_buf1, len);
+ } else {
+ if (tc_aes_decrypt(data, data, &s) == TC_CRYPTO_FAIL) {
+ OSDP_LOG_ERROR("ECB DECRYPT - Failed\n");
+ }
+ }
+}
+
+void
+osdp_get_rand(uint8_t *buf, int len)
+{
+ size_t ret = 0;
+
+#if MYNEWT_VAL(OSDP_USE_CRYPTO_HOOK)
+ ret = osdp_hook_crypto_random_bytes(buf, len);
+#elif MYNEWT_VAL(TRNG)
+ trng = (struct trng_dev *) os_dev_open("trng", OS_WAIT_FOREVER, NULL);
+ if (trng == NULL) {
+ OSDP_LOG_ERROR("Could not open TRNG\n");
+ buf = NULL;
+ }
+
+ ret = trng_read(trng, buf, len);
+ while (ret < len) {
+ os_sched(NULL);
+ ret += trng_read(trng, &buf[ret], len - ret);
+ }
+
+ os_dev_close((struct os_dev *)trng);
+#endif
+
+ if (ret == 0) {
+ buf = NULL;
+ }
+}
+
+uint32_t
+osdp_get_sc_status_mask(osdp_t *ctx)
+{
+ int i;
+ uint32_t mask = 0;
+ struct osdp_pd *pd;
+
+ assert(ctx);
+
+ if (ISSET_FLAG(TO_OSDP(ctx), FLAG_CP_MODE)) {
+ for (i = 0; i < NUM_PD(ctx); i++) {
+ pd = TO_PD(ctx, i);
+ if (pd->state == OSDP_CP_STATE_ONLINE &&
+ ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) {
+ mask |= 1 << i;
+ }
+ }
+ } else {
+ pd = TO_PD(ctx, 0);
+ if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) {
+ mask = 1;
+ }
+ }
+
+ return mask;
+}
+
+uint32_t
+osdp_get_status_mask(osdp_t *ctx)
+{
+ int i;
+ uint32_t mask = 0;
+ struct osdp_pd *pd;
+
+ assert(ctx);
+
+ if (ISSET_FLAG(TO_OSDP(ctx), FLAG_CP_MODE)) {
+ for (i = 0; i < TO_OSDP(ctx)->cp->num_pd; i++) {
+ pd = TO_PD(ctx, i);
+ if (pd->state == OSDP_CP_STATE_ONLINE) {
+ mask |= 1 << i;
+ }
+ }
+ } else {
+ /* PD is stateless */
+ mask = 1;
+ }
+
+ return mask;
+}
diff --git a/net/osdp/src/osdp_cp.c b/net/osdp/src/osdp_cp.c
new file mode 100644
index 0000000..b2632dd
--- /dev/null
+++ b/net/osdp/src/osdp_cp.c
@@ -0,0 +1,1227 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdlib.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)
+
+#if MYNEWT_VAL(OSDP_SC_ENABLED)
+#define OSDP_PD_SC_RETRY_MS (MYNEWT_VAL(OSDP_SC_RETRY_WAIT_SEC) * 1000)
+#endif
+
+#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_NO_DATA 1
+#define OSDP_CP_ERR_RETRY_CMD 2
+#define OSDP_CP_ERR_CAN_YIELD 3
+#define OSDP_CP_ERR_INPROG 4
+
+struct cp_cmd_node {
+ queue_node_t node;
+ struct osdp_cmd object;
+};
+
+static int
+cp_cmd_queue_init(struct osdp_pd *pd)
+{
+ if (slab_init(&pd->cmd.slab, sizeof(struct cp_cmd_node),
+ MYNEWT_VAL(OSDP_PD_COMMAND_QUEUE_SIZE))) {
+ OSDP_LOG_ERROR("Failed to initialize command slab\n");
+ return -1;
+ }
+ queue_init(&pd->cmd.queue);
+ return 0;
+}
+
+static void
+cp_cmd_queue_del(struct osdp_pd *pd)
+{
+ slab_del(&pd->cmd.slab);
+}
+
+static struct osdp_cmd *
+cp_cmd_alloc(struct osdp_pd *pd)
+{
+ struct cp_cmd_node *cmd = NULL;
+
+ if (slab_alloc(&pd->cmd.slab, (void **)&cmd)) {
+ OSDP_LOG_ERROR("Command slab 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 *n;
+
+ n = CONTAINER_OF(cmd, struct cp_cmd_node, object);
+ slab_free(&pd->cmd.slab, n);
+}
+
+static void
+cp_cmd_enqueue(struct osdp_pd *pd, struct osdp_cmd *cmd)
+{
+ struct cp_cmd_node *n;
+
+ n = CONTAINER_OF(cmd, struct cp_cmd_node, object);
+ queue_enqueue(&pd->cmd.queue, &n->node);
+}
+
+static int
+cp_cmd_dequeue(struct osdp_pd *pd, struct osdp_cmd **cmd)
+{
+ struct cp_cmd_node *n;
+ queue_node_t *node;
+
+ if (queue_dequeue(&pd->cmd.queue, &node)) {
+ return -1;
+ }
+ n = CONTAINER_OF(node, struct cp_cmd_node, node);
+ *cmd = &n->object;
+ return 0;
+}
+
+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("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("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("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;
+ }
+ osdp_get_rand(pd->sc.cp_random, 8);
+ 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("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("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("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("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("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("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("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("Unexpected REPLY(%02x)\n", pd->reply_id);
+ return OSDP_CP_ERR_GENERIC;
+ }
+
+ if (ret == OSDP_CP_ERR_GENERIC) {
+ OSDP_LOG_ERROR("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("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("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, 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 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 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();
+}
+
+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 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_dequeue(pd, &cmd)) {
+ ret = OSDP_CP_ERR_NONE; /* command queue is empty */
+ break;
+ }
+ pd->cmd_id = cmd->id;
+ memcpy(pd->ephemeral_data, cmd, sizeof(struct osdp_cmd));
+ cp_cmd_free(pd, cmd);
+ /* fall-thru */
+ case OSDP_CP_PHY_STATE_SEND_CMD:
+ if ((cp_send_command(pd)) < 0) {
+ OSDP_LOG_ERROR("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("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("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)
+{
+ 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 */
+ }
+
+ c = cp_cmd_alloc(pd);
+ if (c == NULL) {
+ return OSDP_CP_ERR_GENERIC;
+ }
+
+ 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);
+ return OSDP_CP_ERR_INPROG;
+}
+
+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("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) > OSDP_CMD_RETRY_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("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("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("SC disabled or not capable. Set PD offline due "
+ "to ENFORCE_SECURE\n");
+ cp_set_offline(pd);
+ } else {
+ cp_set_state(pd, OSDP_CP_STATE_ONLINE);
+ }
+ 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("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("SC Failed. Online without SC\n");
+ pd->sc_tstamp = osdp_millis_now();
+ cp_set_state(pd, OSDP_CP_STATE_ONLINE);
+ 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("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("CHLNG failed. Set PD offline due to "
+ "ENFORCE_SECURE\n");
+ cp_set_offline(pd);
+ } else {
+ OSDP_LOG_ERROR("CHLNG failed. Online without SC\n");
+ pd->sc_tstamp = osdp_millis_now();
+ osdp_phy_state_reset(pd);
+ cp_set_state(pd, OSDP_CP_STATE_ONLINE);
+ }
+ 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("SCRYPT failed. Set PD offline due to "
+ "ENFORCE_SECURE\n");
+ cp_set_offline(pd);
+ } else {
+ OSDP_LOG_ERROR("SCRYPT failed. Online without SC\n");
+ osdp_phy_state_reset(pd);
+ pd->sc_tstamp = osdp_millis_now();
+ cp_set_state(pd, OSDP_CP_STATE_ONLINE);
+ }
+ break;
+ }
+ if (ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD)) {
+ OSDP_LOG_WARN("SC ACtive with SCBK-D. Set SCBK\n");
+ cp_set_state(pd, OSDP_CP_STATE_SET_SCBK);
+ break;
+ }
+ OSDP_LOG_INFO("SC Active\n");
+ pd->sc_tstamp = osdp_millis_now();
+ cp_set_state(pd, OSDP_CP_STATE_ONLINE);
+ 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("Failed to set SCBK; "
+ "Set PD offline due to ENFORCE_SECURE\n");
+ cp_set_offline(pd);
+ } else {
+ OSDP_LOG_WARN("Failed to set SCBK; "
+ "Continue with SCBK-D\n");
+ cp_set_state(pd, OSDP_CP_STATE_ONLINE);
+ }
+ break;
+ }
+ OSDP_LOG_INFO("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;
+ struct osdp_cmd *cmd;
+ struct osdp_pd *pd;
+
+ if (osdp_get_sc_status_mask(ctx) != PD_MASK(ctx)) {
+ OSDP_LOG_WARN("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);
+ cmd = cp_cmd_alloc(pd);
+ if (cmd == NULL) {
+ return -1;
+ }
+ cmd->id = CMD_KEYSET;
+ memcpy(&cmd->keyset, p, sizeof(struct osdp_cmd_keyset));
+ cp_cmd_enqueue(pd, cmd);
+ }
+
+ return 0;
+}
+
+osdp_t *
+osdp_cp_setup(struct osdp_ctx *osdp_ctx, int num_pd, osdp_pd_info_t *info,
+ uint8_t *master_key)
+{
+ int i, 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("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)) {
+ 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("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));
+ }
+
+ /*
+ These are static allocations in the ported version
+ safe_free(TO_PD(ctx, 0));
+ safe_free(TO_CP(ctx)->channel_lock);
+ safe_free(TO_CP(ctx));
+ safe_free(ctx);
+ */
+}
+
+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);
+ struct osdp_cmd *cmd;
+ int cmd_id;
+
+ if (pd < 0 || pd >= NUM_PD(ctx)) {
+ OSDP_LOG_ERROR("Invalid PD number\n");
+ return -1;
+ }
+ if (TO_PD(ctx, pd)->state != OSDP_CP_STATE_ONLINE) {
+ OSDP_LOG_WARN("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("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("Invalid CMD_ID:%d\n", p->id);
+ return -1;
+ }
+
+ cmd = cp_cmd_alloc(TO_PD(ctx, pd));
+ if (cmd == NULL) {
+ return -1;
+ }
+
+ memcpy(cmd, p, sizeof(struct osdp_cmd));
+ cmd->id = cmd_id; /* translate to internal */
+ cp_cmd_enqueue(TO_PD(ctx, pd), cmd);
+ return 0;
+}
+
+#endif /* OSDP_MODE_CP */
diff --git a/net/osdp/src/osdp_hooks.c b/net/osdp/src/osdp_hooks.c
new file mode 100644
index 0000000..dc445fd
--- /dev/null
+++ b/net/osdp/src/osdp_hooks.c
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "osdp/osdp_hooks.h"
+#include "modlog/modlog.h"
+
+#if MYNEWT_VAL(OSDP_USE_CRYPTO_HOOK)
+/*
+ * Stub function. Needs to be overridden to use correctly
+ */
+size_t
+osdp_hook_crypto_random_bytes(uint8_t *out, size_t out_len)
+{
+ return 1;
+}
+
+#endif /* OSDP_USE_CRYPTO_HOOK */
diff --git a/net/osdp/src/osdp_pd.c b/net/osdp/src/osdp_pd.c
new file mode 100644
index 0000000..88480a8
--- /dev/null
+++ b/net/osdp/src/osdp_pd.c
@@ -0,0 +1,1137 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "modlog/modlog.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "osdp/osdp_common.h"
+
+#if MYNEWT_VAL(OSDP_MODE_PD) /* compile flag based on mode */
+
+#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 CMD_MFG_DATA_LEN 4 /* variable length command */
+#define CMD_KEYSET_DATA_LEN 18
+#define CMD_CHLNG_DATA_LEN 8
+#define CMD_SCRYPT_DATA_LEN 16
+
+#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_KEYPAD_LEN 2
+#define REPLY_RAW_LEN 4
+#define REPLY_FMT_LEN 3
+#define REPLY_COM_LEN 6
+#define REPLY_NAK_LEN 2
+#define REPLY_MFGREP_LEN 4 /* variable length command */
+#define REPLY_CCRYPT_LEN 33
+#define REPLY_RMAC_I_LEN 17
+
+#define OSDP_PD_ERR_NONE 0
+#define OSDP_PD_ERR_NO_DATA 1
+#define OSDP_PD_ERR_GENERIC -1
+#define OSDP_PD_ERR_REPLY -2
+
+/* Implicit cababilities */
+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 */
+ },
+ {
+ OSDP_PD_CAP_COMMUNICATION_SECURITY,
+ 1, /* (Bit-0) AES128 support */
+ 0, /* N/A */
+ },
+ { -1, 0, 0 } /* Sentinel */
+};
+
+struct pd_event_node {
+ queue_node_t node;
+ struct osdp_event object;
+};
+
+static int
+pd_event_queue_init(struct osdp_pd *pd)
+{
+ if (slab_init(&pd->event.slab, sizeof(struct pd_event_node),
+ MYNEWT_VAL(OSDP_PD_COMMAND_QUEUE_SIZE))) {
+ OSDP_LOG_ERROR("Failed to initialize command slab\n");
+ return -1;
+ }
+ queue_init(&pd->event.queue);
+ return 0;
+}
+
+static void
+pd_event_queue_del(struct osdp_pd *pd)
+{
+ slab_del(&pd->event.slab);
+}
+
+static struct osdp_event *
+pd_event_alloc(struct osdp_pd *pd)
+{
+ struct pd_event_node *event = NULL;
+
+ if (slab_alloc(&pd->event.slab, (void **)&event)) {
+ OSDP_LOG_ERROR("Event slab allocation failed\n");
+ return NULL;
+ }
+ return &event->object;
+}
+
+static void
+pd_event_free(struct osdp_pd *pd, struct osdp_event *event)
+{
+ struct pd_event_node *n;
+
+ n = CONTAINER_OF(event, struct pd_event_node, object);
+ slab_free(&pd->event.slab, n);
+}
+
+static void
+pd_event_enqueue(struct osdp_pd *pd, struct osdp_event *event)
+{
+ struct pd_event_node *n;
+
+ n = CONTAINER_OF(event, struct pd_event_node, object);
+ queue_enqueue(&pd->event.queue, &n->node);
+}
+
+static int
+pd_event_dequeue(struct osdp_pd *pd, struct osdp_event **event)
+{
+ struct pd_event_node *n;
+ queue_node_t *node;
+
+ if (queue_dequeue(&pd->event.queue, &node)) {
+ return -1;
+ }
+ n = CONTAINER_OF(node, struct pd_event_node, node);
+ *event = &n->object;
+ return 0;
+}
+
+static int
+pd_translate_event(struct osdp_event *event, uint8_t *data)
+{
+ int reply_code = 0;
+
+ switch (event->type) {
+ case OSDP_EVENT_CARDREAD:
+ if (event->cardread.format == OSDP_CARD_FMT_RAW_UNSPECIFIED ||
+ event->cardread.format == OSDP_CARD_FMT_RAW_WIEGAND) {
+ reply_code = REPLY_RAW;
+ } else if (event->cardread.format == OSDP_CARD_FMT_ASCII) {
+ reply_code = REPLY_FMT;
+ } else {
+ OSDP_LOG_ERROR("Event: cardread; Error: unknown format\n");
+ break;
+ }
+ break;
+ case OSDP_EVENT_KEYPRESS:
+ reply_code = REPLY_KEYPPAD;
+ break;
+ default:
+ OSDP_LOG_ERROR("Unknown event type %d\n", event->type);
+ break;
+ }
+ if (reply_code == 0) {
+ /* POLL command cannot fail even when there are errors here */
+ return REPLY_ACK;
+ }
+ memcpy(data, event, sizeof(struct osdp_event));
+ return reply_code;
+}
+
+static int
+pd_cmd_cap_ok(struct osdp_pd *pd, struct osdp_cmd *cmd)
+{
+ struct osdp_pd_cap *cap = NULL;
+
+ /* Validate the cmd_id against a PD capabilities where applicable */
+ switch (pd->cmd_id) {
+ case CMD_ISTAT:
+ cap = &pd->cap[OSDP_PD_CAP_CONTACT_STATUS_MONITORING];
+ if (cap->num_items == 0 || cap->compliance_level == 0) {
+ break;
+ }
+ return 0; /* Remove this when REPLY_ISTATR is supported */
+ case CMD_OSTAT:
+ cap = &pd->cap[OSDP_PD_CAP_OUTPUT_CONTROL];
+ if (cap->num_items == 0 || cap->compliance_level == 0) {
+ break;
+ }
+ return 0; /* Remove this when REPLY_OSTATR is supported */
+ case CMD_OUT:
+ cap = &pd->cap[OSDP_PD_CAP_OUTPUT_CONTROL];
+ if (cmd->output.output_no + 1 > cap->num_items) {
+ OSDP_LOG_DEBUG("CAP check: output_no(%d) > cap->num_items(%d)\n",
+ cmd->output.output_no + 1, cap->num_items);
+ break;
+ }
+ if (cap->compliance_level == 0) {
+ break;
+ }
+ return 1;
+ case CMD_LED:
+ cap = &pd->cap[OSDP_PD_CAP_READER_LED_CONTROL];
+ if (cmd->led.led_number + 1 > cap->num_items) {
+ OSDP_LOG_DEBUG("CAP check: LED(%d) > cap->num_items(%d)\n",
+ cmd->led.led_number + 1, cap->num_items);
+ break;
+ }
+ if (cap->compliance_level == 0) {
+ break;
+ }
+ return 1;
+ case CMD_BUZ:
+ cap = &pd->cap[OSDP_PD_CAP_READER_AUDIBLE_OUTPUT];
+ if (cap->num_items == 0 || cap->compliance_level == 0) {
+ break;
+ }
+ return 1;
+ case CMD_TEXT:
+ cap = &pd->cap[OSDP_PD_CAP_READER_TEXT_OUTPUT];
+ if (cap->num_items == 0 || cap->compliance_level == 0) {
+ break;
+ }
+ return 1;
+ case CMD_CHLNG:
+ case CMD_SCRYPT:
+ case CMD_KEYSET:
+ cap = &pd->cap[OSDP_PD_CAP_COMMUNICATION_SECURITY];
+ if (cap->compliance_level == 0) {
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_SC_UNSUP;
+ return 0;
+ }
+ return 1;
+ }
+
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_CMD_UNKNOWN;
+ return 0;
+}
+
+static int
+pd_decode_command(struct osdp_pd *pd, uint8_t *buf, int len)
+{
+ int i, ret = OSDP_PD_ERR_GENERIC, pos = 0;
+ struct osdp_cmd cmd;
+ struct osdp_event *event;
+
+ pd->reply_id = 0;
+ pd->cmd_id = cmd.id = buf[pos++];
+ len--;
+
+ if (ISSET_FLAG(pd, OSDP_FLAG_ENFORCE_SECURE) &&
+ !ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) {
+ /**
+ * Only CMD_ID, CMD_CAP and SC handshake commands (CMD_CHLNG
+ * and CMD_SCRYPT) are allowed when SC is inactive and
+ * ENFORCE_SECURE was requested.
+ */
+ if (pd->cmd_id != CMD_ID && pd->cmd_id != CMD_CAP &&
+ pd->cmd_id != CMD_CHLNG && pd->cmd_id != CMD_SCRYPT) {
+ OSDP_LOG_ERROR("CMD(%02x) not allowed due to ENFORCE_SECURE\n",
+ pd->cmd_id);
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_RECORD;
+ return OSDP_PD_ERR_REPLY;
+ }
+ }
+
+ /* helper macro, can be called from switch cases below */
+#define PD_CMD_CAP_CHECK(pd, cmd) \
+ if (!pd_cmd_cap_ok(pd, cmd)) { \
+ OSDP_LOG_INFO("PD is not capable of handling CMD(%02x); " \
+ "Reply with NAK_CMD_UNKNOWN\n", pd->cmd_id); \
+ ret = OSDP_PD_ERR_REPLY; \
+ break; \
+ }
+
+#define ASSERT_LENGTH(got, exp) \
+ if (got != exp) { \
+ OSDP_LOG_ERROR("CMD(%02x) length error! Got:%d, Exp:%d\n", \
+ pd->cmd_id, got, exp); \
+ return OSDP_PD_ERR_GENERIC; \
+ }
+
+ switch (pd->cmd_id) {
+ case CMD_POLL:
+ ASSERT_LENGTH(len, CMD_POLL_DATA_LEN);
+ /* Check if we have external events in the queue */
+ if (pd_event_dequeue(pd, &event) == 0) {
+ ret = pd_translate_event(event, pd->ephemeral_data);
+ pd->reply_id = ret;
+ pd_event_free(pd, event);
+ } else {
+ pd->reply_id = REPLY_ACK;
+ }
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_LSTAT:
+ ASSERT_LENGTH(len, CMD_LSTAT_DATA_LEN);
+ pd->reply_id = REPLY_LSTATR;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_ISTAT:
+ ASSERT_LENGTH(len, CMD_ISTAT_DATA_LEN);
+ PD_CMD_CAP_CHECK(pd, NULL);
+ pd->reply_id = REPLY_ISTATR;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_OSTAT:
+ ASSERT_LENGTH(len, CMD_OSTAT_DATA_LEN);
+ PD_CMD_CAP_CHECK(pd, NULL);
+ pd->reply_id = REPLY_OSTATR;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_RSTAT:
+ ASSERT_LENGTH(len, CMD_RSTAT_DATA_LEN);
+ pd->reply_id = REPLY_RSTATR;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_ID:
+ ASSERT_LENGTH(len, CMD_ID_DATA_LEN);
+ pos++; /* Skip reply type info. */
+ pd->reply_id = REPLY_PDID;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_CAP:
+ ASSERT_LENGTH(len, CMD_CAP_DATA_LEN);
+ pos++; /* Skip reply type info. */
+ pd->reply_id = REPLY_PDCAP;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_OUT:
+ ASSERT_LENGTH(len, CMD_OUT_DATA_LEN);
+ if (!pd->command_callback) {
+ break;
+ }
+ cmd.id = OSDP_CMD_OUTPUT;
+ cmd.output.output_no = buf[pos++];
+ cmd.output.control_code = buf[pos++];
+ cmd.output.timer_count = buf[pos++];
+ cmd.output.timer_count |= buf[pos++] << 8;
+ PD_CMD_CAP_CHECK(pd, &cmd);
+ ret = pd->command_callback(pd->command_callback_arg, &cmd);
+ if (ret != 0) {
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_RECORD;
+ ret = OSDP_PD_ERR_REPLY;
+ break;
+ }
+ pd->reply_id = REPLY_ACK;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_LED:
+ ASSERT_LENGTH(len, CMD_LED_DATA_LEN);
+ if (!pd->command_callback) {
+ 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_count = buf[pos++];
+ cmd.led.temporary.timer_count |= 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_CMD_CAP_CHECK(pd, &cmd);
+ ret = pd->command_callback(pd->command_callback_arg, &cmd);
+ if (ret != 0) {
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_RECORD;
+ ret = OSDP_PD_ERR_REPLY;
+ break;
+ }
+ pd->reply_id = REPLY_ACK;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_BUZ:
+ ASSERT_LENGTH(len, CMD_BUZ_DATA_LEN);
+ if (!pd->command_callback) {
+ break;
+ }
+ cmd.id = OSDP_CMD_BUZZER;
+ cmd.buzzer.reader = buf[pos++];
+ cmd.buzzer.control_code = buf[pos++];
+ cmd.buzzer.on_count = buf[pos++];
+ cmd.buzzer.off_count = buf[pos++];
+ cmd.buzzer.rep_count = buf[pos++];
+ PD_CMD_CAP_CHECK(pd, &cmd);
+ ret = pd->command_callback(pd->command_callback_arg, &cmd);
+ if (ret != 0) {
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_RECORD;
+ ret = OSDP_PD_ERR_REPLY;
+ break;
+ }
+ pd->reply_id = REPLY_ACK;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_TEXT:
+ if (len < CMD_TEXT_DATA_LEN || !pd->command_callback) {
+ break;
+ }
+ cmd.id = OSDP_CMD_TEXT;
+ cmd.text.reader = buf[pos++];
+ cmd.text.control_code = 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) {
+ break;
+ }
+ for (i = 0; i < cmd.text.length; i++) {
+ cmd.text.data[i] = buf[pos++];
+ }
+ PD_CMD_CAP_CHECK(pd, &cmd);
+ ret = pd->command_callback(pd->command_callback_arg, &cmd);
+ if (ret != 0) {
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_RECORD;
+ ret = OSDP_PD_ERR_REPLY;
+ break;
+ }
+ pd->reply_id = REPLY_ACK;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_COMSET:
+ ASSERT_LENGTH(len, CMD_COMSET_DATA_LEN);
+ if (!pd->command_callback) {
+ break;
+ }
+ cmd.id = OSDP_CMD_COMSET;
+ cmd.comset.address = buf[pos++];
+ cmd.comset.baud_rate = buf[pos++];
+ cmd.comset.baud_rate |= buf[pos++] << 8;
+ cmd.comset.baud_rate |= buf[pos++] << 16;
+ cmd.comset.baud_rate |= buf[pos++] << 24;
+ if (cmd.comset.address >= 0x7F ||
+ (cmd.comset.baud_rate != 9600 &&
+ cmd.comset.baud_rate != 38400 &&
+ cmd.comset.baud_rate != 115200)) {
+ OSDP_LOG_ERROR("COMSET Failed! command discarded\n");
+ cmd.comset.address = pd->address;
+ cmd.comset.baud_rate = pd->baud_rate;
+ }
+ ret = pd->command_callback(pd->command_callback_arg, &cmd);
+ if (ret != 0) {
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_RECORD;
+ ret = OSDP_PD_ERR_REPLY;
+ break;
+ }
+ memcpy(pd->ephemeral_data, &cmd, sizeof(struct osdp_cmd));
+ pd->reply_id = REPLY_COM;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_MFG:
+ if (len < CMD_MFG_DATA_LEN || !pd->command_callback) {
+ break;
+ }
+ cmd.id = OSDP_CMD_MFG;
+ cmd.mfg.vendor_code = buf[pos++]; /* vendor_code */
+ cmd.mfg.vendor_code |= buf[pos++] << 8;
+ cmd.mfg.vendor_code |= buf[pos++] << 16;
+ cmd.mfg.command = buf[pos++];
+ cmd.mfg.length = len - CMD_MFG_DATA_LEN;
+ if (cmd.mfg.length > OSDP_CMD_MFG_MAX_DATALEN) {
+ OSDP_LOG_ERROR("cmd length error\n");
+ break;
+ }
+ for (i = 0; i < cmd.mfg.length; i++) {
+ cmd.mfg.data[i] = buf[pos++];
+ }
+ ret = pd->command_callback(pd->command_callback_arg, &cmd);
+ if (ret < 0) { /* Errors */
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_RECORD;
+ ret = OSDP_PD_ERR_REPLY;
+ break;
+ }
+ if (ret > 0) { /* App wants to send a REPLY_MFGREP to the CP */
+ memcpy(pd->ephemeral_data, &cmd, sizeof(struct osdp_cmd));
+ pd->reply_id = REPLY_MFGREP;
+ } else {
+ pd->reply_id = REPLY_ACK;
+ }
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_KEYSET:
+ PD_CMD_CAP_CHECK(pd, &cmd);
+ ASSERT_LENGTH(len, CMD_KEYSET_DATA_LEN);
+ /**
+ * For CMD_KEYSET to be accepted, PD must be
+ * ONLINE and SC_ACTIVE.
+ */
+ if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE) == 0) {
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_SC_COND;
+ OSDP_LOG_ERROR("Keyset with SC inactive\n");
+ break;
+ }
+ /* only key_type == 1 (SCBK) and key_len == 16 is supported */
+ if (buf[pos] != 1 || buf[pos + 1] != 16) {
+ OSDP_LOG_ERROR("Keyset invalid len/type: %d/%d\n",
+ buf[pos], buf[pos + 1]);
+ break;
+ }
+ cmd.id = OSDP_CMD_KEYSET;
+ cmd.keyset.type = buf[pos++];
+ cmd.keyset.length = buf[pos++];
+ memcpy(cmd.keyset.data, buf + pos, 16);
+ memcpy(pd->sc.scbk, buf + pos, 16);
+ ret = OSDP_PD_ERR_NONE;
+ if (pd->command_callback) {
+ ret = pd->command_callback(pd->command_callback_arg,
+ &cmd);
+ } else {
+ OSDP_LOG_WARN("Keyset without command callback trigger\n");
+ }
+ if (ret != 0) {
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_RECORD;
+ ret = OSDP_PD_ERR_REPLY;
+ break;
+ }
+ CLEAR_FLAG(pd, PD_FLAG_SC_USE_SCBKD);
+ CLEAR_FLAG(pd, OSDP_FLAG_INSTALL_MODE);
+ pd->reply_id = REPLY_ACK;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_CHLNG:
+ PD_CMD_CAP_CHECK(pd, &cmd);
+ ASSERT_LENGTH(len, CMD_CHLNG_DATA_LEN);
+ osdp_sc_init(pd);
+ CLEAR_FLAG(pd, PD_FLAG_SC_ACTIVE);
+ for (i = 0; i < CMD_CHLNG_DATA_LEN; i++) {
+ pd->sc.cp_random[i] = buf[pos++];
+ }
+ pd->reply_id = REPLY_CCRYPT;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case CMD_SCRYPT:
+ PD_CMD_CAP_CHECK(pd, &cmd);
+ ASSERT_LENGTH(len, CMD_SCRYPT_DATA_LEN);
+ for (i = 0; i < CMD_SCRYPT_DATA_LEN; i++) {
+ pd->sc.cp_cryptogram[i] = buf[pos++];
+ }
+ pd->reply_id = REPLY_RMAC_I;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ default:
+ OSDP_LOG_ERROR("Unknown command ID %02x\n", pd->cmd_id);
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_CMD_UNKNOWN;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ }
+
+ if (ret != 0 && ret != OSDP_PD_ERR_REPLY) {
+ OSDP_LOG_ERROR("Invalid command structure. CMD: %02x, Len: %d ret: %d\n",
+ pd->cmd_id, len, ret);
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_CMD_LEN;
+ return OSDP_PD_ERR_REPLY;
+ }
+
+ if (pd->cmd_id != CMD_POLL) {
+ OSDP_LOG_DEBUG("CMD: %02x REPLY: %02x\n", pd->cmd_id, pd->reply_id);
+ }
+
+ return ret;
+}
+
+/**
+ * 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;
+ uint8_t t1, *smb;
+ struct osdp_event *event;
+ struct osdp_cmd *cmd;
+
+ 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("OOM at build REPLY(%02x) - have:%d, need:%d\n", \
+ pd->reply_id, max_len, need); \
+ return OSDP_PD_ERR_GENERIC; \
+ }
+
+ switch (pd->reply_id) {
+ case REPLY_ACK:
+ ASSERT_BUF_LEN(REPLY_ACK_LEN);
+ buf[len++] = pd->reply_id;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case REPLY_PDID:
+ ASSERT_BUF_LEN(REPLY_PDID_LEN);
+ 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 = OSDP_PD_ERR_NONE;
+ break;
+ case REPLY_PDCAP:
+ ASSERT_BUF_LEN(REPLY_PDCAP_LEN);
+ 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) {
+ OSDP_LOG_ERROR("Out of buffer space!\n");
+ 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 = OSDP_PD_ERR_NONE;
+ break;
+ case REPLY_LSTATR:
+ ASSERT_BUF_LEN(REPLY_LSTATR_LEN);
+ buf[len++] = pd->reply_id;
+ buf[len++] = ISSET_FLAG(pd, PD_FLAG_TAMPER);
+ buf[len++] = ISSET_FLAG(pd, PD_FLAG_POWER);
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case REPLY_RSTATR:
+ ASSERT_BUF_LEN(REPLY_RSTATR_LEN);
+ buf[len++] = pd->reply_id;
+ buf[len++] = ISSET_FLAG(pd, PD_FLAG_R_TAMPER);
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case REPLY_KEYPPAD:
+ event = (struct osdp_event *)pd->ephemeral_data;
+ ASSERT_BUF_LEN(REPLY_KEYPAD_LEN + event->keypress.length);
+ buf[len++] = pd->reply_id;
+ buf[len++] = (uint8_t)event->keypress.reader_no;
+ buf[len++] = (uint8_t)event->keypress.length;
+ for (i = 0; i < event->keypress.length; i++) {
+ buf[len++] = event->keypress.data[i];
+ }
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case REPLY_RAW:
+ event = (struct osdp_event *)pd->ephemeral_data;
+ t1 = (event->cardread.length + 7) / 8;
+ ASSERT_BUF_LEN(REPLY_RAW_LEN + t1);
+ buf[len++] = pd->reply_id;
+ buf[len++] = (uint8_t)event->cardread.reader_no;
+ buf[len++] = (uint8_t)event->cardread.format;
+ buf[len++] = BYTE_0(event->cardread.length);
+ buf[len++] = BYTE_1(event->cardread.length);
+ for (i = 0; i < t1; i++) {
+ buf[len++] = event->cardread.data[i];
+ }
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case REPLY_FMT:
+ event = (struct osdp_event *)pd->ephemeral_data;
+ ASSERT_BUF_LEN(REPLY_FMT_LEN + event->cardread.length);
+ buf[len++] = pd->reply_id;
+ buf[len++] = (uint8_t)event->cardread.reader_no;
+ buf[len++] = (uint8_t)event->cardread.direction;
+ buf[len++] = (uint8_t)event->cardread.length;
+ for (i = 0; i < event->cardread.length; i++) {
+ buf[len++] = event->cardread.data[i];
+ }
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case REPLY_COM:
+ ASSERT_BUF_LEN(REPLY_COM_LEN);
+ /**
+ * 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.
+ */
+ cmd = (struct osdp_cmd *)pd->ephemeral_data;
+ buf[len++] = pd->reply_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);
+
+ pd->address = (int)cmd->comset.address;
+ pd->baud_rate = (int)cmd->comset.baud_rate;
+ OSDP_LOG_INFO("COMSET Succeeded! New PD-Addr: %d; Baud: %d\n",
+ pd->address, pd->baud_rate);
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case REPLY_NAK:
+ ASSERT_BUF_LEN(REPLY_NAK_LEN);
+ buf[len++] = pd->reply_id;
+ buf[len++] = pd->ephemeral_data[0];
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case REPLY_MFGREP:
+ cmd = (struct osdp_cmd *)pd->ephemeral_data;
+ ASSERT_BUF_LEN(REPLY_MFGREP_LEN + cmd->mfg.length);
+ buf[len++] = pd->reply_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 = OSDP_PD_ERR_NONE;
+ break;
+ case REPLY_CCRYPT:
+ if (smb == NULL) {
+ break;
+ }
+ ASSERT_BUF_LEN(REPLY_CCRYPT_LEN);
+ osdp_get_rand(pd->sc.pd_random, 8);
+ osdp_compute_session_keys(TO_CTX(pd));
+ osdp_compute_pd_cryptogram(pd);
+ buf[len++] = pd->reply_id;
+ for (i = 0; i < 8; i++) {
+ buf[len++] = pd->sc.pd_client_uid[i];
+ }
+ for (i = 0; i < 8; i++) {
+ buf[len++] = pd->sc.pd_random[i];
+ }
+ for (i = 0; i < 16; i++) {
+ buf[len++] = pd->sc.pd_cryptogram[i];
+ }
+ smb[0] = 3; /* length */
+ smb[1] = SCS_12; /* type */
+ smb[2] = ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD) ? 0 : 1;
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ case REPLY_RMAC_I:
+ if (smb == NULL) {
+ break;
+ }
+ ASSERT_BUF_LEN(REPLY_RMAC_I_LEN);
+ osdp_compute_rmac_i(pd);
+ buf[len++] = pd->reply_id;
+ for (i = 0; i < 16; i++) {
+ buf[len++] = pd->sc.r_mac[i];
+ }
+ smb[0] = 3; /* length */
+ smb[1] = SCS_14; /* type */
+ if (osdp_verify_cp_cryptogram(pd) == 0) {
+ smb[2] = 1; /* CP auth succeeded */
+ SET_FLAG(pd, PD_FLAG_SC_ACTIVE);
+ pd->sc_tstamp = osdp_millis_now();
+ if (ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD)) {
+ OSDP_LOG_WARN("SC Active with SCBK-D\n");
+ } else {
+ OSDP_LOG_INFO("SC Active\n");
+ }
+ } else {
+ smb[2] = 0; /* CP auth failed */
+ OSDP_LOG_WARN("failed to verify CP_crypt\n");
+ }
+ ret = OSDP_PD_ERR_NONE;
+ break;
+ }
+
+ if (smb && (smb[1] > SCS_14) && ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) {
+ smb[0] = 2; /* length */
+ smb[1] = (len > 1) ? SCS_18 : SCS_16;
+ }
+
+ if (ret != 0) {
+ /* catch all errors and report it as a RECORD error to CP */
+ OSDP_LOG_ERROR("Failed to build REPLY(%02x); Sending NAK instead!\n",
+ pd->reply_id);
+ ASSERT_BUF_LEN(REPLY_NAK_LEN);
+ 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 OSDP_PD_ERR_GENERIC;
+ }
+
+ /* fill reply data */
+ ret = pd_build_reply(pd, pd->rx_buf, sizeof(pd->rx_buf));
+ if (ret <= 0) {
+ return OSDP_PD_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_PD_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("Channel send for %d bytes failed! ret: %d\n", len, ret);
+ return OSDP_PD_ERR_GENERIC;
+ }
+
+ if (MYNEWT_VAL(OSDP_PACKET_TRACE)) {
+ if (pd->cmd_id != CMD_POLL) {
+ osdp_dump(pd->rx_buf, pd->rx_buf_len,
+ "OSDP: PD[%d]: Sent\n", pd->address);
+ }
+ }
+
+ return OSDP_PD_ERR_NONE;
+}
+
+static int
+pd_receive_packet(struct osdp_pd *pd)
+{
+ uint8_t *buf;
+ int len, err, remaining;
+
+ len = pd->channel.recv(pd->channel.data, pd->rx_buf + pd->rx_buf_len,
+ sizeof(pd->rx_buf) - pd->rx_buf_len);
+ if (len > 0) {
+ pd->rx_buf_len += len;
+ }
+
+ if (MYNEWT_VAL(OSDP_PACKET_TRACE)) {
+ /**
+ * A crude way of identifying and not printing poll messages
+ * when OSDP_PACKET_TRACE is enabled. This is an early
+ * print to catch errors so keeping it simple.
+ * OSDP_CMD_ID_OFFSET + 2 is also checked as the CMD_ID can be
+ * pushed back by 2 bytes if secure channel block is present in
+ * header.
+ */
+ if (pd->rx_buf_len > MYNEWT_VAL(OSDP_CMD_ID_OFFSET) + 2 &&
+ pd->rx_buf[MYNEWT_VAL(OSDP_CMD_ID_OFFSET)] != CMD_POLL &&
+ pd->rx_buf[MYNEWT_VAL(OSDP_CMD_ID_OFFSET) + 2] != CMD_POLL) {
+ osdp_dump(pd->rx_buf, pd->rx_buf_len,
+ "OSDP: PD[%d]: Received\n", pd->address);
+ }
+ }
+
+ 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_PD_ERR_NO_DATA;
+ }
+ if (err == OSDP_ERR_PKT_FMT) {
+ return OSDP_PD_ERR_GENERIC;
+ }
+ if (err == OSDP_ERR_PKT_NONE) {
+ pd->reply_id = 0; /* reset past reply ID so phy can send NAK */
+ pd->ephemeral_data[0] = 0; /* reset past NAK reason */
+ len = osdp_phy_decode_packet(pd, pd->rx_buf, len, &buf);
+ if (len <= 0) {
+ if (pd->reply_id != 0) {
+ return OSDP_PD_ERR_REPLY; /* Send a NAK */
+ }
+ return OSDP_PD_ERR_GENERIC; /* fatal errors */
+ }
+ err = pd_decode_command(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 void
+osdp_update(struct osdp_pd *pd)
+{
+ int ret;
+
+ switch (pd->state) {
+ case OSDP_PD_STATE_IDLE:
+ if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE) &&
+ osdp_millis_since(pd->sc_tstamp) > MYNEWT_VAL(OSDP_PD_SC_TIMEOUT_MS)) {
+ OSDP_LOG_INFO("PD SC session timeout!\n");
+ CLEAR_FLAG(pd, PD_FLAG_SC_ACTIVE);
+ }
+ ret = pd->channel.recv(pd->channel.data, pd->rx_buf,
+ sizeof(pd->rx_buf));
+ if (ret <= 0) {
+ break;
+ }
+ pd->rx_buf_len = ret;
+ pd->tstamp = osdp_millis_now();
+ pd->state = OSDP_PD_STATE_PROCESS_CMD;
+ __fallthrough;
+ case OSDP_PD_STATE_PROCESS_CMD:
+ ret = pd_receive_packet(pd);
+ if (ret == OSDP_PD_ERR_NO_DATA &&
+ osdp_millis_since(pd->tstamp) < MYNEWT_VAL(OSDP_RESP_TOUT_MS)) {
+ break;
+ }
+ if (ret != OSDP_PD_ERR_NONE && ret != OSDP_PD_ERR_REPLY) {
+ OSDP_LOG_ERROR("CMD receive error/timeout - err:%d\n", ret);
+ pd->state = OSDP_PD_STATE_ERR;
+ break;
+ }
+ if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE) &&
+ ret == OSDP_PD_ERR_NONE) {
+ pd->sc_tstamp = osdp_millis_now();
+ }
+ pd->state = OSDP_PD_STATE_SEND_REPLY;
+ __fallthrough;
+ case OSDP_PD_STATE_SEND_REPLY:
+ if (pd_send_reply(pd) == -1) {
+ pd->state = OSDP_PD_STATE_ERR;
+ break;
+ }
+ pd->rx_buf_len = 0;
+ 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);
+ if (pd->channel.flush) {
+ pd->channel.flush(pd->channel.data);
+ }
+ 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));
+ }
+}
+
+osdp_t *
+osdp_pd_setup(struct osdp_ctx *osdp_ctx, osdp_pd_info_t *info, uint8_t *scbk)
+{
+ struct osdp_pd *pd;
+ struct osdp_cp *cp;
+ struct osdp *ctx;
+
+ assert(info);
+
+ /*
+ osdp_log_ctx_set(info->address);
+ */
+
+ ctx = &osdp_ctx->ctx;
+ ctx->magic = 0xDEADBEAF;
+
+ ctx->cp = &osdp_ctx->cp_ctx;
+ cp = TO_CP(ctx);
+ cp->__parent = ctx;
+ cp->num_pd = 1;
+
+ ctx->pd = &osdp_ctx->pd_ctx[0];
+ SET_CURRENT_PD(ctx, 0);
+ pd = TO_PD(ctx, 0);
+
+ pd->__parent = ctx;
+ pd->offset = 0;
+ pd->baud_rate = info->baud_rate;
+ pd->address = info->address;
+ pd->flags = info->flags;
+ pd->seq_number = -1;
+ memcpy(&pd->channel, &info->channel, sizeof(struct osdp_channel));
+
+ if (pd_event_queue_init(pd)) {
+ goto error;
+ }
+
+ if (scbk == NULL) {
+ if (ISSET_FLAG(pd, OSDP_FLAG_ENFORCE_SECURE)) {
+ OSDP_LOG_ERROR("SCBK must be provided in ENFORCE_SECURE\n");
+ goto error;
+ }
+ OSDP_LOG_WARN("SCBK not provided. PD is in INSTALL_MODE\n");
+ SET_FLAG(pd, OSDP_FLAG_INSTALL_MODE);
+ } else {
+ memcpy(pd->sc.scbk, scbk, 16);
+ }
+ SET_FLAG(pd, PD_FLAG_SC_CAPABLE);
+ if (IS_ENABLED(CONFIG_OSDP_SKIP_MARK_BYTE)) {
+ SET_FLAG(pd, PD_FLAG_PKT_SKIP_MARK);
+ }
+ osdp_pd_set_attributes(pd, info->cap, &info->id);
+ osdp_pd_set_attributes(pd, osdp_pd_cap, NULL);
+
+ SET_FLAG(pd, PD_FLAG_PD_MODE); /* used in checks in phy */
+
+ osdp_pd_set_command_callback(ctx, info->pd_cb, NULL);
+
+ OSDP_LOG_INFO("PD setup complete\n");
+ return (osdp_t *) ctx;
+
+error:
+ osdp_pd_teardown((osdp_t *) ctx);
+ return NULL;
+}
+
+/* --- Exported Methods --- */
+
+void
+osdp_pd_teardown(osdp_t *ctx)
+{
+ assert(ctx);
+
+ pd_event_queue_del(TO_PD(ctx, 0));
+
+ /*
+ These are static allocations in the ported version
+ safe_free(TO_PD(ctx, 0));
+ safe_free(TO_CP(ctx));
+ safe_free(ctx);
+ */
+}
+
+void
+osdp_refresh(osdp_t *ctx)
+{
+ assert(ctx);
+ struct osdp_pd *pd = GET_CURRENT_PD(ctx);
+
+ osdp_update(pd);
+}
+
+void
+osdp_pd_set_capabilities(osdp_t *ctx, struct osdp_pd_cap *cap)
+{
+ assert(ctx);
+ struct osdp_pd *pd = GET_CURRENT_PD(ctx);
+
+ osdp_pd_set_attributes(pd, cap, NULL);
+}
+
+void
+osdp_pd_set_command_callback(osdp_t *ctx, pd_command_callback_t cb,
+ void *arg)
+{
+ assert(ctx);
+ struct osdp_pd *pd = GET_CURRENT_PD(ctx);
+
+ pd->command_callback_arg = arg;
+ pd->command_callback = cb;
+}
+
+int
+osdp_pd_notify_event(osdp_t *ctx, struct osdp_event *event)
+{
+ assert(ctx);
+ struct osdp_event *ev;
+ struct osdp_pd *pd = GET_CURRENT_PD(ctx);
+
+ ev = pd_event_alloc(pd);
+ if (ev == NULL) {
+ return -1;
+ }
+
+ memcpy(ev, event, sizeof(struct osdp_event));
+ pd_event_enqueue(pd, ev);
+ return 0;
+}
+
+#endif /* OSDP_MODE_PD */
diff --git a/net/osdp/src/osdp_phy.c b/net/osdp/src/osdp_phy.c
new file mode 100644
index 0000000..ee041fd
--- /dev/null
+++ b/net/osdp/src/osdp_phy.c
@@ -0,0 +1,475 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <string.h>
+#include "modlog/modlog.h"
+#include "osdp/osdp_common.h"
+
+#define LOG_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 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, mark_byte_len = 0;
+ struct osdp_packet_header *pkt;
+
+ ARG_UNUSED(pd);
+ if (ISSET_FLAG(pd, PD_FLAG_PKT_HAS_MARK)) {
+ mark_byte_len = 1;
+ buf += 1;
+ }
+ pkt = (struct osdp_packet_header *)buf;
+ if (pkt->control & PKT_CONTROL_SCB) {
+ sb_len = pkt->data[0];
+ }
+ return mark_byte_len + 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);
+ if (ISSET_FLAG(pd, PD_FLAG_PKT_HAS_MARK)) {
+ buf += 1;
+ }
+ 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, id, scb_len = 0, mark_byte_len = 0;
+ 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) {
+ OSDP_LOG_ERROR("packet_init: out of space! CMD: %02x", pd->cmd_id);
+ return OSDP_ERR_PKT_FMT;
+ }
+
+ /**
+ * In PD mode just follow what we received from CP. In CP mode, as we
+ * initiate the transaction, choose based on CONFIG_OSDP_SKIP_MARK_BYTE.
+ */
+ if ((pd_mode && ISSET_FLAG(pd, PD_FLAG_PKT_HAS_MARK)) ||
+ (!pd_mode && !ISSET_FLAG(pd, PD_FLAG_PKT_SKIP_MARK))) {
+ buf[0] = OSDP_PKT_MARK;
+ buf++;
+ mark_byte_len = 1;
+ SET_FLAG(pd, PD_FLAG_PKT_HAS_MARK);
+ }
+
+ /* Fill packet header */
+ pkt = (struct osdp_packet_header *)buf;
+ 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;
+
+ if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) {
+ pkt->control |= PKT_CONTROL_SCB;
+ pkt->data[0] = scb_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] = scb_len = 3;
+ pkt->data[1] = SCS_11;
+ }
+
+ return mark_byte_len + sizeof(struct osdp_packet_header) + scb_len;
+}
+
+int
+osdp_phy_packet_finalize(struct osdp_pd *pd, uint8_t *buf,
+ int len, int max_len)
+{
+ uint8_t *data;
+ uint16_t crc16;
+ struct osdp_packet_header *pkt;
+ int i, is_cmd, data_len;
+
+ is_cmd = !ISSET_FLAG(pd, PD_FLAG_PD_MODE);
+
+ /* Do a sanity check only; we expect expect header to be prefilled */
+ if ((unsigned long)len <= sizeof(struct osdp_packet_header)) {
+ OSDP_LOG_ERROR("PKT_F: Invalid header");
+ return OSDP_ERR_PKT_FMT;
+ }
+
+ if (ISSET_FLAG(pd, PD_FLAG_PKT_HAS_MARK)) {
+ if (buf[0] != OSDP_PKT_MARK) {
+ OSDP_LOG_ERROR("PKT_F: MARK validation failed! ID: 0x%02x",
+ is_cmd ? pd->cmd_id : pd->reply_id);
+ return OSDP_ERR_PKT_FMT;
+ }
+ /* temporarily get rid of mark byte */
+ buf += 1;
+ len -= 1;
+ max_len -= 1;
+ }
+ pkt = (struct osdp_packet_header *)buf;
+ if (pkt->som != OSDP_PKT_SOM) {
+ OSDP_LOG_ERROR("PKT_F: header SOM validation failed! ID: 0x%02x",
+ is_cmd ? pd->cmd_id : pd->reply_id);
+ return OSDP_ERR_PKT_FMT;
+ }
+
+ /* len: with 2 byte CRC */
+ pkt->len_lsb = BYTE_0(len + 2);
+ pkt->len_msb = BYTE_1(len + 2);
+
+ if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE) &&
+ pkt->control & PKT_CONTROL_SCB &&
+ pkt->data[1] >= SCS_15) {
+ 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: with 2 byte CRC; with 4 byte MAC */
+ pkt->len_lsb = BYTE_0(len + 2 + 4);
+ pkt->len_msb = BYTE_1(len + 2 + 4);
+
+ /* compute and extend the buf with 4 MAC bytes */
+ osdp_compute_mac(pd, is_cmd, buf, len);
+ data = is_cmd ? pd->sc.c_mac : pd->sc.r_mac;
+ for (i = 0; i < 4; i++) {
+ buf[len + i] = data[i];
+ }
+ len += 4;
+ }
+
+ /* fill crc16 */
+ if (len + 2 > max_len) {
+ goto out_of_space_error;
+ }
+ crc16 = osdp_compute_crc16(buf, len);
+ buf[len + 0] = BYTE_0(crc16);
+ buf[len + 1] = BYTE_1(crc16);
+ len += 2;
+
+ if (ISSET_FLAG(pd, PD_FLAG_PKT_HAS_MARK)) {
+ len += 1; /* put back mark byte */
+ }
+
+ return len;
+
+out_of_space_error:
+ OSDP_LOG_ERROR("PKT_F: Out of buffer space! CMD(%02x)", pd->cmd_id);
+ return OSDP_ERR_PKT_FMT;
+}
+
+int
+osdp_phy_check_packet(struct osdp_pd *pd, uint8_t *buf, int len,
+ int *one_pkt_len)
+{
+ uint16_t comp, cur;
+ int pd_addr, pkt_len;
+ struct osdp_packet_header *pkt;
+
+ /* wait till we have the header */
+ if ((unsigned long)len < sizeof(struct osdp_packet_header)) {
+ /* incomplete data */
+ return OSDP_ERR_PKT_WAIT;
+ }
+
+ CLEAR_FLAG(pd, PD_FLAG_PKT_HAS_MARK);
+ if (buf[0] == OSDP_PKT_MARK) {
+ buf += 1;
+ len -= 1;
+ SET_FLAG(pd, PD_FLAG_PKT_HAS_MARK);
+ }
+
+ pkt = (struct osdp_packet_header *)buf;
+
+ /* validate packet header */
+ if (pkt->som != OSDP_PKT_SOM) {
+ OSDP_LOG_ERROR("Invalid SOM 0x%02x", pkt->som);
+ return OSDP_ERR_PKT_FMT;
+ }
+
+ if (!ISSET_FLAG(pd, PD_FLAG_PD_MODE) &&
+ !(pkt->pd_address & 0x80)) {
+ OSDP_LOG_ERROR("Reply without address MSB set!", pkt->pd_address);
+ return OSDP_ERR_PKT_FMT;
+ }
+
+ /* validate packet length */
+ pkt_len = (pkt->len_msb << 8) | pkt->len_lsb;
+ if (len < pkt_len) {
+ /* wait for more data? */
+ return OSDP_ERR_PKT_WAIT;
+ }
+
+ *one_pkt_len = pkt_len + (ISSET_FLAG(pd, PD_FLAG_PKT_HAS_MARK) ? 1 : 0);
+
+ /* validate CRC/checksum */
+ if (pkt->control & PKT_CONTROL_CRC) {
+ cur = (buf[pkt_len - 1] << 8) | buf[pkt_len - 2];
+ comp = osdp_compute_crc16(buf, pkt_len - 2);
+ if (comp != cur) {
+ OSDP_LOG_ERROR("Invalid crc 0x%04x/0x%04x", comp, cur);
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_MSG_CHK;
+ return OSDP_ERR_PKT_CHECK;
+ }
+ } else {
+ cur = buf[pkt_len - 1];
+ comp = osdp_compute_checksum(buf, pkt_len - 1);
+ if (comp != cur) {
+ OSDP_LOG_ERROR("Invalid checksum %02x/%02x", comp, cur);
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_MSG_CHK;
+ return OSDP_ERR_PKT_CHECK;
+ }
+ }
+
+ /* 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 (!ISSET_FLAG(pd, PD_FLAG_PD_MODE)) {
+ OSDP_LOG_ERROR("Invalid pd address %d", pd_addr);
+ return OSDP_ERR_PKT_FMT;
+ }
+ return OSDP_ERR_PKT_SKIP;
+ }
+
+ /* validate sequence number */
+ comp = pkt->control & PKT_CONTROL_SQN;
+ if (ISSET_FLAG(pd, PD_FLAG_PD_MODE)) {
+ if (comp == 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 (comp == pd->seq_number) {
+ /**
+ * TODO: PD must resend the last response if CP send the
+ * same sequence number again.
+ */
+ OSDP_LOG_ERROR("seq-repeat/reply-resend not supported!");
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_SEQ_NUM;
+ return OSDP_ERR_PKT_FMT;
+ }
+ }
+ cur = osdp_phy_get_seq_number(pd, ISSET_FLAG(pd, PD_FLAG_PD_MODE));
+ if (cur != comp && !ISSET_FLAG(pd, PD_FLAG_SKIP_SEQ_CHECK)) {
+ OSDP_LOG_ERROR("packet seq mismatch %d/%d", cur, comp);
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_SEQ_NUM;
+ return OSDP_ERR_PKT_FMT;
+ }
+
+ return OSDP_ERR_PKT_NONE;
+}
+
+int
+osdp_phy_decode_packet(struct osdp_pd *pd, uint8_t *buf, int len,
+ uint8_t **pkt_start)
+{
+ uint8_t *data, *mac;
+ int mac_offset, is_cmd;
+ struct osdp_packet_header *pkt;
+
+ if (ISSET_FLAG(pd, PD_FLAG_PKT_HAS_MARK)) {
+ /* Consume mark byte */
+ buf += 1;
+ len -= 1;
+ }
+
+ pkt = (struct osdp_packet_header *)buf;
+ len -= pkt->control & PKT_CONTROL_CRC ? 2 : 1;
+ mac_offset = len - 4;
+ data = pkt->data;
+ len -= sizeof(struct osdp_packet_header);
+
+ if (pkt->control & PKT_CONTROL_SCB) {
+ if (ISSET_FLAG(pd, PD_FLAG_PD_MODE) &&
+ !ISSET_FLAG(pd, PD_FLAG_SC_CAPABLE)) {
+ OSDP_LOG_ERROR("PD is not SC capable");
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_SC_UNSUP;
+ return OSDP_ERR_PKT_FMT;
+ }
+ if (pkt->data[1] < SCS_11 || pkt->data[1] > SCS_18) {
+ OSDP_LOG_ERROR("Invalid SB Type");
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_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 OSDP_FLAG_INSTALL_MODE).
+ */
+ if (ISSET_FLAG(pd, OSDP_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)) {
+ OSDP_LOG_ERROR("Received plain-text message in SC");
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_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, mac_offset);
+ mac = is_cmd ? pd->sc.c_mac : pd->sc.r_mac;
+ if (memcmp(buf + mac_offset, mac, 4) != 0) {
+ OSDP_LOG_ERROR("Invalid MAC; discarding SC");
+ CLEAR_FLAG(pd, PD_FLAG_SC_ACTIVE);
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_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) {
+ OSDP_LOG_ERROR("Failed at decrypt; discarding SC");
+ CLEAR_FLAG(pd, PD_FLAG_SC_ACTIVE);
+ pd->reply_id = REPLY_NAK;
+ pd->ephemeral_data[0] = OSDP_PD_NAK_SC_COND;
+ return OSDP_ERR_PKT_FMT;
+ }
+ len += 1; /* put back cmd/reply ID */
+ }
+ }
+
+ *pkt_start = data;
+ return len;
+}
+
+void
+osdp_phy_state_reset(struct osdp_pd *pd)
+{
+ pd->phy_state = 0;
+ pd->seq_number = -1;
+ pd->rx_buf_len = 0;
+}
diff --git a/net/osdp/src/osdp_sc.c b/net/osdp/src/osdp_sc.c
new file mode 100644
index 0000000..24dcb89
--- /dev/null
+++ b/net/osdp/src/osdp_sc.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2019 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "modlog/modlog.h"
+
+#include <string.h>
+#include "osdp/osdp_common.h"
+
+#define TAG "SC: "
+
+#define OSDP_SC_EOM_MARKER 0x80 /* End of Message Marker */
+
+/* Default key as specified in OSDP specification */
+static const uint8_t osdp_scbk_default[16] = {
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
+};
+
+void
+osdp_compute_scbk(struct osdp_pd *pd, uint8_t *master_key, uint8_t *scbk)
+{
+ int i;
+
+ memcpy(scbk, pd->sc.pd_client_uid, 8);
+ for (i = 8; i < 16; i++) {
+ scbk[i] = ~scbk[i - 8];
+ }
+ osdp_encrypt(master_key, NULL, scbk, 16);
+}
+
+void
+osdp_compute_session_keys(struct osdp *ctx)
+{
+ int i;
+ struct osdp_pd *pd = GET_CURRENT_PD(ctx);
+
+ if (ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD)) {
+ memcpy(pd->sc.scbk, osdp_scbk_default, 16);
+ } else {
+ /**
+ * Compute SCBK only in CP mode. PD mode, expect to already have
+ * the SCBK (sent from application layer).
+ */
+ if (ISSET_FLAG(pd, PD_FLAG_PD_MODE) == 0) {
+ osdp_compute_scbk(pd, ctx->sc_master_key, pd->sc.scbk);
+ }
+ }
+
+ memset(pd->sc.s_enc, 0, 16);
+ memset(pd->sc.s_mac1, 0, 16);
+ memset(pd->sc.s_mac2, 0, 16);
+
+ pd->sc.s_enc[0] = 0x01;
+ pd->sc.s_enc[1] = 0x82;
+ pd->sc.s_mac1[0] = 0x01;
+ pd->sc.s_mac1[1] = 0x01;
+ pd->sc.s_mac2[0] = 0x01;
+ pd->sc.s_mac2[1] = 0x02;
+
+ for (i = 2; i < 8; i++) {
+ pd->sc.s_enc[i] = pd->sc.cp_random[i - 2];
+ pd->sc.s_mac1[i] = pd->sc.cp_random[i - 2];
+ pd->sc.s_mac2[i] = pd->sc.cp_random[i - 2];
+ }
+
+ osdp_encrypt(pd->sc.scbk, NULL, pd->sc.s_enc, 16);
+ osdp_encrypt(pd->sc.scbk, NULL, pd->sc.s_mac1, 16);
+ osdp_encrypt(pd->sc.scbk, NULL, pd->sc.s_mac2, 16);
+}
+
+void
+osdp_compute_cp_cryptogram(struct osdp_pd *pd)
+{
+ /* cp_cryptogram = AES-ECB( pd_random[8] || cp_random[8], s_enc ) */
+ memcpy(pd->sc.cp_cryptogram + 0, pd->sc.pd_random, 8);
+ memcpy(pd->sc.cp_cryptogram + 8, pd->sc.cp_random, 8);
+ osdp_encrypt(pd->sc.s_enc, NULL, pd->sc.cp_cryptogram, 16);
+}
+
+/**
+ * Like memcmp; but operates at constant time.
+ *
+ * Returns 0 if memory pointed to by s1 and and s2 are identical; non-zero
+ * otherwise.
+ */
+static int
+osdp_ct_compare(const void *s1, const void *s2, size_t len)
+{
+ size_t i, ret = 0;
+ const uint8_t *_s1 = s1;
+ const uint8_t *_s2 = s2;
+
+ for (i = 0; i < len; i++) {
+ ret |= _s1[i] ^ _s2[i];
+ }
+ return (int)ret;
+}
+
+int
+osdp_verify_cp_cryptogram(struct osdp_pd *pd)
+{
+ uint8_t cp_crypto[16];
+
+ /* cp_cryptogram = AES-ECB( pd_random[8] || cp_random[8], s_enc ) */
+ memcpy(cp_crypto + 0, pd->sc.pd_random, 8);
+ memcpy(cp_crypto + 8, pd->sc.cp_random, 8);
+ osdp_encrypt(pd->sc.s_enc, NULL, cp_crypto, 16);
+
+ if (osdp_ct_compare(pd->sc.cp_cryptogram, cp_crypto, 16) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+void
+osdp_compute_pd_cryptogram(struct osdp_pd *pd)
+{
+ /* pd_cryptogram = AES-ECB( cp_random[8] || pd_random[8], s_enc ) */
+ memcpy(pd->sc.pd_cryptogram + 0, pd->sc.cp_random, 8);
+ memcpy(pd->sc.pd_cryptogram + 8, pd->sc.pd_random, 8);
+ osdp_encrypt(pd->sc.s_enc, NULL, pd->sc.pd_cryptogram, 16);
+}
+
+int
+osdp_verify_pd_cryptogram(struct osdp_pd *pd)
+{
+ uint8_t pd_crypto[16];
+
+ /* pd_cryptogram = AES-ECB( cp_random[8] || pd_random[8], s_enc ) */
+ memcpy(pd_crypto + 0, pd->sc.cp_random, 8);
+ memcpy(pd_crypto + 8, pd->sc.pd_random, 8);
+ osdp_encrypt(pd->sc.s_enc, NULL, pd_crypto, 16);
+
+ if (osdp_ct_compare(pd->sc.pd_cryptogram, pd_crypto, 16) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+void
+osdp_compute_rmac_i(struct osdp_pd *pd)
+{
+ /* rmac_i = AES-ECB( AES-ECB( cp_cryptogram, s_mac1 ), s_mac2 ) */
+ memcpy(pd->sc.r_mac, pd->sc.cp_cryptogram, 16);
+ osdp_encrypt(pd->sc.s_mac1, NULL, pd->sc.r_mac, 16);
+ osdp_encrypt(pd->sc.s_mac2, NULL, pd->sc.r_mac, 16);
+}
+
+int
+osdp_decrypt_data(struct osdp_pd *pd, int is_cmd, uint8_t *data, int length)
+{
+ int i;
+ uint8_t iv[16];
+
+ if (length % 16 != 0) {
+ OSDP_LOG_ERROR(TAG "decrypt_pkt invalid len:%d\n", length);
+ return -1;
+ }
+
+ memcpy(iv, is_cmd ? pd->sc.r_mac : pd->sc.c_mac, 16);
+ for (i = 0; i < 16; i++) {
+ iv[i] = ~iv[i];
+ }
+
+ osdp_decrypt(pd->sc.s_enc, iv, data, length);
+
+ while (data[length - 1] == 0x00) {
+ length--;
+ }
+ if (data[length - 1] != OSDP_SC_EOM_MARKER) {
+ return -1;
+ }
+ data[length - 1] = 0;
+
+ return length - 1;
+}
+
+int
+osdp_encrypt_data(struct osdp_pd *pd, int is_cmd, uint8_t *data, int length)
+{
+ int i, pad_len;
+ uint8_t iv[16];
+
+ data[length] = OSDP_SC_EOM_MARKER; /* append EOM marker */
+ pad_len = AES_PAD_LEN(length + 1);
+ if ((pad_len - length - 1) > 0) {
+ memset(data + length + 1, 0, pad_len - length - 1);
+ }
+ memcpy(iv, is_cmd ? pd->sc.r_mac : pd->sc.c_mac, 16);
+ for (i = 0; i < 16; i++) {
+ iv[i] = ~iv[i];
+ }
+
+ osdp_encrypt(pd->sc.s_enc, iv, data, pad_len);
+
+ return pad_len;
+}
+
+int
+osdp_compute_mac(struct osdp_pd *pd, int is_cmd,
+ const uint8_t *data, int len)
+{
+ int pad_len;
+ uint8_t buf[MYNEWT_VAL(OSDP_UART_BUFFER_LENGTH)] = { 0 };
+ uint8_t iv[16];
+
+ memcpy(buf, data, len);
+ pad_len = (len % 16 == 0) ? len : AES_PAD_LEN(len);
+ if (len % 16 != 0) {
+ buf[len] = 0x80; /* end marker */
+ }
+ /**
+ * MAC for data blocks B[1] .. B[N] (post padding) is computed as:
+ * IV1 = R_MAC (or) C_MAC -- depending on is_cmd
+ * IV2 = B[N-1] after -- AES-CBC ( IV1, B[1] to B[N-1], SMAC-1 )
+ * MAC = AES-ECB ( IV2, B[N], SMAC-2 )
+ */
+
+ memcpy(iv, is_cmd ? pd->sc.r_mac : pd->sc.c_mac, 16);
+ if (pad_len > 16) {
+ /* N-1 blocks -- encrypted with SMAC-1 */
+ osdp_encrypt(pd->sc.s_mac1, iv, buf, pad_len - 16);
+ /* N-1 th block is the IV for N th block */
+ memcpy(iv, buf + pad_len - 32, 16);
+ }
+
+ /* N-th Block encrypted with SMAC-2 == MAC */
+ osdp_encrypt(pd->sc.s_mac2, iv, buf + pad_len - 16, 16);
+ memcpy(is_cmd ? pd->sc.c_mac : pd->sc.r_mac, buf + pad_len - 16, 16);
+
+ return 0;
+}
+
+void
+osdp_sc_init(struct osdp_pd *pd)
+{
+ uint8_t key[16];
+
+ if (ISSET_FLAG(pd, PD_FLAG_PD_MODE)) {
+ memcpy(key, pd->sc.scbk, 16);
+ }
+ memset(&pd->sc, 0, sizeof(struct osdp_secure_channel));
+ if (ISSET_FLAG(pd, PD_FLAG_PD_MODE)) {
+ memcpy(pd->sc.scbk, key, 16);
+ }
+ if (ISSET_FLAG(pd, PD_FLAG_PD_MODE)) {
+ pd->sc.pd_client_uid[0] = BYTE_0(pd->id.vendor_code);
+ pd->sc.pd_client_uid[1] = BYTE_1(pd->id.vendor_code);
+ pd->sc.pd_client_uid[2] = BYTE_0(pd->id.model);
+ pd->sc.pd_client_uid[3] = BYTE_1(pd->id.version);
+ pd->sc.pd_client_uid[4] = BYTE_0(pd->id.serial_number);
+ pd->sc.pd_client_uid[5] = BYTE_1(pd->id.serial_number);
+ pd->sc.pd_client_uid[6] = BYTE_2(pd->id.serial_number);
+ pd->sc.pd_client_uid[7] = BYTE_3(pd->id.serial_number);
+ }
+}
diff --git a/net/osdp/src/osdp_utils.c b/net/osdp/src/osdp_utils.c
new file mode 100644
index 0000000..39d99cd
--- /dev/null
+++ b/net/osdp/src/osdp_utils.c
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ * Copyright (c) 2019 Nordic Semiconductor ASA
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include "osdp/osdp_utils.h"
+
+static void
+die_oom(const char *msg, size_t count, size_t size)
+{
+ fprintf(stderr, "fatal: %s() out of memory during alloc for %zu*%zu\n",
+ msg, count, size);
+ exit(-1);
+}
+
+void
+safe_free(void *p)
+{
+ if (p != NULL) {
+ free(p);
+ }
+}
+
+void *
+safe_malloc(size_t size)
+
+{
+ void *p;
+
+ p = malloc(size);
+
+ if (p == NULL) {
+ die_oom("malloc", 1, size);
+ }
+
+ return p;
+}
+
+void *
+safe_calloc(size_t count, size_t size)
+{
+ void *p;
+
+ p = calloc(count, size);
+
+ if (p == NULL) {
+ die_oom("calloc", count, size);
+ }
+
+ return p;
+}
+
+void *
+safe_strdup(const char *s)
+{
+ char *p;
+
+ p = strdup(s);
+
+ if (p == NULL) {
+ die_oom("strdup", 1, strlen(s));
+ }
+
+ return p;
+}
+
+void *
+safe_realloc(void *data, size_t size)
+{
+ void *p;
+
+ p = realloc(data, size);
+ if (p == NULL) {
+ die_oom("realloc", 1, size);
+ }
+
+ return p;
+}
+
+void *
+safe_realloc_zero(void *data, size_t old_size, size_t new_size)
+{
+ void *p;
+
+ assert(old_size != new_size);
+
+ p = safe_realloc(data, new_size);
+ if (new_size > old_size) {
+ memset((unsigned char *)p + old_size, 0, new_size - old_size);
+ }
+ return p;
+}
+
+uint32_t
+round_up_pow2(uint32_t v)
+{
+ /* from the bit-twiddling hacks */
+ v -= 1;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v |= v >> 16;
+ v += 1;
+ return v;
+}
+
+void
+hexdump(const uint8_t *data, size_t len, const char *fmt, ...)
+{
+ size_t i;
+ va_list args;
+ char str[16 + 1] = {0};
+
+ va_start(args, fmt);
+ vprintf(fmt, args);
+ va_end(args);
+ printf(" [%zu] =>\n 0000 %02x ", len, data[0]);
+ str[0] = isprint(data[0]) ? data[0] : '.';
+ for (i = 1; i < len; i++) {
+ if ((i & 0x0f) == 0) {
+ printf(" |%16s|", str);
+ printf("\n %04zu ", i);
+ } else if ((i & 0x07) == 0) {
+ printf(" ");
+ }
+ printf("%02x ", data[i]);
+ str[i & 0x0f] = isprint(data[i]) ? data[i] : '.';
+ }
+ if ((i &= 0x0f) != 0) {
+ if (i <= 8) {
+ printf(" ");
+ }
+ while (i < 16) {
+ printf(" ");
+ str[i++] = ' ';
+ }
+ printf(" |%16s|", str);
+ }
+ printf("\n");
+}
+
+int
+char2hex(char c, uint8_t *x)
+{
+ if (c >= '0' && c <= '9') {
+ *x = c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ *x = c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ *x = c - 'A' + 10;
+ } else {
+ return -OS_EINVAL;
+ }
+
+ return 0;
+}
+
+size_t
+hex2bin(const char *hex, size_t hexlen, uint8_t *buf, size_t buflen)
+{
+ uint8_t dec;
+
+ if (buflen < hexlen / 2 + hexlen % 2) {
+ return 0;
+ }
+
+ /* if hexlen is uneven, insert leading zero nibble */
+ if (hexlen % 2) {
+ if (char2hex(hex[0], &dec) < 0) {
+ return 0;
+ }
+ buf[0] = dec;
+ hex++;
+ buf++;
+ }
+
+ /* regular hex conversion */
+ for (size_t i = 0; i < hexlen / 2; i++) {
+ if (char2hex(hex[2 * i], &dec) < 0) {
+ return 0;
+ }
+ buf[i] = dec << 4;
+
+ if (char2hex(hex[2 * i + 1], &dec) < 0) {
+ return 0;
+ }
+ buf[i] += dec;
+ }
+
+ return hexlen / 2 + hexlen % 2;
+}
diff --git a/net/osdp/src/queue.c b/net/osdp/src/queue.c
new file mode 100644
index 0000000..ef97da4
--- /dev/null
+++ b/net/osdp/src/queue.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+#include "osdp/queue.h"
+
+void
+queue_init(queue_t *queue)
+{
+ list_init(&queue->list);
+}
+
+void
+queue_enqueue(queue_t *queue, queue_node_t *node)
+{
+ list_append(&queue->list, node);
+}
+
+int
+queue_dequeue(queue_t *queue, queue_node_t **node)
+{
+ return list_popleft(&queue->list, node);
+}
+
+int
+queue_peek_last(queue_t *queue, queue_node_t **node)
+{
+ if (queue->list.tail == NULL) {
+ return -1;
+ }
+
+ *node = queue->list.tail;
+ return 0;
+}
+
+int
+queue_peek_first(queue_t *queue, queue_node_t **node)
+{
+ if (queue->list.head == NULL) {
+ return -1;
+ }
+
+ *node = queue->list.head;
+ return 0;
+}
diff --git a/net/osdp/src/slab.c b/net/osdp/src/slab.c
new file mode 100644
index 0000000..82f2342
--- /dev/null
+++ b/net/osdp/src/slab.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2020 Siddharth Chandrasekaran <si...@embedjournal.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+#include <string.h>
+
+#include "osdp/slab.h"
+
+int
+slab_init(slab_t *slab, size_t size, size_t count)
+{
+ slab->size = round_up_pow2(size);
+ slab->blob = safe_calloc(count, slab->size);
+ slab->alloc_map = safe_calloc((count + 31) / 32, sizeof(uint32_t));
+ slab->count = count;
+ return 0;
+}
+
+void
+slab_del(slab_t *slab)
+{
+ safe_free(slab->blob);
+ safe_free(slab->alloc_map);
+ memset(slab, 0, sizeof(slab_t));
+}
+
+int
+slab_alloc(slab_t *slab, void **p)
+{
+ size_t i = 0, offset = 0;
+
+ while (i < slab->count &&
+ slab->alloc_map[offset] & (1L << (i & 0x1f))) {
+ if ((i & 0x1f) == 0x1f) {
+ offset++;
+ }
+ i++;
+ }
+ if (i >= slab->count) {
+ return -1;
+ }
+ slab->alloc_map[offset] |= 1L << (i & 0x1f);
+ *p = slab->blob + (slab->size * i);
+ memset(*p, 0, slab->size);
+ return 0;
+}
+
+int
+slab_free(slab_t *slab, void *p)
+{
+ size_t i;
+
+ for (i = 0; i < slab->count; i++) {
+ if ((slab->blob + (slab->size * i)) == p) {
+ break;
+ }
+ }
+ if (i >= slab->count) {
+ return -1;
+ }
+ if (!(slab->alloc_map[i / 32] & (1L << (i & 0x1f)))) {
+ return -2;
+ }
+ slab->alloc_map[i / 32] &= ~(1L << (i & 0x1f));
+ return 0;
+}
diff --git a/net/osdp/syscfg.yml b/net/osdp/syscfg.yml
new file mode 100644
index 0000000..5cae42f
--- /dev/null
+++ b/net/osdp/syscfg.yml
@@ -0,0 +1,281 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+syscfg.defs:
+
+ OSDP_MODE_PD:
+ value: 0
+ description: 'Configure this device to operate as a PD (Peripheral Device)'
+
+ OSDP_MODE_CP:
+ value: 0
+ description: 'Configure this device to operate as a CP (Control Panel)'
+
+ OSDP_SC_ENABLED:
+ value: 0
+ description: 'Secure the OSDP communication channel with encryption and mutual
+ authentication.'
+
+ OSDP_USE_CRYPTO_HOOK:
+ value: 0
+ description: 'Override crypto functions.'
+
+ OSDP_SC_RETRY_WAIT_SEC:
+ value: 600
+ description: 'Time in seconds to wait after a secure channel failure, and before
+ retrying to establish it.'
+
+ OSDP_RESP_TOUT_MS:
+ value: 200
+ description: 'Response timeout'
+
+ OSDP_PD_SC_TIMEOUT_MS:
+ value: 400
+ description: 'OSDP_PD_SC_TIMEOUT_MS'
+
+ OSDP_UART_DEV_NAME:
+ description: Device name of UART device for OSDP"
+ value: '"NONE"'
+
+ OSDP_UART_BAUD_RATE:
+ value: 115200
+ description: 'OSDP defines that baud rate can be either 9600 or 38400 or
+ 115200.'
+
+ OSDP_UART_BUFFER_LENGTH:
+ value: 256
+ description: 'OSDP UART buffer length'
+
+ OSDP_THREAD_STACK_SIZE:
+ value: 1024
+ description: 'OSDP Thread stack size'
+
+ OSDP_PACKET_TRACE:
+ value: 0
+ description: 'Print bytes sent/received over OSDP to console
+ Prints bytes sent/received over OSDP to console for debugging.
+ LOG_HEXDUMP_DBG() is used to achieve this and can be very verbose.'
+
+ CONFIG_OSDP_SKIP_MARK_BYTE:
+ value: 0
+ description: 'Some devices send the leading 0xFF (MARK) byte on the packet while
+ others dont. Compile time macro to choose either modes.
+ - In PD mode, library will respond with MARK byte if the command sent
+ from the CP had it.
+ - In CP mode, library will send command with MARK byte if
+ CONFIG_OSDP_SKIP_MARK_BYTE is not enabled.'
+
+ OSDP_CMD_ID_OFFSET:
+ value: 6
+ description: 'If CONFIG_OSDP_SKIP_MARK_BYTE is 0, this offset is 6, else 5.'
+
+ # Peripheral Device mode config
+
+ OSDP_NUM_CONNECTED_PD:
+ value: 1
+ description: 'In PD mode, number of connected PDs is is always 1 and cannot be configured.'
+
+ OSDP_PD_COMMAND_QUEUE_SIZE:
+ value: 16
+ description: '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.'
+
+ OSDP_PD_ADDRESS:
+ value: 1
+ description: '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.'
+
+ OSDP_PD_SCBK:
+ value: '"NONE"'
+ description: 'Secure Channel Base Key (SCBK). Hexadecimal string representation
+ of the the 16 byte OSDP PD Secure Channel Base Key. When this field is sent
+ to "NONE", the PD is set to Install Mode. In this mode, the PD would allow a
+ CP to setup a secure channel with default SCBK. Once as secure channel is
+ active with the default key, the CP can send a KEYSET command to set new keys
+ to the PD. It is up to the user to make sure that the PD enters the Install Mode
+ only during provisioning time (controlled environment).'
+
+ # Peripheral Device ID Information
+
+ OSDP_PD_ID_VENDOR_CODE:
+ value: 0
+ description: 'PD Vendor Code. IEEE assigned OUI. Least 24 bits are valid. range 0 0x00FFFFFF.'
+
+ OSDP_PD_ID_MODEL:
+ value: 0
+ description: 'range 0 255 PD Product Model Number. Manufacturers model number. Least 8 bits are valid.'
+
+ OSDP_PD_ID_VERSION:
+ value: 0
+ description: 'PD Product Version. range 0 255 .Manufacturers version of this product. Least 8 bits are valid.'
+
+ OSDP_PD_ID_SERIAL_NUMBER:
+ value: 0
+ description: 'PD Serial Number. range 0 0xFFFFFFFF. A 4-byte serial number for the PD.'
+
+ OSDP_PD_ID_FIRMWARE_VERSION:
+ value: 0
+ description: 'hex PD Firmware Version. range 0 0x00FFFFFF'
+
+ # Contact Status Monitoring
+
+ OSDP_PD_CAP_CONTACT_STATUS_MONITORING_COMP_LEVEL:
+ value: 0
+ description: '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 guidelines.'
+
+ OSDP_PD_CAP_CONTACT_STATUS_MONITORING_NUM_ITEMS:
+ value: 0
+ description: 'The number of Inputs'
+
+ # Output Control
+
+ OSDP_PD_CAP_OUTPUT_CONTROL_COMP_LEVEL:
+ value: 0
+ description: '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.'
+
+ OSDP_PD_CAP_OUTPUT_CONTROL_NUM_ITEMS:
+ value: 0
+ description: 'The number of Outputs.'
+
+ # LED Control
+
+ OSDP_PD_CAP_READER_LED_CONTROL_COMP_LEVEL:
+ value: 0
+ description: 'Compliance Level
+ - 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'
+
+ OSDP_PD_CAP_READER_LED_CONTROL_NUM_ITEMS:
+ value: 0
+ description: 'The number of LEDs per reader.'
+
+ # Audible Output
+
+ OSDP_PD_CAP_READER_AUDIBLE_OUTPUT_COMP_LEVEL:
+ value: 0
+ description: 'Possible values:
+ - 01: the PD support on/off control only
+ - 02: the PD supports timed commands'
+
+ OSDP_PD_CAP_READER_AUDIBLE_OUTPUT_NUM_ITEMS:
+ value: 0
+ description: 'The number of audible annunciators per reader'
+
+ # Text Output
+
+ OSDP_PD_CAP_READER_TEXT_OUTPUT_COMP_LEVEL:
+ value: 0
+ description: '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.'
+
+ OSDP_PD_CAP_READER_TEXT_OUTPUT_NUM_ITEMS:
+ value: 0
+ description: 'Number of textual displays per reader'
+
+ # Card Data Format
+
+ OSDP_PD_CAP_CARD_DATA_FORMAT_COMP_LEVEL:
+ value: 0
+ description: '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.'
+
+ # Time Keeping
+
+ OSDP_PD_CAP_TIME_KEEPING_COMP_LEVEL:
+ value: 0
+ description: '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'
+
+ # CP mode config
+
+ OSDP_PD_ADDRESS_LIST:
+ value: '"abc"'
+ description: Comma Separated Values of PD addresses. The number of values in this
+ string should exactly match the number of connected PDs specified above
+
+
+ OSDP_CMD_RETRY_WAIT_SEC:
+ value: 30
+ description: 'Time in seconds to wait after a command failure, and before retrying or
+ issuing further commands.'
+
+ OSDP_PD_POLL_RATE:
+ value: 20
+ description: 'The Control Panel must query the Peripheral Device periodically to
+ maintain connection sequence and to get status and events. This option
+ defined the number of times such a POLL command is sent per second.'
+
+ OSDP_MASTER_KEY:
+ value: '"NONE"'
+ description: 'Secure Channel Master Key. Hexadecimal string representation of the the 16 byte OSDP Secure Channel
+ master Key. This is a mandatory key when secure channel is enabled.'
+
+ # Logging
+
+ OSDP_LOG_MODULE:
+ description: 'Numeric module ID to use for OSDP log messages.'
+ value: 150
+ OSDP_LOG_LVL:
+ description: 'Minimum level for the OSDP log.'
+ value: 0
+
+syscfg.logs:
+ OSDP_LOG:
+ module: MYNEWT_VAL(OSDP_LOG_MODULE)
+ level: MYNEWT_VAL(OSDP_LOG_LVL)
+
+syscfg.vals: