/*
 * 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 <stdlib.h>
#include <string.h>
#include <assert.h>
#include "syscfg/syscfg.h"
#include "os/os.h"
#include "nimble/ble.h"
#include "nimble/hci_common.h"
#include "controller/ble_phy.h"
#include "controller/ble_hw.h"
#include "controller/ble_ll.h"
#include "controller/ble_ll_sched.h"
#include "controller/ble_ll_scan.h"
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
#include "controller/ble_ll_scan_aux.h"
#endif
#include "controller/ble_ll_tmr.h"
#include "controller/ble_ll_hci.h"
#include "controller/ble_ll_whitelist.h"
#include "controller/ble_ll_resolv.h"
#include "controller/ble_ll_rfmgmt.h"
#include "controller/ble_ll_trace.h"
#include "controller/ble_ll_sync.h"
#include "ble_ll_conn_priv.h"

#if MYNEWT_VAL(BLE_LL_ROLE_OBSERVER)

/*
 * XXX:
 * 1) I think I can guarantee that we dont process things out of order if
 * I send an event when a scan request is sent. The scan_rsp_pending flag
 * code might be made simpler.
 *
 * 2) Interleave sending scan requests to different advertisers? I guess I need
 * a list of advertisers to which I sent a scan request and have yet to
 * receive a scan response from? Implement this.
 */

/* Dont allow more than 255 of these entries */
#if MYNEWT_VAL(BLE_LL_NUM_SCAN_RSP_ADVS) > 255
    #error "Cannot have more than 255 scan response entries!"
#endif

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
#define SCAN_VALID_PHY_MASK     (BLE_HCI_LE_PHY_1M_PREF_MASK | BLE_HCI_LE_PHY_CODED_PREF_MASK)
#else
#define SCAN_VALID_PHY_MASK     (BLE_HCI_LE_PHY_1M_PREF_MASK)
#endif

struct ble_ll_scan_params {
    uint8_t own_addr_type;
    uint8_t scan_filt_policy;
    struct ble_ll_scan_phy scan_phys[BLE_LL_SCAN_PHY_NUMBER];
};

static struct ble_ll_scan_params g_ble_ll_scan_params;

/* The scanning state machine global object */
static struct ble_ll_scan_sm g_ble_ll_scan_sm;

/*
 * Structure used to store advertisers. This is used to limit sending scan
 * requests to the same advertiser and also to filter duplicate events sent
 * to the host.
 */
struct ble_ll_scan_advertisers
{
    uint16_t            sc_adv_flags;
    uint16_t            adi;
    struct ble_dev_addr adv_addr;
};

#define BLE_LL_SC_ADV_F_RANDOM_ADDR     (0x01)
#define BLE_LL_SC_ADV_F_SCAN_RSP_RXD    (0x02)
#define BLE_LL_SC_ADV_F_DIRECT_RPT_SENT (0x04)
#define BLE_LL_SC_ADV_F_ADV_RPT_SENT    (0x08)
#define BLE_LL_SC_ADV_F_SCAN_RSP_SENT   (0x10)

/* Contains list of advertisers that we have heard scan responses from */
static uint8_t g_ble_ll_scan_num_rsp_advs;
struct ble_ll_scan_advertisers
g_ble_ll_scan_rsp_advs[MYNEWT_VAL(BLE_LL_NUM_SCAN_RSP_ADVS)];

/* Duplicates filtering data */
#define BLE_LL_SCAN_ENTRY_TYPE_LEGACY(addr_type) \
    ((addr_type) & 1)
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
#define BLE_LL_SCAN_ENTRY_TYPE_EXT(addr_type, has_aux, is_anon, adi) \
    (((adi >> 8) & 0xF0) | (1 << 3) | (is_anon << 2) | (has_aux << 1) | ((addr_type) & 1))
#endif

#define BLE_LL_SCAN_DUP_F_ADV_REPORT_SENT       (0x01)
#define BLE_LL_SCAN_DUP_F_DIR_ADV_REPORT_SENT   (0x02)
#define BLE_LL_SCAN_DUP_F_SCAN_RSP_SENT         (0x04)

struct ble_ll_scan_dup_entry {
    uint8_t type;       /* entry type, see BLE_LL_SCAN_ENTRY_TYPE_* */
    uint8_t addr[6];
    uint8_t flags;      /* use BLE_LL_SCAN_DUP_F_xxx */
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    uint16_t adi;
#endif
    TAILQ_ENTRY(ble_ll_scan_dup_entry) link;
};

static os_membuf_t g_scan_dup_mem[ OS_MEMPOOL_SIZE(
                                   MYNEWT_VAL(BLE_LL_NUM_SCAN_DUP_ADVS),
                                   sizeof(struct ble_ll_scan_dup_entry)) ];
static struct os_mempool g_scan_dup_pool;
static TAILQ_HEAD(ble_ll_scan_dup_list, ble_ll_scan_dup_entry) g_scan_dup_list;

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
static int
ble_ll_scan_start(struct ble_ll_scan_sm *scansm);

#endif

static inline uint32_t
ble_ll_scan_time_hci_to_ticks(uint16_t value)
{
    return ble_ll_tmr_u2t(value * BLE_HCI_SCAN_ITVL);
}

/* See Vol 6 Part B Section 4.4.3.2. Active scanning backoff */
static void
ble_ll_scan_req_backoff(struct ble_ll_scan_sm *scansm, int success)
{
    BLE_LL_ASSERT(scansm->backoff_count == 0);
    BLE_LL_ASSERT(scansm->scan_rsp_pending == 0);

    if (success) {
        scansm->scan_rsp_cons_fails = 0;
        ++scansm->scan_rsp_cons_ok;
        if (scansm->scan_rsp_cons_ok == 2) {
            scansm->scan_rsp_cons_ok = 0;
            if (scansm->upper_limit > 1) {
                scansm->upper_limit >>= 1;
            }
        }
        STATS_INC(ble_ll_stats, scan_req_txg);
    } else {
        scansm->scan_rsp_cons_ok = 0;
        ++scansm->scan_rsp_cons_fails;
        if (scansm->scan_rsp_cons_fails == 2) {
            scansm->scan_rsp_cons_fails = 0;
            if (scansm->upper_limit < 256) {
                scansm->upper_limit <<= 1;
            }
        }
        STATS_INC(ble_ll_stats, scan_req_txf);
    }

    scansm->backoff_count = ble_ll_rand() & (scansm->upper_limit - 1);
    ++scansm->backoff_count;
    BLE_LL_ASSERT(scansm->backoff_count <= 256);
}

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
static void
ble_ll_scan_refresh_nrpa(struct ble_ll_scan_sm *scansm)
{
    ble_npl_time_t now;

    now = ble_npl_time_get();
    if (LL_TMR_GEQ(now, scansm->scan_nrpa_timer)) {
        /* Generate new NRPA */
        ble_ll_rand_data_get(scansm->scan_nrpa, BLE_DEV_ADDR_LEN);
        scansm->scan_nrpa[5] &= ~0xc0;

        /* We'll use the same timeout as for RPA rotation */
        scansm->scan_nrpa_timer = now + ble_ll_resolv_get_rpa_tmo();
    }
}

uint8_t *
ble_ll_get_scan_nrpa(void)
{
    struct ble_ll_scan_sm *scansm = &g_ble_ll_scan_sm;

    ble_ll_scan_refresh_nrpa(scansm);

    return scansm->scan_nrpa;
}
#endif

uint8_t
ble_ll_scan_get_own_addr_type(void)
{
    return g_ble_ll_scan_sm.own_addr_type;
}

uint8_t
ble_ll_scan_get_filt_policy(void)
{
    return g_ble_ll_scan_sm.scan_filt_policy;
}

uint8_t
ble_ll_scan_get_filt_dups(void)
{
    return g_ble_ll_scan_sm.scan_filt_dups;
}

uint8_t
ble_ll_scan_backoff_kick(void)
{
    struct ble_ll_scan_sm *scansm = &g_ble_ll_scan_sm;

    if (scansm->backoff_count > 0) {
        scansm->backoff_count--;
    }

    return scansm->backoff_count;
}

void
ble_ll_scan_backoff_update(int success)
{
    struct ble_ll_scan_sm *scansm = &g_ble_ll_scan_sm;

    ble_ll_scan_req_backoff(scansm, success);
}

static void
ble_ll_scan_req_pdu_prepare(struct ble_ll_scan_sm *scansm,
                            const uint8_t *adv_addr, uint8_t adv_addr_type,
                            int8_t rpa_index)
{
    uint8_t hdr_byte;
    struct ble_ll_scan_pdu_data *pdu_data;
    uint8_t *scana;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    struct ble_ll_resolv_entry *rl;
    uint8_t rpa[BLE_DEV_ADDR_LEN];
#endif

    pdu_data = &scansm->pdu_data;

    /* Construct first PDU header byte */
    hdr_byte = BLE_ADV_PDU_TYPE_SCAN_REQ;
    if (adv_addr_type) {
        hdr_byte |= BLE_ADV_PDU_HDR_RXADD_RAND;
    }

    /* Determine ScanA */
    if (scansm->own_addr_type & 0x01) {
        hdr_byte |= BLE_ADV_PDU_HDR_TXADD_RAND;
        scana = g_random_addr;
    } else {
        scana = g_dev_addr;
    }

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    if (scansm->own_addr_type & 0x02) {
        if (rpa_index >= 0) {
            rl = &g_ble_ll_resolv_list[rpa_index];
        } else {
            rl = NULL;
        }

        /*
         * If device is on RL and we have local IRK, we use RPA generated using
         * that IRK as ScanA. Otherwise we use NRPA as ScanA to prevent our
         * device from being tracked when doing an active scan (Core 5.1, Vol 6,
         * Part B, section 6.3)
         */
        if (rl && rl->rl_has_local) {
            ble_ll_resolv_get_priv_addr(rl, 1, rpa);
            scana = rpa;
        } else {
            ble_ll_scan_refresh_nrpa(scansm);
            scana = scansm->scan_nrpa;
        }

        hdr_byte |= BLE_ADV_PDU_HDR_TXADD_RAND;
    }
#endif

    /* Save scan request data */
    pdu_data->hdr_byte = hdr_byte;
    memcpy(pdu_data->scana, scana, BLE_DEV_ADDR_LEN);
    memcpy(pdu_data->adva, adv_addr, BLE_DEV_ADDR_LEN);
}

static uint8_t
ble_ll_scan_req_tx_pdu_cb(uint8_t *dptr, void *pducb_arg, uint8_t *hdr_byte)
{
    struct ble_ll_scan_sm *scansm = pducb_arg;
    struct ble_ll_scan_pdu_data *pdu_data = &scansm->pdu_data;

    memcpy(dptr, pdu_data->scana, BLE_DEV_ADDR_LEN);
    memcpy(dptr + BLE_DEV_ADDR_LEN, pdu_data->adva, BLE_DEV_ADDR_LEN);

    *hdr_byte = pdu_data->hdr_byte;

    return BLE_DEV_ADDR_LEN * 2;
}

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
/* if copy_from is provided new report is initialized with that instead of
 * defaults
 */
static struct ble_hci_ev *
ble_ll_scan_get_ext_adv_report(struct ext_adv_report *copy_from)
{
    struct ble_hci_ev_le_subev_ext_adv_rpt *ev;
    struct ext_adv_report *report;
    struct ble_hci_ev *hci_ev;

    hci_ev = ble_transport_alloc_evt(1);
    if (!hci_ev) {
        return NULL;
    }

    hci_ev->opcode = BLE_HCI_EVCODE_LE_META;
    hci_ev->length = sizeof(*ev) + sizeof(*report);
    ev = (void *) hci_ev->data;

    memset(ev, 0, sizeof(*ev));
    ev->subev_code = BLE_HCI_LE_SUBEV_EXT_ADV_RPT;
    /* We support only one report per event now */
    ev->num_reports = 1;

    report = ev->reports;

    if (copy_from) {
        memcpy(report, copy_from, sizeof(*report));
        report->data_len = 0;
    } else {
        memset(report, 0, sizeof(*report));

        report->pri_phy = BLE_PHY_1M;
        /* Init SID with "Not available" which is 0xFF */
        report->sid = 0xFF;
        /* Init TX Power with "Not available" which is 127 */
        report->tx_power = 127;
        /* Init RSSI with "Not available" which is 127 */
        report->rssi = 127;
        /* Init address type with "anonymous" which is 0xFF */
        report->addr_type = 0xFF;
    }

    return hci_ev;
}

void
ble_ll_scan_end_adv_evt(struct ble_ll_aux_data *aux_data)
{
}
#endif

void
ble_ll_scan_halt(void)
{
    struct ble_ll_scan_sm *scansm = &g_ble_ll_scan_sm;

    /* Update backoff if we failed to receive scan response */
    if (scansm->scan_rsp_pending) {
        scansm->scan_rsp_pending = 0;
        ble_ll_scan_req_backoff(scansm, 0);
    }
}

/**
 * Checks to see if we have received a scan response from this advertiser.
 *
 * @param adv_addr Address of advertiser
 * @param txadd TxAdd bit (0: public; random otherwise)
 *
 * @return int 0: have not received a scan response; 1 otherwise.
 */
int
ble_ll_scan_have_rxd_scan_rsp(uint8_t *addr, uint8_t txadd,
                              uint8_t ext_adv, uint16_t adi)
{
    uint8_t num_advs;
    struct ble_ll_scan_advertisers *adv;

    /* Do we have an address match? Must match address type */
    adv = &g_ble_ll_scan_rsp_advs[0];
    num_advs = g_ble_ll_scan_num_rsp_advs;
    while (num_advs) {
        if (!memcmp(&adv->adv_addr, addr, BLE_DEV_ADDR_LEN)) {
            /* Address type must match */
            if (txadd) {
                if (adv->sc_adv_flags & BLE_LL_SC_ADV_F_RANDOM_ADDR) {
                    if (ext_adv) {
                        if (adi == adv->adi) {
                            return 1;
                        }
                        goto next;
                    }
                    return 1;
                }
            } else {
                if ((adv->sc_adv_flags & BLE_LL_SC_ADV_F_RANDOM_ADDR) == 0) {
                    if (ext_adv) {
                        if (adi == adv->adi) {
                            return 1;
                        }
                        goto next;
                    }
                    return 1;
                }
            }
        }
next:
        ++adv;
        --num_advs;
    }

    return 0;
}

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
void
ble_ll_scan_add_scan_rsp_adv(uint8_t *addr, uint8_t txadd,
                             uint8_t ext_adv, uint16_t adi)
{
    uint8_t num_advs;
    struct ble_ll_scan_advertisers *adv;

    /* XXX: for now, if we dont have room, just leave */
    num_advs = g_ble_ll_scan_num_rsp_advs;
    if (num_advs == MYNEWT_VAL(BLE_LL_NUM_SCAN_RSP_ADVS)) {
        return;
    }

    /* Check if address is already on the list */
    if (ble_ll_scan_have_rxd_scan_rsp(addr, txadd, ext_adv, adi)) {
        return;
    }

    /* Add the advertiser to the array */
    adv = &g_ble_ll_scan_rsp_advs[num_advs];
    memcpy(&adv->adv_addr, addr, BLE_DEV_ADDR_LEN);
    adv->sc_adv_flags = BLE_LL_SC_ADV_F_SCAN_RSP_RXD;
    if (txadd) {
        adv->sc_adv_flags |= BLE_LL_SC_ADV_F_RANDOM_ADDR;
    }
    adv->adi = adi;
    ++g_ble_ll_scan_num_rsp_advs;

    return;
}

static int
ble_ll_hci_send_legacy_ext_adv_report(uint8_t evtype,
                                      const uint8_t *addr, uint8_t addr_type,
                                      int8_t rssi,
                                      uint8_t adv_data_len,
                                      struct os_mbuf *adv_data,
                                      const uint8_t *inita, uint8_t inita_type)
{
    struct ble_hci_ev_le_subev_ext_adv_rpt *ev;
    struct ext_adv_report *report;
    struct ble_hci_ev *hci_ev;

    if (!ble_ll_hci_is_le_event_enabled(BLE_HCI_LE_SUBEV_EXT_ADV_RPT)) {
        return -1;
    }

    /* Drop packet if adv data doesn't fit  */
    if ((sizeof(*ev) + sizeof(ev->reports[0]) + adv_data_len) > BLE_HCI_MAX_DATA_LEN) {
        STATS_INC(ble_ll_stats, adv_evt_dropped);
        return -1;
    }

    hci_ev = ble_ll_scan_get_ext_adv_report(NULL);
    if (!hci_ev) {
        return -1;
    }

    ev = (void *) hci_ev->data;
    report = ev->reports;

    switch (evtype) {
    case BLE_HCI_ADV_RPT_EVTYPE_ADV_IND:
        report->evt_type = BLE_HCI_LEGACY_ADV_EVTYPE_ADV_IND;
        break;
    case BLE_HCI_ADV_RPT_EVTYPE_DIR_IND:
        report->evt_type = BLE_HCI_LEGACY_ADV_EVTYPE_ADV_DIRECT_IND;
        break;
    case BLE_HCI_ADV_RPT_EVTYPE_NONCONN_IND:
        report->evt_type = BLE_HCI_LEGACY_ADV_EVTYPE_ADV_NONCON_IND;
        break;
    case BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP:
        report->evt_type = BLE_HCI_LEGACY_ADV_EVTYPE_SCAN_RSP_ADV_IND;
         break;
    case BLE_HCI_ADV_RPT_EVTYPE_SCAN_IND:
        report->evt_type = BLE_HCI_LEGACY_ADV_EVTYPE_ADV_SCAN_IND;
        break;
    default:
        BLE_LL_ASSERT(0);
        ble_transport_free(hci_ev);
        return -1;
    }

    report->addr_type = addr_type;
    memcpy(report->addr, addr, BLE_DEV_ADDR_LEN);
    report->pri_phy = BLE_PHY_1M;
    report->sid = 0xFF;
    report->tx_power = 127;
    report->rssi = rssi;

    if (inita) {
        report->dir_addr_type = inita_type;
        memcpy(report->dir_addr, inita, BLE_DEV_ADDR_LEN);
    }

    if (adv_data_len) {
        hci_ev->length += adv_data_len;
        report->data_len = adv_data_len;
        os_mbuf_copydata(adv_data, 0, adv_data_len, report->data);
    }

    return ble_ll_hci_event_send(hci_ev);
}
#endif

static int
ble_ll_hci_send_adv_report(uint8_t evtype,
                           const uint8_t *addr, uint8_t addr_type, int8_t rssi,
                           uint8_t adv_data_len, struct os_mbuf *adv_data)
{
    struct ble_hci_ev_le_subev_adv_rpt *ev;
    struct ble_hci_ev *hci_ev;
    int8_t *ev_rssi;

    if (!ble_ll_hci_is_le_event_enabled(BLE_HCI_LE_SUBEV_ADV_RPT)) {
        return -1;
    }

    /* Drop packet if adv data doesn't fit, note extra 1 is for RSSI   */
    if ((sizeof(*ev) + sizeof(ev->reports[0]) + adv_data_len + 1) > BLE_HCI_MAX_DATA_LEN) {
        STATS_INC(ble_ll_stats, adv_evt_dropped);
        return -1;
    }

    hci_ev = ble_transport_alloc_evt(1);
    if (!hci_ev) {
        return -1;
    }

    hci_ev->opcode = BLE_HCI_EVCODE_LE_META;
    hci_ev->length = sizeof(*ev) + sizeof(ev->reports[0]) + adv_data_len + 1;
    ev = (void *) hci_ev->data;

    ev->subev_code = BLE_HCI_LE_SUBEV_ADV_RPT;
    ev->num_reports = 1;

    ev->reports[0].type = evtype;
    ev->reports[0].addr_type = addr_type;
    memcpy(ev->reports[0].addr, addr, BLE_DEV_ADDR_LEN);
    ev->reports[0].data_len = adv_data_len;
    os_mbuf_copydata(adv_data, 0, adv_data_len, ev->reports[0].data);

    /* RSSI is after adv data... */
    ev_rssi = (int8_t *) (hci_ev->data + sizeof(*ev) + sizeof(ev->reports[0]) + adv_data_len);
    *ev_rssi = rssi;

    return ble_ll_hci_event_send(hci_ev);
}

static int
ble_ll_hci_send_dir_adv_report(const uint8_t *addr, uint8_t addr_type,
                               const uint8_t *inita, uint8_t inita_type,
                               int8_t rssi)
{
    struct ble_hci_ev_le_subev_direct_adv_rpt *ev;
    struct ble_hci_ev *hci_ev;

    if (!ble_ll_hci_is_le_event_enabled(BLE_HCI_LE_SUBEV_DIRECT_ADV_RPT)) {
        return -1;
    }

    hci_ev = ble_transport_alloc_evt(1);
    if (!hci_ev) {
        return -1;
    }

    hci_ev->opcode = BLE_HCI_EVCODE_LE_META;
    hci_ev->length = sizeof(*ev) + sizeof(*(ev->reports));
    ev = (void *) hci_ev->data;

    ev->subev_code = BLE_HCI_LE_SUBEV_DIRECT_ADV_RPT;
    ev->num_reports = 1;

    ev->reports[0].type = BLE_HCI_ADV_RPT_EVTYPE_DIR_IND;
    ev->reports[0].addr_type = addr_type;
    memcpy(ev->reports[0].addr, addr, BLE_DEV_ADDR_LEN);
    ev->reports[0].dir_addr_type = inita_type;
    memcpy(ev->reports[0].dir_addr, inita, BLE_DEV_ADDR_LEN);
    ev->reports[0].rssi = rssi;

    return ble_ll_hci_event_send(hci_ev);
}

static int
ble_ll_scan_dup_update_legacy(uint8_t addr_type, const uint8_t *addr,
                              uint8_t subev, uint8_t evtype)
{
    struct ble_ll_scan_dup_entry *e;
    uint8_t type;

    type = BLE_LL_SCAN_ENTRY_TYPE_LEGACY(addr_type);

    /*
     * We assume ble_ll_scan_dup_check() was called before which either matched
     * some entry or allocated new one and placed in on the top of queue.
     */

    e = TAILQ_FIRST(&g_scan_dup_list);
    BLE_LL_ASSERT(e && e->type == type && !memcmp(e->addr, addr, 6));

    if (subev == BLE_HCI_LE_SUBEV_DIRECT_ADV_RPT) {
        e->flags |= BLE_LL_SCAN_DUP_F_DIR_ADV_REPORT_SENT;
    } else {
        if (evtype == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) {
            e->flags |= BLE_LL_SCAN_DUP_F_SCAN_RSP_SENT;
        } else {
            e->flags |= BLE_LL_SCAN_DUP_F_ADV_REPORT_SENT;
        }
    }

    return 0;
}

/**
 * Send an advertising report to the host.
 *
 * NOTE: while we are allowed to send multiple devices in one report, we
 * will just send for one for now.
 *
 * @param pdu_type
 * @param txadd
 * @param rxbuf
 * @param hdr
 * @param scansm
 */
static void
ble_ll_scan_send_adv_report(uint8_t pdu_type,
                            const uint8_t *adva, uint8_t adva_type,
                            const uint8_t *inita, uint8_t inita_type,
                            struct os_mbuf *om,
                            struct ble_mbuf_hdr *hdr,
                            struct ble_ll_scan_sm *scansm)
{
    uint8_t subev = BLE_HCI_LE_SUBEV_ADV_RPT;
    uint8_t adv_data_len;
    uint8_t evtype;
    int rc;

    if (pdu_type == BLE_ADV_PDU_TYPE_ADV_DIRECT_IND) {
        if (ble_ll_is_rpa(inita, inita_type)) {
            /* For resolvable we send separate subevent */
            subev = BLE_HCI_LE_SUBEV_DIRECT_ADV_RPT;
        }

        evtype = BLE_HCI_ADV_RPT_EVTYPE_DIR_IND;
        adv_data_len = 0;
    } else {
        if (pdu_type == BLE_ADV_PDU_TYPE_ADV_IND) {
            evtype = BLE_HCI_ADV_RPT_EVTYPE_ADV_IND;
        } else if (pdu_type == BLE_ADV_PDU_TYPE_ADV_SCAN_IND) {
            evtype = BLE_HCI_ADV_RPT_EVTYPE_SCAN_IND;
        } else if (pdu_type == BLE_ADV_PDU_TYPE_ADV_NONCONN_IND) {
            evtype = BLE_HCI_ADV_RPT_EVTYPE_NONCONN_IND;
        } else {
            evtype = BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP;
        }
        adv_data_len = om->om_data[1] - BLE_DEV_ADDR_LEN;
        os_mbuf_adj(om, BLE_LL_PDU_HDR_LEN + BLE_DEV_ADDR_LEN);
    }

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    /* If RPA has been used, make sure we use correct address types
     * in the advertising report.
     */
    if (BLE_MBUF_HDR_RESOLVED(hdr)) {
        adva_type += 2;
    }
    if (BLE_MBUF_HDR_TARGETA_RESOLVED(hdr)) {
        inita_type += 2;
    }
#endif

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    if (scansm->ext_scanning) {
        rc = ble_ll_hci_send_legacy_ext_adv_report(evtype,
                                                   adva, adva_type,
                                                   hdr->rxinfo.rssi,
                                                   adv_data_len, om,
                                                   inita, inita_type);
goto done;
}
#endif

    if (subev == BLE_HCI_LE_SUBEV_DIRECT_ADV_RPT) {
        rc = ble_ll_hci_send_dir_adv_report(adva, adva_type, inita, inita_type,
                                            hdr->rxinfo.rssi);
        goto done;
    }

    rc = ble_ll_hci_send_adv_report(evtype, adva, adva_type, hdr->rxinfo.rssi,
                                    adv_data_len, om);
done:
    if (!rc && scansm->scan_filt_dups) {
        ble_ll_scan_dup_update_legacy(adva_type, adva, subev, evtype);
    }
}

/**
 * Called to enable the receiver for scanning.
 *
 * Context: Link Layer task
 *
 * @param sch
 *
 * @return int
 */
static int
ble_ll_scan_start(struct ble_ll_scan_sm *scansm)
{
    int rc;
    struct ble_ll_scan_phy *scanp = scansm->scanp;
    uint8_t chan;
#if (BLE_LL_BT5_PHY_SUPPORTED == 1)
    uint8_t phy_mode;
    int phy;
#endif

    BLE_LL_ASSERT(scansm->scan_rsp_pending == 0);

    /* Set channel */
    chan = scanp->scan_chan;
    rc = ble_phy_setchan(chan, BLE_ACCESS_ADDR_ADV, BLE_LL_CRCINIT_ADV);
    BLE_LL_ASSERT(rc == 0);

    /*
     * Set transmit end callback to NULL in case we transmit a scan request.
     * There is a callback for the connect request.
     */
    ble_phy_set_txend_cb(NULL, NULL);

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
    ble_phy_encrypt_disable();
#endif

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    if (ble_ll_resolv_enabled()) {
        ble_phy_resolv_list_enable();
    } else {
        ble_phy_resolv_list_disable();
    }
#endif

#if (BLE_LL_BT5_PHY_SUPPORTED == 1)
    phy = scanp->phy;
    phy_mode = ble_ll_phy_to_phy_mode(phy, BLE_HCI_LE_PHY_CODED_ANY);
    ble_phy_mode_set(phy_mode, phy_mode);
#endif

    rc = ble_phy_rx_set_start_time(ble_ll_tmr_get() +
                                   g_ble_ll_sched_offset_ticks, 0);
    if (!rc || rc == BLE_PHY_ERR_RX_LATE) {
        /* If we are late here, it is still OK because we keep scanning.
         * Clear error
         */
        rc = 0;

        /* Enable/disable whitelisting */
        if (scansm->scan_filt_policy & 1) {
            ble_ll_whitelist_enable();
        } else {
            ble_ll_whitelist_disable();
        }

        ble_ll_state_set(BLE_LL_STATE_SCANNING);
    }

    return rc;
}

static uint8_t
ble_ll_scan_get_next_adv_prim_chan(uint8_t chan)
{
    ++chan;
    if (chan == BLE_PHY_NUM_CHANS) {
        chan = BLE_PHY_ADV_CHAN_START;
    }

    return chan;
}

static uint32_t
ble_ll_scan_move_window_to(struct ble_ll_scan_phy *scanp, uint32_t time)
{
    uint32_t end_time;

    /*
     * Move window until given tick is before or inside window and move to next
     * channel for each skipped interval.
     */

    end_time = scanp->timing.start_time + scanp->timing.window;
    while (LL_TMR_GEQ(time, end_time)) {
        scanp->timing.start_time += scanp->timing.interval;
        scanp->scan_chan = ble_ll_scan_get_next_adv_prim_chan(scanp->scan_chan);
        end_time = scanp->timing.start_time + scanp->timing.window;
    }

    return scanp->timing.start_time;
}

static bool
ble_ll_scan_is_inside_window(struct ble_ll_scan_phy *scanp, uint32_t time)
{
    uint32_t start_time;

    /* Make sure we are checking against closest window */
    start_time = ble_ll_scan_move_window_to(scanp, time);

    if (scanp->timing.window == scanp->timing.interval) {
        /* always inside window in continuous scan */
        return true;
    }

    return LL_TMR_GEQ(time, start_time) &&
           LL_TMR_LT(time, start_time + scanp->timing.window);
}

/**
 * Stop the scanning state machine
 */
void
ble_ll_scan_sm_stop(int chk_disable)
{
    os_sr_t sr;
    uint8_t lls;
    struct ble_ll_scan_sm *scansm;

    /* Stop the scanning timer  */
    scansm = &g_ble_ll_scan_sm;
    ble_ll_tmr_stop(&scansm->scan_timer);

    /* Only set state if we are currently in a scan window */
    if (chk_disable) {
        OS_ENTER_CRITICAL(sr);
        lls = ble_ll_state_get();

        if (lls == BLE_LL_STATE_SCANNING) {
            /* Disable phy */
            ble_phy_disable();

            /* Set LL state to standby */
            ble_ll_state_set(BLE_LL_STATE_STANDBY);
        }
        OS_EXIT_CRITICAL(sr);
    }

    OS_ENTER_CRITICAL(sr);

#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
    scansm->connsm = NULL;
#endif

    /* Disable scanning state machine */
    scansm->scan_enabled = 0;
    scansm->restart_timer_needed = 0;

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    if (scansm->ext_scanning) {
        ble_ll_sched_rmv_elem_type(BLE_LL_SCHED_TYPE_SCAN_AUX, ble_ll_scan_aux_sched_remove);
        scansm->ext_scanning = 0;
    }
#endif

    /* Update backoff if we failed to receive scan response */
    if (scansm->scan_rsp_pending) {
        scansm->scan_rsp_pending = 0;
        ble_ll_scan_req_backoff(scansm, 0);
    }
    OS_EXIT_CRITICAL(sr);

    /* Count # of times stopped */
    STATS_INC(ble_ll_stats, scan_stops);

    /* No need for RF anymore */
    OS_ENTER_CRITICAL(sr);
    ble_ll_rfmgmt_scan_changed(false, 0);
    ble_ll_rfmgmt_release();
    OS_EXIT_CRITICAL(sr);
}

static int
ble_ll_scan_sm_start(struct ble_ll_scan_sm *scansm)
{
    struct ble_ll_scan_phy *scanp;
    struct ble_ll_scan_phy *scanp_next;

    if (!ble_ll_is_valid_own_addr_type(scansm->own_addr_type, g_random_addr)) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    BLE_LL_ASSERT(scansm->scanp);
    scanp = scansm->scanp;
    scanp_next = scansm->scanp_next;

    /* Count # of times started */
    STATS_INC(ble_ll_stats, scan_starts);

    /* Set flag telling us that scanning is enabled */
    scansm->scan_enabled = 1;

    /* Set first advertising channel */
    scanp->scan_chan = BLE_PHY_ADV_CHAN_START;
    if (scanp_next) {
        scanp_next->scan_chan = BLE_PHY_ADV_CHAN_START;
    }

    /* Reset scan request backoff parameters to default */
    scansm->upper_limit = 1;
    scansm->backoff_count = 1;
    scansm->scan_rsp_pending = 0;

    /* Forget filtered advertisers from previous scan. */
    g_ble_ll_scan_num_rsp_advs = 0;

    os_mempool_clear(&g_scan_dup_pool);
    TAILQ_INIT(&g_scan_dup_list);

    /*
     * First scan window can start when RF is enabled. Add 1 tick since we are
     * most likely not aligned with ticks so RF may be effectively enabled 1
     * tick later.
     */
    scanp->timing.start_time = ble_ll_rfmgmt_enable_now();
    ble_ll_rfmgmt_scan_changed(true, scanp->timing.start_time);

    if (scanp_next) {
        /* Schedule start time right after first phy */
        scanp_next->timing.start_time = scanp->timing.start_time +
                                        scanp->timing.window;
    }

    /* Start scan at 1st window */
    ble_ll_tmr_start(&scansm->scan_timer, scanp->timing.start_time);

    return BLE_ERR_SUCCESS;
}

static void
ble_ll_scan_interrupted_event_cb(struct ble_npl_event *ev)
{
    struct ble_ll_scan_sm *scansm = &g_ble_ll_scan_sm;

    if (!scansm->scan_enabled) {
        return;
    }

    /*
    * If we timed out waiting for a response, the scan response pending
    * flag should be set. Deal with scan backoff. Put device back into rx.
    */

    if (scansm->scan_rsp_pending) {
        scansm->scan_rsp_pending = 0;
        ble_ll_scan_req_backoff(scansm, 0);
    }

    ble_ll_scan_chk_resume();
}

/**
 * Called to process the scanning OS event which was posted to the LL task
 *
 * Context: Link Layer task.
 *
 * @param arg
 */
static void
ble_ll_scan_event_proc(struct ble_npl_event *ev)
{
    struct ble_ll_scan_sm *scansm;
    os_sr_t sr;
    bool start_scan;
    bool inside_window;
    struct ble_ll_scan_phy *scanp;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    bool inside_window_next;
    struct ble_ll_scan_phy *scanp_next;
#endif
    uint32_t next_proc_time;
    uint32_t now;
    /*
     * Get the scanning state machine. If not enabled (this is possible), just
     * leave and do nothing (just make sure timer is stopped).
     */
    scansm = (struct ble_ll_scan_sm *)ble_npl_event_get_arg(ev);
    scanp = scansm->scanp;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    scanp_next = scansm->scanp_next;
#endif

    OS_ENTER_CRITICAL(sr);
    if (!scansm->scan_enabled) {
        ble_ll_tmr_stop(&scansm->scan_timer);
        ble_ll_rfmgmt_scan_changed(false, 0);
        ble_ll_rfmgmt_release();
        OS_EXIT_CRITICAL(sr);
        return;
    }

    if (scansm->scan_rsp_pending) {
        /* Aux scan in progress. Wait */
        STATS_INC(ble_ll_stats, scan_timer_stopped);
        scansm->restart_timer_needed = 1;
        OS_EXIT_CRITICAL(sr);
        return;
    }

    now = ble_ll_tmr_get();

    inside_window = ble_ll_scan_is_inside_window(scanp, now);

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    /* Update also next PHY if configured */
    if (scanp_next) {
        inside_window_next = ble_ll_scan_is_inside_window(scanp_next, now);

        /*
         * Switch PHY if current PHY is outside window and next PHY is either
         * inside window or has next window earlier than current PHY.
         */
        if (!inside_window &&
            ((inside_window_next || LL_TMR_LEQ(scanp_next->timing.start_time,
                                               scanp->timing.start_time)))) {
            scansm->scanp = scanp_next;
            scansm->scanp_next = scanp;
            scanp = scansm->scanp;
            scanp_next = scansm->scanp_next;
            inside_window = inside_window_next;
        }
    }
#endif

    /*
     * At this point scanp and scanp_next point to current or closest scan
     * window on both PHYs (scanp is the closer one). Make sure RF is enabled
     * on time.
     */
    ble_ll_rfmgmt_scan_changed(true, scanp->timing.start_time);

    /*
     * If we are inside window, next scan proc should happen at the end of
     * current window to either disable scan or switch to next PHY.
     * If we are outside window, next scan proc should happen at the time of
     * closest scan window.
     */
    if (inside_window) {
        next_proc_time = scanp->timing.start_time + scanp->timing.window;
    } else {
        next_proc_time = scanp->timing.start_time;
    }

    /*
     * If we are not in the standby state it means that the scheduled
     * scanning event was overlapped in the schedule. In this case all we do
     * is post the scan schedule end event.
     */
    start_scan = inside_window;
    switch (ble_ll_state_get()) {
#if MYNEWT_VAL(BLE_LL_ROLE_BROADCASTER)
    case BLE_LL_STATE_ADV:
        start_scan = false;
        break;
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL) || MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
    case BLE_LL_STATE_CONNECTION:
        start_scan = false;
        break;
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
    case BLE_LL_STATE_SYNC:
        start_scan = false;
        break;
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    case BLE_LL_STATE_SCAN_AUX:
        start_scan = false;
        break;
#endif
    case BLE_LL_STATE_SCANNING:
        /* Must disable PHY since we will move to a new channel */
        ble_phy_disable();
        if (!inside_window) {
            ble_ll_state_set(BLE_LL_STATE_STANDBY);
        }
        break;
    case BLE_LL_STATE_STANDBY:
        break;
    default:
        BLE_LL_ASSERT(0);
        break;
    }

    if (start_scan) {
        ble_ll_scan_start(scansm);
    } else {
        ble_ll_rfmgmt_release();
    }

    OS_EXIT_CRITICAL(sr);
    ble_ll_tmr_start(&scansm->scan_timer, next_proc_time);
}

/**
 * ble ll scan rx pdu start
 *
 * Called when a PDU reception has started and the Link Layer is in the
 * scanning state.
 *
 * Context: Interrupt
 *
 * @param pdu_type
 * @param rxflags
 *
 * @return int
 *  0: we will not attempt to reply to this frame
 *  1: we may send a response to this frame.
 */
int
ble_ll_scan_rx_isr_start(uint8_t pdu_type, uint16_t *rxflags)
{
    int rc;
    struct ble_ll_scan_sm *scansm;
    struct ble_ll_scan_phy *scanp;

    rc = 0;
    scansm = &g_ble_ll_scan_sm;
    scanp = scansm->scanp;

    switch (scanp->scan_type) {
    case BLE_SCAN_TYPE_ACTIVE:
        /* If adv ind or scan ind, we may send scan request */
        if ((pdu_type == BLE_ADV_PDU_TYPE_ADV_IND) ||
            (pdu_type == BLE_ADV_PDU_TYPE_ADV_SCAN_IND)) {
            rc = 1;
        }

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
        if ((pdu_type == BLE_ADV_PDU_TYPE_ADV_EXT_IND && scansm->ext_scanning)) {
            *rxflags |= BLE_MBUF_HDR_F_EXT_ADV;
            rc = 1;
        }
#endif

        /*
         * If this is the first PDU after we sent the scan response (as
         * denoted by the scan rsp pending flag), we set a bit in the ble
         * header so the link layer can check to see if the scan request
         * was successful. We do it this way to let the Link Layer do the
         * work for successful scan requests. If failed, we do the work here.
         */
        if (scansm->scan_rsp_pending) {
            scansm->scan_rsp_pending = 0;

            if (pdu_type == BLE_ADV_PDU_TYPE_SCAN_RSP) {
                *rxflags |= BLE_MBUF_HDR_F_SCAN_RSP_RXD;
            } else if (pdu_type == BLE_ADV_PDU_TYPE_AUX_SCAN_RSP) {
                *rxflags |= BLE_MBUF_HDR_F_SCAN_RSP_RXD;
            } else {
                ble_ll_scan_req_backoff(scansm, 0);
            }
        }
        break;
    case BLE_SCAN_TYPE_PASSIVE:
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
        if ((pdu_type == BLE_ADV_PDU_TYPE_ADV_EXT_IND && scansm->ext_scanning)) {
            *rxflags |= BLE_MBUF_HDR_F_EXT_ADV;
        }
        break;
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
    case BLE_SCAN_TYPE_INITIATE:
        if ((pdu_type == BLE_ADV_PDU_TYPE_ADV_IND) ||
            (pdu_type == BLE_ADV_PDU_TYPE_ADV_DIRECT_IND)) {
            rc = 1;
        }

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
        if ((pdu_type == BLE_ADV_PDU_TYPE_ADV_EXT_IND && scansm->ext_scanning)) {
            *rxflags |= BLE_MBUF_HDR_F_EXT_ADV;
            rc = 1;
        }
#endif
        break;
#endif
    default:
        break;
    }

    return rc;
}

static void
ble_ll_scan_get_addr_data_from_legacy(uint8_t pdu_type, uint8_t *rxbuf,
                                     struct ble_ll_scan_addr_data *addrd)
{
    BLE_LL_ASSERT(pdu_type < BLE_ADV_PDU_TYPE_ADV_EXT_IND);

    addrd->adva = rxbuf + BLE_LL_PDU_HDR_LEN;
    addrd->adva_type = ble_ll_get_addr_type(rxbuf[0] & BLE_ADV_PDU_HDR_TXADD_MASK);

    if (pdu_type == BLE_ADV_PDU_TYPE_ADV_DIRECT_IND) {
        addrd->targeta = rxbuf + BLE_LL_PDU_HDR_LEN + BLE_DEV_ADDR_LEN;
        addrd->targeta_type = ble_ll_get_addr_type(rxbuf[0] & BLE_ADV_PDU_HDR_RXADD_MASK);
    } else {
        addrd->targeta = NULL;
        addrd->targeta_type = 0;
    }
}

int
ble_ll_scan_rx_filter(uint8_t own_addr_type, uint8_t scan_filt_policy,
                      struct ble_ll_scan_addr_data *addrd, uint8_t *scan_ok)
{
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    struct ble_ll_resolv_entry *rl = NULL;
#endif
    bool scan_req_allowed = true;
    bool resolved;

    /* Note: caller is expected to fill adva, targeta and rpa_index in addrd */

    /* Use AdvA as initial advertiser address, we may change it if resolved */
    addrd->adv_addr = addrd->adva;
    addrd->adv_addr_type = addrd->adva_type;

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    addrd->adva_resolved = 0;
    addrd->targeta_resolved = 0;

    BLE_LL_ASSERT((addrd->rpa_index < 0) ||
                  (ble_ll_addr_subtype(addrd->adva, addrd->adva_type) ==
                   BLE_LL_ADDR_SUBTYPE_RPA));

    switch (ble_ll_addr_subtype(addrd->adva, addrd->adva_type)) {
    case BLE_LL_ADDR_SUBTYPE_RPA:
        if (addrd->rpa_index < 0) {
            break;
        }

        addrd->adva_resolved = 1;

        /* Use resolved identity address as advertiser address */
        rl = &g_ble_ll_resolv_list[addrd->rpa_index];
        addrd->adv_addr = rl->rl_identity_addr;
        addrd->adv_addr_type = rl->rl_addr_type;
        break;
    case BLE_LL_ADDR_SUBTYPE_IDENTITY:
        /* If AdvA is an identity address, we need to check if that device was
         * added to RL in order to use proper privacy mode.
         */
        rl = ble_ll_resolv_list_find(addrd->adva, addrd->adva_type);
        if (!rl) {
            break;
        }

        /* Ignore device if using network privacy mode and it has IRK */
        if ((rl->rl_priv_mode == BLE_HCI_PRIVACY_NETWORK) && rl->rl_has_peer) {
            return -1;
        }

        addrd->rpa_index = ble_ll_resolv_get_idx(rl);
        break;
    default:
        /* NRPA goes through filtering policy directly */
        break;
    }

    if (addrd->targeta) {
        switch (ble_ll_addr_subtype(addrd->targeta, addrd->targeta_type)) {
        case BLE_LL_ADDR_SUBTYPE_RPA:
            /* Check if TargetA can be resolved using the same RL entry as AdvA */
            if (rl && ble_ll_resolv_rpa(addrd->targeta, rl->rl_local_irk)) {
                addrd->targeta_resolved = 1;
                break;
            }

            /* Check if scan filter policy allows unresolved RPAs to be processed */
            if (!(scan_filt_policy & 0x02)) {
                return -2;
            }

            /* Do not send scan request even if scan policy allows unresolved
             * RPAs - we do not know if this one if directed to us.
             */
            scan_req_allowed = false;
            break;
        case BLE_LL_ADDR_SUBTYPE_IDENTITY:
            /* We shall ignore identity in TargetA if we are using RPA */
            if ((own_addr_type & 0x02) && rl && rl->rl_has_local) {
                return -1;
            }

            /* Ignore if not directed to us */
            if (!ble_ll_is_our_devaddr(addrd->targeta, addrd->targeta_type)) {
                return -1;
            }
            break;
        default:
            /* NRPA goes through filtering policy directly */
            break;
        }
    }

    resolved = addrd->adva_resolved;
#else
    /* Ignore if not directed to us */
    if (addrd->targeta &&
        !ble_ll_is_our_devaddr(addrd->targeta, addrd->targeta_type)) {
        return -1;
    }

