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: