/*
 * 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 <stdint.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include "os/os.h"
#include "nimble/hci_common.h"
#include "nimble/ble_hci_trans.h"
#include "host/ble_gap.h"
#include "host/ble_monitor.h"
#include "ble_hs_priv.h"
#include "ble_hs_dbg_priv.h"

_Static_assert(sizeof (struct hci_data_hdr) == BLE_HCI_DATA_HDR_SZ,
               "struct hci_data_hdr must be 4 bytes");

typedef int ble_hs_hci_evt_fn(uint8_t event_code, uint8_t *data, int len);
static ble_hs_hci_evt_fn ble_hs_hci_evt_disconn_complete;
static ble_hs_hci_evt_fn ble_hs_hci_evt_encrypt_change;
static ble_hs_hci_evt_fn ble_hs_hci_evt_hw_error;
static ble_hs_hci_evt_fn ble_hs_hci_evt_num_completed_pkts;
static ble_hs_hci_evt_fn ble_hs_hci_evt_enc_key_refresh;
static ble_hs_hci_evt_fn ble_hs_hci_evt_le_meta;

typedef int ble_hs_hci_evt_le_fn(uint8_t subevent, uint8_t *data, int len);
static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_conn_complete;
static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_adv_rpt;
static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_conn_upd_complete;
static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_lt_key_req;
static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_conn_parm_req;
static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_dir_adv_rpt;
static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_phy_update_complete;
static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_ext_adv_rpt;
static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_rd_rem_used_feat_complete;
static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_scan_timeout;
static ble_hs_hci_evt_le_fn ble_hs_hci_evt_le_adv_set_terminated;

/* Statistics */
struct host_hci_stats
{
    uint32_t events_rxd;
    uint32_t good_acks_rxd;
    uint32_t bad_acks_rxd;
    uint32_t unknown_events_rxd;
};

#define BLE_HS_HCI_EVT_TIMEOUT        50      /* Milliseconds. */

/** Dispatch table for incoming HCI events.  Sorted by event code field. */
struct ble_hs_hci_evt_dispatch_entry {
    uint8_t event_code;
    ble_hs_hci_evt_fn *cb;
};

static const struct ble_hs_hci_evt_dispatch_entry ble_hs_hci_evt_dispatch[] = {
    { BLE_HCI_EVCODE_DISCONN_CMP, ble_hs_hci_evt_disconn_complete },
    { BLE_HCI_EVCODE_ENCRYPT_CHG, ble_hs_hci_evt_encrypt_change },
    { BLE_HCI_EVCODE_HW_ERROR, ble_hs_hci_evt_hw_error },
    { BLE_HCI_EVCODE_NUM_COMP_PKTS, ble_hs_hci_evt_num_completed_pkts },
    { BLE_HCI_EVCODE_ENC_KEY_REFRESH, ble_hs_hci_evt_enc_key_refresh },
    { BLE_HCI_EVCODE_LE_META, ble_hs_hci_evt_le_meta },
};

#define BLE_HS_HCI_EVT_DISPATCH_SZ \
    (sizeof ble_hs_hci_evt_dispatch / sizeof ble_hs_hci_evt_dispatch[0])

/** Dispatch table for incoming LE meta events.  Sorted by subevent field. */
struct ble_hs_hci_evt_le_dispatch_entry {
    uint8_t subevent;
    ble_hs_hci_evt_le_fn *cb;
};

static const struct ble_hs_hci_evt_le_dispatch_entry
        ble_hs_hci_evt_le_dispatch[] = {
    { BLE_HCI_LE_SUBEV_CONN_COMPLETE, ble_hs_hci_evt_le_conn_complete },
    { BLE_HCI_LE_SUBEV_ADV_RPT, ble_hs_hci_evt_le_adv_rpt },
    { BLE_HCI_LE_SUBEV_CONN_UPD_COMPLETE,
          ble_hs_hci_evt_le_conn_upd_complete },
    { BLE_HCI_LE_SUBEV_LT_KEY_REQ, ble_hs_hci_evt_le_lt_key_req },
    { BLE_HCI_LE_SUBEV_REM_CONN_PARM_REQ, ble_hs_hci_evt_le_conn_parm_req },
    { BLE_HCI_LE_SUBEV_ENH_CONN_COMPLETE, ble_hs_hci_evt_le_conn_complete },
    { BLE_HCI_LE_SUBEV_DIRECT_ADV_RPT, ble_hs_hci_evt_le_dir_adv_rpt },
    { BLE_HCI_LE_SUBEV_PHY_UPDATE_COMPLETE,
        ble_hs_hci_evt_le_phy_update_complete },
    { BLE_HCI_LE_SUBEV_EXT_ADV_RPT, ble_hs_hci_evt_le_ext_adv_rpt },
    { BLE_HCI_LE_SUBEV_RD_REM_USED_FEAT,
            ble_hs_hci_evt_le_rd_rem_used_feat_complete },
    { BLE_HCI_LE_SUBEV_SCAN_TIMEOUT,
            ble_hs_hci_evt_le_scan_timeout },
    { BLE_HCI_LE_SUBEV_ADV_SET_TERMINATED,
            ble_hs_hci_evt_le_adv_set_terminated },
};

#define BLE_HS_HCI_EVT_LE_DISPATCH_SZ \
    (sizeof ble_hs_hci_evt_le_dispatch / sizeof ble_hs_hci_evt_le_dispatch[0])

static const struct ble_hs_hci_evt_dispatch_entry *
ble_hs_hci_evt_dispatch_find(uint8_t event_code)
{
    const struct ble_hs_hci_evt_dispatch_entry *entry;
    int i;

    for (i = 0; i < BLE_HS_HCI_EVT_DISPATCH_SZ; i++) {
        entry = ble_hs_hci_evt_dispatch + i;
        if (entry->event_code == event_code) {
            return entry;
        }
    }

    return NULL;
}

static const struct ble_hs_hci_evt_le_dispatch_entry *
ble_hs_hci_evt_le_dispatch_find(uint8_t event_code)
{
    const struct ble_hs_hci_evt_le_dispatch_entry *entry;
    int i;

    for (i = 0; i < BLE_HS_HCI_EVT_LE_DISPATCH_SZ; i++) {
        entry = ble_hs_hci_evt_le_dispatch + i;
        if (entry->subevent == event_code) {
            return entry;
        }
    }

    return NULL;
}

static int
ble_hs_hci_evt_disconn_complete(uint8_t event_code, uint8_t *data, int len)
{
    struct hci_disconn_complete evt;
    const struct ble_hs_conn *conn;

    if (len < BLE_HCI_EVENT_DISCONN_COMPLETE_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    evt.status = data[2];
    evt.connection_handle = get_le16(data + 3);
    evt.reason = data[5];

    ble_hs_lock();
    conn = ble_hs_conn_find(evt.connection_handle);
    if (conn != NULL) {
        ble_hs_hci_add_avail_pkts(conn->bhc_outstanding_pkts);
    }
    ble_hs_unlock();

    ble_gap_rx_disconn_complete(&evt);

    return 0;
}

static int
ble_hs_hci_evt_encrypt_change(uint8_t event_code, uint8_t *data, int len)
{
    struct hci_encrypt_change evt;

    if (len < BLE_HCI_EVENT_ENCRYPT_CHG_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    evt.status = data[2];
    evt.connection_handle = get_le16(data + 3);
    evt.encryption_enabled = data[5];

    ble_sm_enc_change_rx(&evt);

    return 0;
}

static int
ble_hs_hci_evt_hw_error(uint8_t event_code, uint8_t *data, int len)
{
    uint8_t hw_code;

    if (len < BLE_HCI_EVENT_HW_ERROR_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    hw_code = data[0];
    ble_hs_hw_error(hw_code);

    return 0;
}

static int
ble_hs_hci_evt_enc_key_refresh(uint8_t event_code, uint8_t *data, int len)
{
    struct hci_encrypt_key_refresh evt;

    if (len < BLE_HCI_EVENT_ENC_KEY_REFRESH_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    evt.status = data[2];
    evt.connection_handle = get_le16(data + 3);

    ble_sm_enc_key_refresh_rx(&evt);

    return 0;
}

static int
ble_hs_hci_evt_num_completed_pkts(uint8_t event_code, uint8_t *data, int len)
{
    struct ble_hs_conn *conn;
    uint16_t num_pkts;
    uint16_t handle;
    uint8_t num_handles;
    int tx_outstanding;
    int off;
    int i;

    if (len < BLE_HCI_EVENT_HDR_LEN + BLE_HCI_EVENT_NUM_COMP_PKTS_HDR_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    off = BLE_HCI_EVENT_HDR_LEN;
    num_handles = data[off];
    if (len < BLE_HCI_EVENT_NUM_COMP_PKTS_HDR_LEN +
              num_handles * BLE_HCI_EVENT_NUM_COMP_PKTS_ENT_LEN) {
        return BLE_HS_ECONTROLLER;
    }
    off++;

    /* If we were previously blocked due to controller buffer exhaustion, and
     * this event indicates that some buffers have been freed, then the host
     * needs to resume transmitting.  `tx_outstanding` keeps track of whether
     * this is the case.
     */
    tx_outstanding = 0;

    for (i = 0; i < num_handles; i++) {
        handle = get_le16(data + off);
        num_pkts = get_le16(data + off + 2);
        off += (2 * sizeof(uint16_t));

        if (num_pkts > 0) {
            ble_hs_lock();
            conn = ble_hs_conn_find(handle);
            if (conn != NULL) {
                if (conn->bhc_outstanding_pkts < num_pkts) {
                    ble_hs_sched_reset(BLE_HS_ECONTROLLER);
                } else {
                    conn->bhc_outstanding_pkts -= num_pkts;
                }

                if (ble_hs_hci_avail_pkts == 0) {
                    tx_outstanding = 1;
                }

                ble_hs_hci_add_avail_pkts(num_pkts);
            }
            ble_hs_unlock();
        }
    }

    if (tx_outstanding) {
        ble_hs_wakeup_tx();
    }

    return 0;
}

static int
ble_hs_hci_evt_le_meta(uint8_t event_code, uint8_t *data, int len)
{
    const struct ble_hs_hci_evt_le_dispatch_entry *entry;
    uint8_t subevent;
    int rc;

    if (len < BLE_HCI_EVENT_HDR_LEN + BLE_HCI_LE_MIN_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    subevent = data[2];
    entry = ble_hs_hci_evt_le_dispatch_find(subevent);
    if (entry != NULL) {
        rc = entry->cb(subevent, data + BLE_HCI_EVENT_HDR_LEN,
                           len - BLE_HCI_EVENT_HDR_LEN);
        if (rc != 0) {
            return rc;
        }
    }

    return 0;
}

#if MYNEWT_VAL(BLE_EXT_ADV)
static struct hci_le_conn_complete pend_conn_complete;
#endif

static int
ble_hs_hci_evt_le_conn_complete(uint8_t subevent, uint8_t *data, int len)
{
    struct hci_le_conn_complete evt;
    int extended_offset = 0;

    if (len < BLE_HCI_LE_CONN_COMPLETE_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    /* this code processes two different events that are really similar */
    if ((subevent == BLE_HCI_LE_SUBEV_ENH_CONN_COMPLETE) &&
        ( len < BLE_HCI_LE_ENH_CONN_COMPLETE_LEN)) {
        return BLE_HS_ECONTROLLER;
    }

    memset(&evt, 0, sizeof(evt));

    evt.subevent_code = data[0];
    evt.status = data[1];

    if (evt.status == BLE_ERR_SUCCESS) {
        evt.connection_handle = get_le16(data + 2);
        evt.role = data[4];
        evt.peer_addr_type = data[5];
        memcpy(evt.peer_addr, data + 6, BLE_DEV_ADDR_LEN);

        /* enhanced connection event has the same information with these
         * extra fields stuffed into the middle */
        if (subevent == BLE_HCI_LE_SUBEV_ENH_CONN_COMPLETE) {
            memcpy(evt.local_rpa, data + 12, BLE_DEV_ADDR_LEN);
            memcpy(evt.peer_rpa, data + 18, BLE_DEV_ADDR_LEN);
            extended_offset = 12;
        } else {
            memset(evt.local_rpa, 0, BLE_DEV_ADDR_LEN);
            memset(evt.peer_rpa, 0, BLE_DEV_ADDR_LEN);
        }

        evt.conn_itvl = get_le16(data + 12 + extended_offset);
        evt.conn_latency = get_le16(data + 14 + extended_offset);
        evt.supervision_timeout = get_le16(data + 16 + extended_offset);
        evt.master_clk_acc = data[18 + extended_offset];
    } else {
#if MYNEWT_VAL(BLE_HS_DEBUG)
        evt.connection_handle = BLE_HS_CONN_HANDLE_NONE;
#endif
    }

#if MYNEWT_VAL(BLE_EXT_ADV)
    if (evt.status == BLE_ERR_DIR_ADV_TMO ||
                            evt.role == BLE_HCI_LE_CONN_COMPLETE_ROLE_SLAVE) {
    /* store this until we get set terminated event with adv handle */
        memcpy(&pend_conn_complete, &evt, sizeof(evt));
        return 0;
    }
#endif
    return ble_gap_rx_conn_complete(&evt, 0);
}

static int
ble_hs_hci_evt_le_adv_rpt_first_pass(uint8_t *data, int len)
{
    uint8_t num_reports;
    int off;
    int i;

    if (len < BLE_HCI_LE_ADV_RPT_MIN_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    num_reports = data[1];
    if (num_reports < BLE_HCI_LE_ADV_RPT_NUM_RPTS_MIN ||
        num_reports > BLE_HCI_LE_ADV_RPT_NUM_RPTS_MAX) {
        return BLE_HS_EBADDATA;
    }

    off = 2; /* Subevent code and num reports. */
    for (i = 0; i < num_reports; i++) {
        /* Move past event type (1), address type (1) and address (6) */
        off += 8;

        /* Add advertising data length (N), length (1) and rssi (1) */
        off += data[off];
        off += 2;

        /* Make sure we are not past length */
        if (off > len) {
            return BLE_HS_ECONTROLLER;
        }
    }

    /* Make sure length was correct */
    if (off != len) {
        return BLE_HS_ECONTROLLER;
    }

    return 0;
}

static int
ble_hs_hci_evt_le_adv_rpt(uint8_t subevent, uint8_t *data, int len)
{
    struct ble_gap_disc_desc desc = {0};
    uint8_t num_reports;
    int off;
    int rc;
    int i;

    /* Validate the event is formatted correctly */
    rc = ble_hs_hci_evt_le_adv_rpt_first_pass(data, len);
    if (rc != 0) {
        return rc;
    }

    desc.direct_addr = *BLE_ADDR_ANY;

    off = 2; /* skip sub-event and num reports */
    num_reports = data[1];
    for (i = 0; i < num_reports; i++) {
        desc.event_type = data[off];
        ++off;

        desc.addr.type = data[off];
        ++off;

        memcpy(desc.addr.val, data + off, 6);
        off += 6;

        desc.length_data = data[off];
        ++off;

        desc.data = data + off;
        off += desc.length_data;

        desc.rssi = data[off];
        ++off;

        ble_gap_rx_adv_report(&desc);
    }

    return 0;
}

static int
ble_hs_hci_evt_le_dir_adv_rpt(uint8_t subevent, uint8_t *data, int len)
{
    struct ble_gap_disc_desc desc = {0};
    uint8_t num_reports;
    int suboff;
    int off;
    int i;

    if (len < BLE_HCI_LE_ADV_DIRECT_RPT_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    num_reports = data[1];
    if (len != 2 + num_reports * BLE_HCI_LE_ADV_DIRECT_RPT_SUB_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    /* Data fields not present in a direct advertising report. */
    desc.data = NULL;
    desc.length_data = 0;

    for (i = 0; i < num_reports; i++) {
        suboff = 0;

        off = 2 + suboff * num_reports + i;
        desc.event_type = data[off];
        suboff++;

        off = 2 + suboff * num_reports + i;
        desc.addr.type = data[off];
        suboff++;

        off = 2 + suboff * num_reports + i * 6;
        memcpy(desc.addr.val, data + off, 6);
        suboff += 6;

        off = 2 + suboff * num_reports + i;
        desc.direct_addr.type = data[off];
        suboff++;

        off = 2 + suboff * num_reports + i * 6;
        memcpy(desc.direct_addr.val, data + off, 6);
        suboff += 6;

        off = 2 + suboff * num_reports + i;
        desc.rssi = data[off];
        suboff++;

        ble_gap_rx_adv_report(&desc);
    }

    return 0;
}

static int
ble_hs_hci_evt_le_rd_rem_used_feat_complete(uint8_t subevent, uint8_t *data,
                                                                        int len)
{
    struct hci_le_rd_rem_supp_feat_complete evt;

    if (len < BLE_HCI_LE_RD_REM_USED_FEAT_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    evt.subevent_code = data[0];
    evt.status = data[1];
    evt.connection_handle = get_le16(data + 2);
    memcpy(evt.features, data + 4, 8);

    ble_gap_rx_rd_rem_sup_feat_complete(&evt);

    return 0;
}

#if MYNEWT_VAL(BLE_EXT_ADV)
static int
ble_hs_hci_decode_legacy_type(uint16_t evt_type)
{
     switch (evt_type) {
     case BLE_HCI_LEGACY_ADV_EVTYPE_ADV_IND:
         return BLE_HCI_ADV_RPT_EVTYPE_ADV_IND;
     case BLE_HCI_LEGACY_ADV_EVTYPE_ADV_DIRECT_IND:
         return BLE_HCI_ADV_RPT_EVTYPE_DIR_IND;
     case BLE_HCI_LEGACY_ADV_EVTYPE_ADV_SCAN_IND:
         return BLE_HCI_ADV_RPT_EVTYPE_SCAN_IND;
     case BLE_HCI_LEGACY_ADV_EVTYPE_ADV_NONCON_IND:
         return BLE_HCI_ADV_RPT_EVTYPE_NONCONN_IND;
     case BLE_HCI_LEGACY_ADV_EVTYPE_SCAN_RSP_ADV_IND:
         return BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP;
     default:
         return -1;
     }
}
#endif

static int
ble_hs_hci_evt_le_ext_adv_rpt(uint8_t subevent, uint8_t *data, int len)
{
#if MYNEWT_VAL(BLE_EXT_ADV)
    struct ble_gap_ext_disc_desc desc;
    struct hci_ext_adv_report *ext_adv;
    struct hci_ext_adv_report_param *params;
    int num_reports;
    int i;
    int legacy_event_type;

    if (len < sizeof(*ext_adv)) {
        return BLE_HS_EBADDATA;
    }

    ext_adv = (struct hci_ext_adv_report *) data;
    num_reports = ext_adv->num_reports;
    if (num_reports < BLE_HCI_LE_ADV_RPT_NUM_RPTS_MIN ||
        num_reports > BLE_HCI_LE_ADV_RPT_NUM_RPTS_MAX) {

        return BLE_HS_EBADDATA;
    }

    if (len < (sizeof(*ext_adv) + ext_adv->num_reports * sizeof(*params))) {
        return BLE_HS_ECONTROLLER;
    }

    params = &ext_adv->params[0];
    for (i = 0; i < num_reports; i++) {
        memset(&desc, 0, sizeof(desc));

        desc.props = (params->evt_type) & 0x1F;
        if (desc.props & BLE_HCI_ADV_LEGACY_MASK) {
            legacy_event_type = ble_hs_hci_decode_legacy_type(params->evt_type);
            if (legacy_event_type < 0) {
                params += 1;
                continue;
            }
            desc.legacy_event_type = legacy_event_type;
            desc.data_status = BLE_GAP_EXT_ADV_DATA_STATUS_COMPLETE;
        } else {
            switch(params->evt_type & BLE_HCI_ADV_DATA_STATUS_MASK) {
            case BLE_HCI_ADV_DATA_STATUS_COMPLETE:
                desc.data_status = BLE_GAP_EXT_ADV_DATA_STATUS_COMPLETE;
                break;
            case BLE_HCI_ADV_DATA_STATUS_INCOMPLETE:
                desc.data_status = BLE_GAP_EXT_ADV_DATA_STATUS_INCOMPLETE;
                break;
            case BLE_HCI_ADV_DATA_STATUS_TRUNCATED:
                desc.data_status = BLE_GAP_EXT_ADV_DATA_STATUS_TRUNCATED;
                break;
            default:
                assert(false);
            }
        }
        desc.addr.type = params->addr_type;
        memcpy(desc.addr.val, params->addr, 6);
        desc.length_data = params->adv_data_len;
        desc.data = params->adv_data;
        desc.rssi = params->rssi;
        desc.tx_power = params->tx_power;
        memcpy(desc.direct_addr.val, params->dir_addr, 6);
        desc.direct_addr.type = params->dir_addr_type;
        desc.sid = params->sid;
        desc.prim_phy = params->prim_phy;
        desc.sec_phy = params->sec_phy;
        ble_gap_rx_ext_adv_report(&desc);
        params += 1;
    }
#endif
    return 0;
}

static int
ble_hs_hci_evt_le_scan_timeout(uint8_t subevent, uint8_t *data, int len)
{
#if MYNEWT_VAL(BLE_EXT_ADV) && NIMBLE_BLE_SCAN
        ble_gap_rx_le_scan_timeout();
#endif
        return 0;
}

static int
ble_hs_hci_evt_le_adv_set_terminated(uint8_t subevent, uint8_t *data, int len)
{
#if MYNEWT_VAL(BLE_EXT_ADV)
    struct hci_le_adv_set_terminated evt;

    if (len < BLE_HCI_LE_SUBEV_ADV_SET_TERMINATED_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    evt.subevent_code = data[0];
    evt.status = data[1];
    evt.adv_handle = data[2];
    evt.conn_handle = get_le16(data + 3);
    evt.completed_events = data[5];

    if (evt.status == 0) {
        /* ignore return code as we need to terminate advertising set anyway */
        ble_gap_rx_conn_complete(&pend_conn_complete, evt.adv_handle);
    }
    ble_gap_rx_adv_set_terminated(&evt);
#endif

    return 0;
}

static int
ble_hs_hci_evt_le_conn_upd_complete(uint8_t subevent, uint8_t *data, int len)
{
    struct hci_le_conn_upd_complete evt;

    if (len < BLE_HCI_LE_CONN_UPD_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    evt.subevent_code = data[0];
    evt.status = data[1];
    evt.connection_handle = get_le16(data + 2);
    evt.conn_itvl = get_le16(data + 4);
    evt.conn_latency = get_le16(data + 6);
    evt.supervision_timeout = get_le16(data + 8);

    if (evt.status == 0) {
        if (evt.conn_itvl < BLE_HCI_CONN_ITVL_MIN ||
            evt.conn_itvl > BLE_HCI_CONN_ITVL_MAX) {

            return BLE_HS_EBADDATA;
        }
        if (evt.conn_latency < BLE_HCI_CONN_LATENCY_MIN ||
            evt.conn_latency > BLE_HCI_CONN_LATENCY_MAX) {

            return BLE_HS_EBADDATA;
        }
        if (evt.supervision_timeout < BLE_HCI_CONN_SPVN_TIMEOUT_MIN ||
            evt.supervision_timeout > BLE_HCI_CONN_SPVN_TIMEOUT_MAX) {

            return BLE_HS_EBADDATA;
        }
    }

    ble_gap_rx_update_complete(&evt);

    return 0;
}

static int
ble_hs_hci_evt_le_lt_key_req(uint8_t subevent, uint8_t *data, int len)
{
    struct hci_le_lt_key_req evt;

    if (len < BLE_HCI_LE_LT_KEY_REQ_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    evt.subevent_code = data[0];
    evt.connection_handle = get_le16(data + 1);
    evt.random_number = get_le64(data + 3);
    evt.encrypted_diversifier = get_le16(data + 11);

    ble_sm_ltk_req_rx(&evt);

    return 0;
}

static int
ble_hs_hci_evt_le_conn_parm_req(uint8_t subevent, uint8_t *data, int len)
{
    struct hci_le_conn_param_req evt;

    if (len < BLE_HCI_LE_REM_CONN_PARM_REQ_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    evt.subevent_code = data[0];
    evt.connection_handle = get_le16(data + 1);
    evt.itvl_min = get_le16(data + 3);
    evt.itvl_max = get_le16(data + 5);
    evt.latency = get_le16(data + 7);
    evt.timeout = get_le16(data + 9);

    if (evt.itvl_min < BLE_HCI_CONN_ITVL_MIN ||
        evt.itvl_max > BLE_HCI_CONN_ITVL_MAX ||
        evt.itvl_min > evt.itvl_max) {

        return BLE_HS_EBADDATA;
    }
    if (evt.latency < BLE_HCI_CONN_LATENCY_MIN ||
        evt.latency > BLE_HCI_CONN_LATENCY_MAX) {

        return BLE_HS_EBADDATA;
    }
    if (evt.timeout < BLE_HCI_CONN_SPVN_TIMEOUT_MIN ||
        evt.timeout > BLE_HCI_CONN_SPVN_TIMEOUT_MAX) {

        return BLE_HS_EBADDATA;
    }

    ble_gap_rx_param_req(&evt);

    return 0;
}

static int
ble_hs_hci_evt_le_phy_update_complete(uint8_t subevent, uint8_t *data, int len)
{
    struct hci_le_phy_upd_complete evt;

    if (len < BLE_HCI_LE_PHY_UPD_LEN) {
        return BLE_HS_ECONTROLLER;
    }

    evt.subevent_code = data[0];
    evt.status = data[1];
    evt.connection_handle = get_le16(data + 2);
    evt.tx_phy = data[4];
    evt.rx_phy = data[5];

    ble_gap_rx_phy_update_complete(&evt);

    return 0;
}

int
ble_hs_hci_evt_process(uint8_t *data)
{
    const struct ble_hs_hci_evt_dispatch_entry *entry;
    uint8_t event_code;
    uint8_t param_len;
    int event_len;
    int rc;

    /* Count events received */
    STATS_INC(ble_hs_stats, hci_event);

    /* Display to console */
    ble_hs_dbg_event_disp(data);

    /* Process the event */
    event_code = data[0];
    param_len = data[1];

    event_len = param_len + 2;

    entry = ble_hs_hci_evt_dispatch_find(event_code);
    if (entry == NULL) {
        STATS_INC(ble_hs_stats, hci_unknown_event);
        rc = BLE_HS_ENOTSUP;
    } else {
        rc = entry->cb(event_code, data, event_len);
    }

    ble_hci_trans_buf_free(data);

    return rc;
}

/**
 * Called when a data packet is received from the controller.  This function
 * consumes the supplied mbuf, regardless of the outcome.
 *
 * @param om                    The incoming data packet, beginning with the
 *                                  HCI ACL data header.
 *
 * @return                      0 on success; nonzero on failure.
 */
int
ble_hs_hci_evt_acl_process(struct os_mbuf *om)
{
    struct hci_data_hdr hci_hdr;
    struct ble_hs_conn *conn;
    ble_l2cap_rx_fn *rx_cb;
    uint16_t conn_handle;
    int reject_cid;
    int rc;

    rc = ble_hs_hci_util_data_hdr_strip(om, &hci_hdr);
    if (rc != 0) {
        goto err;
    }

#if (BLETEST_THROUGHPUT_TEST == 0)
#if !BLE_MONITOR
    BLE_HS_LOG(DEBUG, "ble_hs_hci_evt_acl_process(): conn_handle=%u pb=%x "
                      "len=%u data=",
               BLE_HCI_DATA_HANDLE(hci_hdr.hdh_handle_pb_bc),
               BLE_HCI_DATA_PB(hci_hdr.hdh_handle_pb_bc),
               hci_hdr.hdh_len);
    ble_hs_log_mbuf(om);
    BLE_HS_LOG(DEBUG, "\n");
#endif
#endif

    if (hci_hdr.hdh_len != OS_MBUF_PKTHDR(om)->omp_len) {
        rc = BLE_HS_EBADDATA;
        goto err;
    }

    conn_handle = BLE_HCI_DATA_HANDLE(hci_hdr.hdh_handle_pb_bc);

    ble_hs_lock();

    conn = ble_hs_conn_find(conn_handle);
    if (conn == NULL) {
        /* Peer not connected; quietly discard packet. */
        rc = BLE_HS_ENOTCONN;
        reject_cid = -1;
    } else {
        /* Forward ACL data to L2CAP. */
        rc = ble_l2cap_rx(conn, &hci_hdr, om, &rx_cb, &reject_cid);
        om = NULL;
    }

    ble_hs_unlock();

    switch (rc) {
    case 0:
        /* Final fragment received. */
        BLE_HS_DBG_ASSERT(rx_cb != NULL);
        rc = rx_cb(conn->bhc_rx_chan);
        ble_l2cap_remove_rx(conn, conn->bhc_rx_chan);
        break;

    case BLE_HS_EAGAIN:
        /* More fragments on the way. */
        break;

    default:
        if (reject_cid != -1) {
            ble_l2cap_sig_reject_invalid_cid_tx(conn_handle, 0, 0, reject_cid);
        }
        goto err;
    }

    return 0;

err:
    os_mbuf_free_chain(om);
    return rc;
}