    resolved = false;
#endif

    if (scan_filt_policy & 0x01) {
        /* Check on WL if required by scan filter policy */
        if (!ble_ll_whitelist_match(addrd->adv_addr, addrd->adv_addr_type,
                                    resolved)) {
            return -2;
        }
    }

    if (scan_ok) {
        *scan_ok = scan_req_allowed;
    }

    return 0;
}

#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
int
ble_ll_scan_rx_check_init(struct ble_ll_scan_addr_data *addrd)
{
    struct ble_ll_scan_sm *scansm;
    struct ble_ll_conn_sm *connsm;

    scansm = &g_ble_ll_scan_sm;
    connsm = scansm->connsm;
    BLE_LL_ASSERT(connsm);

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    if ((connsm->peer_addr_type > BLE_ADDR_RANDOM) && !addrd->adva_resolved) {
        return -1;
    }
#endif
    if ((addrd->adv_addr_type != (connsm->peer_addr_type & 0x01)) ||
        memcmp(addrd->adv_addr, connsm->peer_addr, 6) != 0) {
        return -1;
    }

    return 0;
}
#endif

static int
ble_ll_scan_rx_isr_end_on_adv(uint8_t pdu_type, uint8_t *rxbuf,
                              struct ble_mbuf_hdr *hdr,
                              struct ble_ll_scan_addr_data *addrd)
{
    struct ble_ll_scan_sm *scansm = &g_ble_ll_scan_sm;
    struct ble_ll_scan_phy *scanp = scansm->scanp;
    struct ble_mbuf_hdr_rxinfo *rxinfo = &hdr->rxinfo;
    uint8_t scan_ok;
    int rc;

    ble_ll_scan_get_addr_data_from_legacy(pdu_type, rxbuf, addrd);

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    addrd->rpa_index = ble_hw_resolv_list_match();
#endif

    rc = ble_ll_scan_rx_filter(scansm->own_addr_type,
                               scansm->scan_filt_policy, addrd, &scan_ok);
    if (rc < 0) {
        return 0;
    }

#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
    if ((scanp->scan_type == BLE_SCAN_TYPE_INITIATE) &&
        !(scansm->scan_filt_policy & 0x01)) {
        rc = ble_ll_scan_rx_check_init(addrd);
        if (rc < 0) {
            return 0;
        }
    }
#endif

    rxinfo->flags |= BLE_MBUF_HDR_F_DEVMATCH;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    rxinfo->rpa_index = addrd->rpa_index;
    if (addrd->adva_resolved) {
        rxinfo->flags |= BLE_MBUF_HDR_F_RESOLVED;
    }
    if (addrd->targeta_resolved) {
        rxinfo->flags |= BLE_MBUF_HDR_F_TARGETA_RESOLVED;
    }
#endif

    if (!scan_ok) {
        /* Scan request forbidden by filter policy */
        return 0;
    }

    /* Allow responding to all PDUs when initiating since unwanted PDUs were
     * already filtered out in isr_start.
     */
    if ((scanp->scan_type == BLE_SCAN_TYPE_ACTIVE) &&
            ((pdu_type == BLE_ADV_PDU_TYPE_ADV_IND) ||
             (pdu_type == BLE_ADV_PDU_TYPE_ADV_SCAN_IND))) {
        return 1;
    }

#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
    if (scanp->scan_type == BLE_SCAN_TYPE_INITIATE) {
        return 1;
    }
#endif

    return 0;
}

static int
ble_ll_scan_rx_isr_end_on_scan_rsp(uint8_t pdu_type, uint8_t *rxbuf,
                                   struct ble_mbuf_hdr *hdr,
                                   struct ble_ll_scan_addr_data *addrd)
{
    struct ble_ll_scan_sm *scansm = &g_ble_ll_scan_sm;
    struct ble_mbuf_hdr_rxinfo *rxinfo = &hdr->rxinfo;
    uint8_t sreq_adva_type;
    uint8_t *sreq_adva;

    ble_ll_scan_get_addr_data_from_legacy(pdu_type, rxbuf, addrd);

    if (!BLE_MBUF_HDR_SCAN_RSP_RXD(hdr)) {
        /*
         * We were not expecting scan response so just ignore and do not
         * update backoff.
         */
        return -1;
    }

    sreq_adva_type = !!(scansm->pdu_data.hdr_byte & BLE_ADV_PDU_HDR_RXADD_MASK);
    sreq_adva = scansm->pdu_data.adva;

    /*
     * Ignore scan response if AdvA does not match AdvA in request and also
     * update backoff as if there was no scan response.
     */
    if ((addrd->adva_type != sreq_adva_type) ||
        memcmp(addrd->adva, sreq_adva, BLE_DEV_ADDR_LEN)) {
        ble_ll_scan_req_backoff(scansm, 0);
        return -1;
    }

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    /*
     * We are not pushing this one through filters so need to update
     * rpa_index here as otherwise pkt_in won't be able to determine
     * advertiser address properly.
     */
    rxinfo->rpa_index = ble_hw_resolv_list_match();
    if (rxinfo->rpa_index >= 0) {
        rxinfo->flags |= BLE_MBUF_HDR_F_RESOLVED;
    }
#endif

    rxinfo->flags |= BLE_MBUF_HDR_F_DEVMATCH;

    return 0;
}

static bool
ble_ll_scan_send_scan_req(uint8_t pdu_type, uint8_t *rxbuf,
                          struct ble_mbuf_hdr *hdr,
                          struct ble_ll_scan_addr_data *addrd)
{
    struct ble_ll_scan_sm *scansm = &g_ble_ll_scan_sm;
    struct ble_mbuf_hdr_rxinfo *rxinfo = &hdr->rxinfo;
    bool is_ext_adv = false;
    int8_t rpa_index;
    uint16_t adi = 0;
    int rc;

    /* Check if we already scanned this device successfully */
    if (ble_ll_scan_have_rxd_scan_rsp(addrd->adv_addr, addrd->adv_addr_type,
                                      is_ext_adv, adi)) {
        return false;
    }

    /* Better not be a scan response pending */
    BLE_LL_ASSERT(scansm->scan_rsp_pending == 0);

    /* We want to send a request. See if backoff allows us */
    if (scansm->backoff_count > 0) {
        if (--scansm->backoff_count != 0) {
            return false;
        }
    }

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    rpa_index = addrd->rpa_index;
#else
    rpa_index = -1;
#endif

    /* Use original AdvA in scan request (Core 5.1, Vol 6, Part B, section 6.3) */
    ble_ll_scan_req_pdu_prepare(scansm, addrd->adva, addrd->adva_type,
                                rpa_index);

    rc = ble_phy_tx(ble_ll_scan_req_tx_pdu_cb, scansm, BLE_PHY_TRANSITION_TX_RX);
    if (rc) {
        return false;
    }

    scansm->scan_rsp_pending = 1;
    rxinfo->flags |= BLE_MBUF_HDR_F_SCAN_REQ_TXD;

    return true;
}

/**
 * Called when a receive PDU has ended.
 *
 * Context: Interrupt
 *
 * @param rxpdu
 *
 * @return int
 *       < 0: Disable the phy after reception.
 *      == 0: Success. Do not disable the PHY.
 *       > 0: Do not disable PHY as that has already been done.
 */
int
ble_ll_scan_rx_isr_end(struct os_mbuf *rxpdu, uint8_t crcok)
{
    struct ble_ll_scan_sm *scansm = &g_ble_ll_scan_sm;
    struct ble_mbuf_hdr *hdr = BLE_MBUF_HDR_PTR(rxpdu);
    struct ble_mbuf_hdr_rxinfo *rxinfo = &hdr->rxinfo;
    uint8_t *rxbuf;
    uint8_t pdu_type;
    struct ble_ll_scan_addr_data addrd;
    int rc;

    /*
     * If buffer for incoming PDU was not allocated we need to force scan to be
     * restarted since LL will not be notified. Keep PHY enabled.
     */
    if (rxpdu == NULL) {
        ble_ll_scan_interrupted(scansm);
        return 0;
    }

    if (!crcok) {
        goto scan_rx_isr_ignore;
    }

    rxbuf = rxpdu->om_data;
    pdu_type = rxbuf[0] & BLE_ADV_PDU_HDR_TYPE_MASK;

    switch (pdu_type) {
    case BLE_ADV_PDU_TYPE_ADV_IND:
    case BLE_ADV_PDU_TYPE_ADV_DIRECT_IND:
    case BLE_ADV_PDU_TYPE_ADV_NONCONN_IND:
    case BLE_ADV_PDU_TYPE_ADV_SCAN_IND:
        rc = ble_ll_scan_rx_isr_end_on_adv(pdu_type, rxbuf, hdr, &addrd);
        break;
    case BLE_ADV_PDU_TYPE_SCAN_RSP:
        rc = ble_ll_scan_rx_isr_end_on_scan_rsp(pdu_type, rxbuf, hdr, &addrd);
        break;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    case BLE_ADV_PDU_TYPE_ADV_EXT_IND:
        rc = ble_ll_scan_aux_rx_isr_end_on_ext(&g_ble_ll_scan_sm, rxpdu);
        if (rc < 0) {
            rxinfo->flags |= BLE_MBUF_HDR_F_IGNORED;
        }
        ble_ll_state_set(BLE_LL_STATE_STANDBY);
        /* Return here, we do not want any further processing since it's all
         * handled in scan_aux.
         */
        return -1;
#endif
    default:
        /* This is not something we would like to process here */
        rc = -1;
        break;
    }

    if (rc == -1) {
        goto scan_rx_isr_ignore;
    }

    if (rc == 1) {
        switch (scansm->scanp->scan_type) {
        case BLE_SCAN_TYPE_ACTIVE:
            if (ble_ll_scan_send_scan_req(pdu_type, rxbuf, hdr, &addrd)) {
                /* Keep PHY active and LL in scanning state */
                return 0;
            }
            break;
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
        case BLE_SCAN_TYPE_INITIATE:
            if (ble_ll_conn_send_connect_req(rxpdu, &addrd, 0) == 0) {
                hdr->rxinfo.flags |= BLE_MBUF_HDR_F_CONNECT_IND_TXD;
                return 0;
            }
            break;
#endif
        }
    }

    /* We are done with this PDU so go to standby and let LL resume if needed */
    ble_ll_state_set(BLE_LL_STATE_STANDBY);
    return -1;

scan_rx_isr_ignore:
    rxinfo->flags |= BLE_MBUF_HDR_F_IGNORED;
    ble_ll_state_set(BLE_LL_STATE_STANDBY);
    return -1;
}

/**
 * Called to resume scanning. This is called after an advertising event or
 * connection event has ended. It is also called if we receive a packet while
 * in the initiating or scanning state.
 *
 * If periodic advertising is enabled this is also called on sync event end
 * or sync packet received if chaining
 *
 * Context: Link Layer task
 */
void
ble_ll_scan_chk_resume(void)
{
    os_sr_t sr;
    struct ble_ll_scan_sm *scansm;
    uint32_t now;

    scansm = &g_ble_ll_scan_sm;
    if (scansm->scan_enabled) {
        OS_ENTER_CRITICAL(sr);
        if (scansm->restart_timer_needed) {
            scansm->restart_timer_needed = 0;
            ble_ll_event_send(&scansm->scan_sched_ev);
            STATS_INC(ble_ll_stats, scan_timer_restarted);
            OS_EXIT_CRITICAL(sr);
            return;
        }

        now = ble_ll_tmr_get();
        if (ble_ll_state_get() == BLE_LL_STATE_STANDBY &&
            ble_ll_scan_is_inside_window(scansm->scanp, now)) {
            /* Turn on the receiver and set state */
            ble_ll_scan_start(scansm);
        }
        OS_EXIT_CRITICAL(sr);
    }
}

/**
 * Scan timer callback; means that the scan window timeout has been reached
 * and we should perform the appropriate actions.
 *
 * Context: Interrupt (cputimer)
 *
 * @param arg Pointer to scan state machine.
 */
void
ble_ll_scan_timer_cb(void *arg)
{
    struct ble_ll_scan_sm *scansm;

    scansm = (struct ble_ll_scan_sm *)arg;
    ble_ll_event_send(&scansm->scan_sched_ev);
}

void
ble_ll_scan_interrupted(struct ble_ll_scan_sm *scansm)
{
    ble_ll_event_send(&scansm->scan_interrupted_ev);
}

/**
 * Called when the wait for response timer expires while in the scanning
 * state.
 *
 * Context: Interrupt.
 */
void
ble_ll_scan_wfr_timer_exp(void)
{
    struct ble_ll_scan_sm *scansm = &g_ble_ll_scan_sm;

    /* Update backoff if we failed to receive scan response */
    if (scansm->scan_rsp_pending) {
        scansm->scan_rsp_pending = 0;
        ble_ll_scan_req_backoff(scansm, 0);
    }

    ble_phy_restart_rx();
}

static inline void
ble_ll_scan_dup_move_to_head(struct ble_ll_scan_dup_entry *e)
{
    if (e != TAILQ_FIRST(&g_scan_dup_list)) {
        TAILQ_REMOVE(&g_scan_dup_list, e, link);
        TAILQ_INSERT_HEAD(&g_scan_dup_list, e, link);
    }
}

static inline struct ble_ll_scan_dup_entry *
ble_ll_scan_dup_new(void)
{
    struct ble_ll_scan_dup_entry *e;

    e = os_memblock_get(&g_scan_dup_pool);
    if (!e) {
        e = TAILQ_LAST(&g_scan_dup_list, ble_ll_scan_dup_list);
        TAILQ_REMOVE(&g_scan_dup_list, e, link);
    }

    memset(e, 0, sizeof(*e));

    return e;
}

static int
ble_ll_scan_dup_check_legacy(uint8_t addr_type, uint8_t *addr, uint8_t pdu_type)
{
    struct ble_ll_scan_dup_entry *e;
    uint8_t type;
    int rc;

    type = BLE_LL_SCAN_ENTRY_TYPE_LEGACY(addr_type);

    TAILQ_FOREACH(e, &g_scan_dup_list, link) {
        if ((e->type == type) && !memcmp(e->addr, addr, 6)) {
            break;
        }
    }

    if (e) {
        if (pdu_type == BLE_ADV_PDU_TYPE_ADV_DIRECT_IND) {
            rc = e->flags & BLE_LL_SCAN_DUP_F_DIR_ADV_REPORT_SENT;
        } else if (pdu_type == BLE_ADV_PDU_TYPE_SCAN_RSP) {
            rc = e->flags & BLE_LL_SCAN_DUP_F_SCAN_RSP_SENT;
        } else {
            rc = e->flags & BLE_LL_SCAN_DUP_F_ADV_REPORT_SENT;
        }

        ble_ll_scan_dup_move_to_head(e);
    } else {
        rc = 0;

        e = ble_ll_scan_dup_new();
        e->flags = 0;
        e->type = type;
        memcpy(e->addr, addr, 6);

        TAILQ_INSERT_HEAD(&g_scan_dup_list, e, link);
    }

    return rc;
}

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
int
ble_ll_scan_dup_check_ext(uint8_t addr_type, uint8_t *addr, bool has_aux,
                          uint16_t adi)
{
    struct ble_ll_scan_dup_entry *e;
    bool is_anon;
    uint8_t type;
    int rc;

    is_anon = addr == NULL;
    adi = has_aux ? adi : 0;

    type = BLE_LL_SCAN_ENTRY_TYPE_EXT(addr_type, has_aux, is_anon, adi);

    TAILQ_FOREACH(e, &g_scan_dup_list, link) {
        if ((e->type == type) &&
            (is_anon || !memcmp(e->addr, addr, BLE_DEV_ADDR_LEN))) {
            break;
        }
    }

    if (e) {
        if (e->adi != adi) {
            rc = 0;

            e->flags = 0;
            e->adi = adi;
        } else {
            rc = e->flags & BLE_LL_SCAN_DUP_F_ADV_REPORT_SENT;
        }

        ble_ll_scan_dup_move_to_head(e);
    } else {
        rc = 0;

        e = ble_ll_scan_dup_new();
        e->flags = 0;
        e->type = type;
        e->adi = adi;
        if (!is_anon) {
            memcpy(e->addr, addr, 6);
        }

        TAILQ_INSERT_HEAD(&g_scan_dup_list, e, link);
    }

    return rc;
}

int
ble_ll_scan_dup_update_ext(uint8_t addr_type, uint8_t *addr, bool has_aux,
                           uint16_t adi)
{
    struct ble_ll_scan_dup_entry *e;
    bool is_anon;
    uint8_t type;

    is_anon = addr == NULL;
    adi = has_aux ? adi : 0;

    type = BLE_LL_SCAN_ENTRY_TYPE_EXT(addr_type, has_aux, is_anon, adi);

    /*
     * We assume ble_ll_scan_dup_check() was called before which either matched
     * some entry or allocated new one and placed in on the top of queue.
     */

    e = TAILQ_FIRST(&g_scan_dup_list);
    BLE_LL_ASSERT(e && e->type == type && (is_anon || !memcmp(e->addr, addr, 6)));

    e->flags |= BLE_LL_SCAN_DUP_F_ADV_REPORT_SENT;

    return 0;
}
#endif

static void
ble_ll_scan_rx_pkt_in_restore_addr_data(struct ble_mbuf_hdr *hdr,
                                        struct ble_ll_scan_addr_data *addrd)
{
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    struct ble_ll_scan_sm *scansm = &g_ble_ll_scan_sm;
    struct ble_mbuf_hdr_rxinfo *rxinfo = &hdr->rxinfo;
    struct ble_ll_resolv_entry *rl;
#endif

    addrd->adv_addr = addrd->adva;
    addrd->adv_addr_type = addrd->adva_type;

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    addrd->rpa_index = rxinfo->rpa_index;

    if (hdr->rxinfo.flags & BLE_MBUF_HDR_F_RESOLVED) {
        BLE_LL_ASSERT(rxinfo->rpa_index >= 0);
        rl = &g_ble_ll_resolv_list[rxinfo->rpa_index];
        addrd->adv_addr = rl->rl_identity_addr;
        addrd->adv_addr_type = rl->rl_addr_type;
        addrd->adva_resolved = 1;
    } else {
        addrd->adva_resolved = 0;
    }

    if (hdr->rxinfo.flags & BLE_MBUF_HDR_F_TARGETA_RESOLVED) {
        addrd->targeta = ble_ll_get_our_devaddr(scansm->own_addr_type & 1);
        addrd->targeta_type = scansm->own_addr_type & 1;
        addrd->targeta_resolved = 1;
    } else {
        addrd->targeta_resolved = 0;
    }
#endif
}

static void
ble_ll_scan_rx_pkt_in_on_legacy(uint8_t pdu_type, struct os_mbuf *om,
                                struct ble_mbuf_hdr *hdr,
                                struct ble_ll_scan_addr_data *addrd)
{
    struct ble_ll_scan_sm *scansm = &g_ble_ll_scan_sm;
    uint8_t *rxbuf = om->om_data;
    bool send_hci_report;


    if (!BLE_MBUF_HDR_DEVMATCH(hdr) ||
        !BLE_MBUF_HDR_CRC_OK(hdr) ||
        BLE_MBUF_HDR_IGNORED(hdr) ||
        !scansm->scan_enabled) {
        return;
    }

    ble_ll_scan_get_addr_data_from_legacy(pdu_type, rxbuf, addrd);
    ble_ll_scan_rx_pkt_in_restore_addr_data(hdr, addrd);

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    if (addrd->adva_resolved) {
        BLE_LL_ASSERT(addrd->rpa_index >= 0);
        ble_ll_resolv_set_peer_rpa(addrd->rpa_index, addrd->adva);
    }
#endif

    send_hci_report = !scansm->scan_filt_dups ||
                      !ble_ll_scan_dup_check_legacy(addrd->adv_addr_type,
                                                    addrd->adv_addr,
                                                    pdu_type);
    if (send_hci_report) {
        /* Sending advertising report will also update scan_dup list */
        ble_ll_scan_send_adv_report(pdu_type,
                                    addrd->adv_addr, addrd->adv_addr_type,
                                    addrd->targeta, addrd->targeta_type,
                                    om, hdr, scansm);
    }

    if (BLE_MBUF_HDR_SCAN_RSP_RXD(hdr)) {
        ble_ll_scan_req_backoff(scansm, 1);
    }
}

/**
 * Process a received PDU while in the scanning state.
 *
 * Context: Link Layer task.
 *
 * @param pdu_type
 * @param rxbuf
 */
void
ble_ll_scan_rx_pkt_in(uint8_t ptype, struct os_mbuf *om, struct ble_mbuf_hdr *hdr)
{
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
    struct ble_mbuf_hdr_rxinfo *rxinfo;
    uint8_t *targeta;
#endif
    struct ble_ll_scan_sm *scansm;
    struct ble_ll_scan_addr_data addrd;
    uint8_t max_pdu_type;

    scansm = &g_ble_ll_scan_sm;

    /* Ignore PDUs we do not expect here */
    max_pdu_type = BLE_ADV_PDU_TYPE_ADV_SCAN_IND;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    if (scansm->ext_scanning) {
        /* Note: We do not expect AUX_CONNECT_RSP here */
        max_pdu_type = BLE_ADV_PDU_TYPE_ADV_EXT_IND;
    }
#endif
    if (ptype > max_pdu_type) {
        ble_ll_scan_chk_resume();
        return;
    }

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    if (ptype == BLE_ADV_PDU_TYPE_ADV_EXT_IND) {
        ble_ll_scan_aux_pkt_in_on_ext(om, hdr);
        ble_ll_scan_chk_resume();
        return;
    }
#endif

    switch (scansm->scanp->scan_type) {
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
    case BLE_SCAN_TYPE_INITIATE:
        rxinfo = &hdr->rxinfo;
        if (rxinfo->flags & BLE_MBUF_HDR_F_CONNECT_IND_TXD) {
            /* We need to keep original TargetA in case it was resolved, so rl
             * can be updated properly.
             */
            ble_ll_scan_get_addr_data_from_legacy(ptype, om->om_data, &addrd);
            targeta = addrd.targeta;
            ble_ll_scan_rx_pkt_in_restore_addr_data(hdr, &addrd);

            ble_ll_scan_sm_stop(0);
            ble_ll_conn_created_on_legacy(om, &addrd, targeta);
            return;
        }
        break;
#endif
    default:
        ble_ll_scan_rx_pkt_in_on_legacy(ptype, om, hdr, &addrd);
        break;
    }

    ble_ll_scan_chk_resume();
}

int
ble_ll_scan_hci_set_params(const uint8_t *cmdbuf, uint8_t len)
{
    const struct ble_hci_le_set_scan_params_cp *cmd = (const void *)cmdbuf;
    uint16_t scan_itvl;
    uint16_t scan_window;
    struct ble_ll_scan_sm *scansm;
    struct ble_ll_scan_phy *scanp;

    if (len != sizeof(*cmd)) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    /* If already enabled, we return an error */
    scansm = &g_ble_ll_scan_sm;
    if (scansm->scan_enabled) {
        return BLE_ERR_CMD_DISALLOWED;
    }

    /* Get the scan interval and window */
    scan_itvl = le16toh(cmd->scan_itvl);
    scan_window = le16toh(cmd->scan_window);

    /* Check scan type */
    if ((cmd->scan_type != BLE_HCI_SCAN_TYPE_PASSIVE) &&
        (cmd->scan_type != BLE_HCI_SCAN_TYPE_ACTIVE)) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    /* Check interval and window */
    if ((scan_itvl < BLE_HCI_SCAN_ITVL_MIN) ||
        (scan_itvl > BLE_HCI_SCAN_ITVL_MAX) ||
        (scan_window < BLE_HCI_SCAN_WINDOW_MIN) ||
        (scan_window > BLE_HCI_SCAN_WINDOW_MAX) ||
        (scan_itvl < scan_window)) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    /* Check own addr type */
    if (cmd->own_addr_type > BLE_HCI_ADV_OWN_ADDR_MAX) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    /* Check scanner filter policy */
    if (cmd->filter_policy > BLE_HCI_SCAN_FILT_MAX) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    /* Store scan parameters */
    g_ble_ll_scan_params.own_addr_type = cmd->own_addr_type;
    g_ble_ll_scan_params.scan_filt_policy = cmd->filter_policy;

    scanp = &g_ble_ll_scan_params.scan_phys[PHY_UNCODED];
    scanp->configured = 1;
    scanp->scan_type = cmd->scan_type;
    scanp->timing.interval = ble_ll_scan_time_hci_to_ticks(scan_itvl);
    scanp->timing.window = ble_ll_scan_time_hci_to_ticks(scan_window);

#if (BLE_LL_SCAN_PHY_NUMBER == 2)
    scanp = &g_ble_ll_scan_params.scan_phys[PHY_CODED];
    scanp->configured = 0;
#endif

    return 0;
}

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
static int
ble_ll_scan_check_phy_params(uint8_t type, uint16_t itvl, uint16_t window)
{
    /* Check scan type */
    if ((type != BLE_HCI_SCAN_TYPE_PASSIVE) &&
        (type != BLE_HCI_SCAN_TYPE_ACTIVE)) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    /* Check interval and window */
    if ((itvl < BLE_HCI_SCAN_ITVL_MIN) ||
        (itvl > BLE_HCI_SCAN_ITVL_MAX_EXT) ||
        (window < BLE_HCI_SCAN_WINDOW_MIN) ||
        (window > BLE_HCI_SCAN_WINDOW_MAX_EXT) ||
        (itvl < window)) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    return 0;
}

int
ble_ll_scan_hci_set_ext_params(const uint8_t *cmdbuf, uint8_t len)
{
    const struct ble_hci_le_set_ext_scan_params_cp *cmd = (const void *) cmdbuf;
    const struct scan_params *params = cmd->scans;

    struct ble_ll_scan_phy new_params[BLE_LL_SCAN_PHY_NUMBER] = { };
    struct ble_ll_scan_phy *uncoded = &new_params[PHY_UNCODED];
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
    struct ble_ll_scan_phy *coded = &new_params[PHY_CODED];
#endif
    uint16_t interval;
    uint16_t window;
    int rc;

    if (len <= sizeof(*cmd)) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    len -= sizeof(*cmd);

    /* If already enabled, we return an error */
    if (g_ble_ll_scan_sm.scan_enabled) {
        return BLE_ERR_CMD_DISALLOWED;
    }

    /* Check own addr type */
    if (cmd->own_addr_type > BLE_HCI_ADV_OWN_ADDR_MAX) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    /* Check scanner filter policy */
    if (cmd->filter_policy > BLE_HCI_SCAN_FILT_MAX) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    /* Check if no reserved bits in PHYS are set and that at least one valid PHY
     * is set.
     */
    if (!(cmd->phys & SCAN_VALID_PHY_MASK) ||
        (cmd->phys & ~SCAN_VALID_PHY_MASK)) {
         return BLE_ERR_INV_HCI_CMD_PARMS;
     }

    if (cmd->phys & BLE_HCI_LE_PHY_1M_PREF_MASK) {
        if (len < sizeof(*params)) {
            return BLE_ERR_INV_HCI_CMD_PARMS;
        }

        interval = le16toh(params->itvl);
        window = le16toh(params->window);

        rc = ble_ll_scan_check_phy_params(params->type, interval, window);
        if (rc) {
            return rc;
        }

        uncoded->scan_type = params->type;
        uncoded->timing.interval = ble_ll_scan_time_hci_to_ticks(interval);
        uncoded->timing.window = ble_ll_scan_time_hci_to_ticks(window);

        /* That means user wants to use this PHY for scanning */
        uncoded->configured = 1;
        params++;
        len -= sizeof(*params);
    }

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
    if (cmd->phys & BLE_HCI_LE_PHY_CODED_PREF_MASK) {
        if (len < sizeof(*params)) {
            return BLE_ERR_INV_HCI_CMD_PARMS;
        }

        interval = le16toh(params->itvl);
        window = le16toh(params->window);

        rc = ble_ll_scan_check_phy_params(params->type, interval, window);
        if (rc) {
            return rc;
        }

        coded->scan_type = params->type;
        coded->timing.interval = ble_ll_scan_time_hci_to_ticks(interval);
        coded->timing.window = ble_ll_scan_time_hci_to_ticks(window);

        /* That means user wants to use this PHY for scanning */
        coded->configured = 1;
    }

    /* if any of PHYs is configured for continuous scan we alter interval to
     * fit other PHY
     */
    if (coded->configured && uncoded->configured) {
        if (coded->timing.interval == coded->timing.window) {
            coded->timing.interval += uncoded->timing.window;
        }

        if (uncoded->timing.interval == uncoded->timing.window) {
            uncoded->timing.interval += coded->timing.window;
        }
    }
#endif

    g_ble_ll_scan_params.own_addr_type = cmd->own_addr_type;
    g_ble_ll_scan_params.scan_filt_policy = cmd->filter_policy;

    memcpy(g_ble_ll_scan_params.scan_phys, new_params, sizeof(new_params));

    return 0;
}

#endif

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
static void
ble_ll_scan_duration_period_timers_restart(struct ble_ll_scan_sm *scansm)
{
    ble_npl_callout_stop(&scansm->duration_timer);
    ble_npl_callout_stop(&scansm->period_timer);

    if (scansm->duration_ticks) {
        ble_npl_callout_reset(&scansm->duration_timer,
                              scansm->duration_ticks);

        if (scansm->period_ticks) {
            ble_npl_callout_reset(&scansm->period_timer,
                                  scansm->period_ticks);
        }
    }
}

static void
ble_ll_scan_duration_timer_cb(struct ble_npl_event *ev)
{
    struct ble_ll_scan_sm *scansm = ble_npl_event_get_arg(ev);

    ble_ll_scan_sm_stop(2);

    /* if period is set both timers get started from period cb */
    if (!scansm->period_ticks) {
        ble_ll_hci_ev_send_scan_timeout();
    }
}

static void
ble_ll_scan_period_timer_cb(struct ble_npl_event *ev)
{
    struct ble_ll_scan_sm *scansm = ble_npl_event_get_arg(ev);

    ble_ll_scan_sm_start(scansm);

    /* always start timer regardless of ble_ll_scan_sm_start result
     * if it failed will restart in next period
     */
    ble_ll_scan_duration_period_timers_restart(scansm);
}
#endif

/**
 * ble ll scan set enable
 *
 * HCI scan set enable command processing function
 *
 *  Context: Link Layer task (HCI Command parser).
 *
 * @return int BLE error code.
 */
static int
ble_ll_scan_set_enable(uint8_t enable, uint8_t filter_dups, uint16_t period,
                       uint16_t dur, bool ext)
{
    int rc;
    struct ble_ll_scan_sm *scansm;
    struct ble_ll_scan_phy *scanp;
    struct ble_ll_scan_phy *scanp_phy;
    int i;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    ble_npl_time_t period_ticks = 0;
    ble_npl_time_t dur_ticks = 0;
#endif

    /* Check for valid parameters */
    if ((filter_dups > 1) || (enable > 1)) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    scansm = &g_ble_ll_scan_sm;

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    /* we can do that here since value will never change until reset */
    scansm->ext_scanning = ext;

    if (ext) {
        /* Period parameter is ignored when the Duration parameter is zero */
        if (!dur) {
            period = 0;
        }

        /* period is in 1.28 sec units */
        if (ble_npl_time_ms_to_ticks(period * 1280, &period_ticks)) {
            return BLE_ERR_INV_HCI_CMD_PARMS;
        }

        /* duration is in 10ms units */
        dur_ticks = ble_npl_time_ms_to_ticks32(dur * 10);

        if (dur_ticks && period_ticks && (dur_ticks >= period_ticks)) {
            return BLE_ERR_INV_HCI_CMD_PARMS;
        }
    }
#endif

    /* disable*/
    if (!enable) {
        if (scansm->scan_enabled) {
            ble_ll_scan_sm_stop(1);
        }
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
        ble_npl_callout_stop(&scansm->duration_timer);
        ble_npl_callout_stop(&scansm->period_timer);
#endif

        return BLE_ERR_SUCCESS;
    }

    /* if already enable we just need to update parameters */
    if (scansm->scan_enabled) {
        /* Controller does not allow initiating and scanning.*/
#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
        for (i = 0; i < BLE_LL_SCAN_PHY_NUMBER; i++) {
            scanp_phy = &scansm->scan_phys[i];
            if (scanp_phy->configured &&
                                scanp_phy->scan_type == BLE_SCAN_TYPE_INITIATE) {
                return BLE_ERR_CMD_DISALLOWED;
            }
        }
#endif

#if MYNEWT_VAL(BLE_LL_NUM_SCAN_DUP_ADVS)
        /* update filter policy */
        scansm->scan_filt_dups = filter_dups;
#endif

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
        /* restart timers according to new settings */
        scansm->duration_ticks = dur_ticks;
        scansm->period_ticks = period_ticks;
        ble_ll_scan_duration_period_timers_restart(scansm);
#endif

        return BLE_ERR_SUCCESS;
    }

    /* we can store those upfront regardless of start scan result since scan is
     * disabled now
     */

#if MYNEWT_VAL(BLE_LL_NUM_SCAN_DUP_ADVS)
    scansm->scan_filt_dups = filter_dups;
#endif
    scansm->scanp = NULL;
    scansm->scanp_next = NULL;

    scansm->own_addr_type = g_ble_ll_scan_params.own_addr_type;
    scansm->scan_filt_policy = g_ble_ll_scan_params.scan_filt_policy;

    for (i = 0; i < BLE_LL_SCAN_PHY_NUMBER; i++) {
        scanp_phy = &scansm->scan_phys[i];
        scanp = &g_ble_ll_scan_params.scan_phys[i];

        if (!scanp->configured) {
            continue;
        }

        scanp_phy->configured = scanp->configured;
        scanp_phy->scan_type = scanp->scan_type;
        scanp_phy->timing = scanp->timing;

        if (!scansm->scanp) {
            scansm->scanp = scanp_phy;
        } else {
            scansm->scanp_next = scanp_phy;
        }
    }

    /* spec is not really clear if we should use defaults in this case
     * or just disallow starting scan without explicit configuration
     * For now be nice to host and just use values based on LE Set Scan
     * Parameters defaults.
     */
    if (!scansm->scanp) {
        scansm->scanp = &scansm->scan_phys[PHY_UNCODED];
        scansm->own_addr_type = BLE_ADDR_PUBLIC;
        scansm->scan_filt_policy = BLE_HCI_SCAN_FILT_NO_WL;

        scanp_phy = scansm->scanp;
        scanp_phy->configured = 1;
        scanp_phy->scan_type = BLE_SCAN_TYPE_PASSIVE;
        scanp_phy->timing.interval =
                        ble_ll_scan_time_hci_to_ticks(BLE_HCI_SCAN_ITVL_DEF);
        scanp_phy->timing.window =
                        ble_ll_scan_time_hci_to_ticks(BLE_HCI_SCAN_WINDOW_DEF);
    }

    rc = ble_ll_scan_sm_start(scansm);

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    if (rc == BLE_ERR_SUCCESS) {
        scansm->duration_ticks = dur_ticks;
        scansm->period_ticks = period_ticks;
        ble_ll_scan_duration_period_timers_restart(scansm);
    }
#endif

    return rc;
}

int ble_ll_scan_hci_set_enable(const uint8_t *cmdbuf, uint8_t len)
{
    const struct ble_hci_le_set_scan_enable_cp *cmd = (const void *) cmdbuf;

    if (len != sizeof(*cmd)) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    return ble_ll_scan_set_enable(cmd->enable, cmd->filter_duplicates, 0, 0,
                                  false);
}

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
int ble_ll_scan_hci_set_ext_enable(const uint8_t *cmdbuf, uint8_t len)
{
    const struct ble_hci_le_set_ext_scan_enable_cp *cmd = (const void *) cmdbuf;

    if (len != sizeof(*cmd)) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    return ble_ll_scan_set_enable(cmd->enable, cmd->filter_dup,
                                  le16toh(cmd->period), le16toh(cmd->duration),
                                  true);
}
#endif

/**
 * Checks if controller can change the whitelist. If scanning is enabled and
 * using the whitelist the controller is not allowed to change the whitelist.
 *
 * @return int 0: not allowed to change whitelist; 1: change allowed.
 */
int
ble_ll_scan_can_chg_whitelist(void)
{
    int rc;
    struct ble_ll_scan_sm *scansm;

    scansm = &g_ble_ll_scan_sm;
    if (scansm->scan_enabled && (scansm->scan_filt_policy & 1)) {
        rc = 0;
    } else {
        rc = 1;
    }

    return rc;
}

#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
int
ble_ll_scan_initiator_start(struct ble_ll_conn_sm *connsm, uint8_t ext,
                            struct ble_ll_conn_create_scan *cc_scan)
{
    struct ble_ll_scan_sm *scansm;
    struct ble_ll_scan_phy *scanp_uncoded;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV) && MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
    struct ble_ll_scan_phy *scanp_coded;
#endif
    uint8_t init_phy_mask;
    int rc;

    scansm = &g_ble_ll_scan_sm;
    scansm->own_addr_type = cc_scan->own_addr_type;
    scansm->scan_filt_policy = cc_scan->filter_policy;
    scansm->scanp = NULL;
    scansm->scanp_next = NULL;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    scansm->ext_scanning = ext;
    init_phy_mask = cc_scan->init_phy_mask;
#else
    init_phy_mask = BLE_PHY_MASK_1M;
#endif
    scansm->connsm = connsm;

    scanp_uncoded = &scansm->scan_phys[PHY_UNCODED];
    if (init_phy_mask & BLE_PHY_MASK_1M) {
        scanp_uncoded->configured = 1;
        scanp_uncoded->timing.interval = ble_ll_scan_time_hci_to_ticks(
                                cc_scan->scan_params[PHY_UNCODED].itvl);
        scanp_uncoded->timing.window = ble_ll_scan_time_hci_to_ticks(
                                cc_scan->scan_params[PHY_UNCODED].window);
        scanp_uncoded->scan_type = BLE_SCAN_TYPE_INITIATE;
        scansm->scanp = scanp_uncoded;
    } else {
        scanp_uncoded->configured = 0;
    }

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV) && MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
    scanp_coded = &scansm->scan_phys[PHY_CODED];
    if (init_phy_mask & BLE_PHY_MASK_CODED) {
        scanp_coded->configured = 1;
        scanp_coded->timing.interval = ble_ll_scan_time_hci_to_ticks(
                                cc_scan->scan_params[PHY_CODED].itvl);
        scanp_coded->timing.window = ble_ll_scan_time_hci_to_ticks(
                                cc_scan->scan_params[PHY_CODED].window);
        scanp_coded->scan_type = BLE_SCAN_TYPE_INITIATE;
        if (scansm->scanp) {
            scansm->scanp_next = scanp_coded;
        } else {
            scansm->scanp = scanp_coded;
        }
    } else {
        scanp_coded->configured = 0;
    }

    /* if any of PHYs is configured for continuous scan we alter interval to
     * fit other PHY
     */
    if (scansm->scanp && scansm->scanp_next && scanp_coded->configured &&
        scanp_uncoded->configured) {
        if (scanp_coded->timing.interval == scanp_coded->timing.window) {
            scanp_coded->timing.interval += scanp_uncoded->timing.window;
        }

        if (scanp_uncoded->timing.interval == scanp_uncoded->timing.window) {
            scanp_uncoded->timing.interval += scanp_coded->timing.window;
        }
    }
#endif

    rc = ble_ll_scan_sm_start(scansm);
    if (rc == 0) {
        g_ble_ll_conn_create_sm.connsm = connsm;
    }

    return rc;
}
#endif

/**
 * Checks to see if the scanner is enabled.
 *
 * @return int 0: not enabled; enabled otherwise
 */
int
ble_ll_scan_enabled(void)
{
    return (int)g_ble_ll_scan_sm.scan_enabled;
}

/**
 * Returns the peer resolvable private address of last device connecting to us
 *
 * @return uint8_t*
 */
uint8_t *
ble_ll_scan_get_peer_rpa(void)
{
    struct ble_ll_scan_sm *scansm;

    /* XXX: should this go into IRK list or connection? */
    scansm = &g_ble_ll_scan_sm;
    return scansm->scan_peer_rpa;
}

/**
 * Returns the local resolvable private address currently being using by
 * the scanner/initiator
 *
 * @return uint8_t*
 */
uint8_t *
ble_ll_scan_get_local_rpa(void)
{
    return g_ble_ll_scan_sm.pdu_data.scana;
}

/**
 * Set the Resolvable Private Address in the scanning (or initiating) state
 * machine.
 *
 * XXX: should this go into IRK list or connection?
 *
 * @param rpa
 */
void
ble_ll_scan_set_peer_rpa(uint8_t *rpa)
{
    struct ble_ll_scan_sm *scansm;

    scansm = &g_ble_ll_scan_sm;
    memcpy(scansm->scan_peer_rpa, rpa, BLE_DEV_ADDR_LEN);
}

struct ble_ll_scan_pdu_data *
ble_ll_scan_get_pdu_data(void)
{
    return &g_ble_ll_scan_sm.pdu_data;
}

static void
ble_ll_scan_common_init(void)
{
    struct ble_ll_scan_sm *scansm;
    struct ble_ll_scan_phy *scanp;
    int i;

    /* Clear state machine in case re-initialized */
    scansm = &g_ble_ll_scan_sm;
    memset(scansm, 0, sizeof(struct ble_ll_scan_sm));

    /* Clear scan parameters in case re-initialized */
    memset(&g_ble_ll_scan_params, 0, sizeof(g_ble_ll_scan_params));

    /* Initialize scanning window end event */
    ble_npl_event_init(&scansm->scan_sched_ev, ble_ll_scan_event_proc, scansm);

    for (i = 0; i < BLE_LL_SCAN_PHY_NUMBER; i++) {
        /* Set all non-zero default parameters */
        scanp = &g_ble_ll_scan_params.scan_phys[i];
        scanp->timing.interval =
                        ble_ll_scan_time_hci_to_ticks(BLE_HCI_SCAN_ITVL_DEF);
        scanp->timing.window =
                        ble_ll_scan_time_hci_to_ticks(BLE_HCI_SCAN_WINDOW_DEF);
    }

    scansm->scan_phys[PHY_UNCODED].phy = BLE_PHY_1M;
#if (BLE_LL_SCAN_PHY_NUMBER == 2)
    scansm->scan_phys[PHY_CODED].phy = BLE_PHY_CODED;
#endif

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    /* Make sure we'll generate new NRPA if necessary */
    scansm->scan_nrpa_timer = ble_npl_time_get();
#endif

    /* Initialize scanning timer */
    ble_ll_tmr_init(&scansm->scan_timer, ble_ll_scan_timer_cb, scansm);

    /* Initialize extended scan timers */
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    ble_npl_callout_init(&scansm->duration_timer, &g_ble_ll_data.ll_evq,
                                        ble_ll_scan_duration_timer_cb, scansm);
    ble_npl_callout_init(&scansm->period_timer, &g_ble_ll_data.ll_evq,
                                        ble_ll_scan_period_timer_cb, scansm);
#endif

    ble_npl_event_init(&scansm->scan_interrupted_ev, ble_ll_scan_interrupted_event_cb, NULL);
}

/**
 * Called when the controller receives the reset command. Resets the
 * scanning state machine to its initial state.
 *
 * @return int
 */
void
ble_ll_scan_reset(void)
{
    struct ble_ll_scan_sm *scansm;

    scansm = &g_ble_ll_scan_sm;

    /* If enabled, stop it. */
    if (scansm->scan_enabled) {
        ble_ll_scan_sm_stop(0);
    }

    /* stop extended scan timers */
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    ble_npl_callout_stop(&scansm->duration_timer);
    ble_npl_callout_stop(&scansm->period_timer);
#endif

    /* Reset duplicate advertisers and those from which we rxd a response */
    g_ble_ll_scan_num_rsp_advs = 0;
    memset(&g_ble_ll_scan_rsp_advs[0], 0, sizeof(g_ble_ll_scan_rsp_advs));

    os_mempool_clear(&g_scan_dup_pool);
    TAILQ_INIT(&g_scan_dup_list);

    /* Call the common init function again */
    ble_ll_scan_common_init();
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    ble_ll_scan_aux_init();
#endif
}

/**
 * ble ll scan init
 *
 * Initialize a scanner. Must be called before scanning can be started.
 * Expected to be called with a un-initialized scanning state machine.
 */
void
ble_ll_scan_init(void)
{
    os_error_t err;

    err = os_mempool_init(&g_scan_dup_pool,
                          MYNEWT_VAL(BLE_LL_NUM_SCAN_DUP_ADVS),
                          sizeof(struct ble_ll_scan_dup_entry),
                          g_scan_dup_mem,
                          "ble_ll_scan_dup_pool");
    BLE_LL_ASSERT(err == 0);

    TAILQ_INIT(&g_scan_dup_list);

    ble_ll_scan_common_init();
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    ble_ll_scan_aux_init();
#endif
}

#endif
