/*
 * 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 <string.h>
#include <errno.h>
#include "testutil/testutil.h"
#include "nimble/ble.h"
#include "nimble/hci_common.h"
#include "host/ble_hs_adv.h"
#include "ble_hs_test.h"
#include "ble_hs_test_util.h"

#define BLE_HCI_SET_SCAN_PARAM_LEN          (7)
#define BLE_HCI_SET_SCAN_ENABLE_LEN         (2)
#define BLE_HCI_DISCONNECT_CMD_LEN          (3)
#define BLE_HCI_SET_ADV_PARAM_LEN           (15)
#define BLE_HCI_SET_ADV_ENABLE_LEN          (1)
#define BLE_HCI_CONN_UPDATE_LEN             (14)
#define BLE_HCI_CONN_PARAM_REPLY_LEN        (14)
#define BLE_HCI_CONN_PARAM_NEG_REPLY_LEN    (3)

static struct ble_gap_event ble_gap_test_event;
static int ble_gap_test_conn_status;
static struct ble_gap_conn_desc ble_gap_test_conn_desc;
static void *ble_gap_test_conn_arg;
static struct ble_gap_upd_params ble_gap_test_conn_peer_params;
static struct ble_gap_upd_params ble_gap_test_conn_self_params;

static int ble_gap_test_disc_event_type;
static struct ble_gap_disc_desc ble_gap_test_disc_desc;
static void *ble_gap_test_disc_arg;

/*****************************************************************************
 * $misc                                                                     *
 *****************************************************************************/

static void
ble_gap_test_util_reset_cb_info(void)
{
    memset(&ble_gap_test_event, 0xff, sizeof ble_gap_test_event);
    ble_gap_test_conn_status = -1;
    memset(&ble_gap_test_conn_desc, 0xff, sizeof ble_gap_test_conn_desc);
    ble_gap_test_conn_arg = (void *)-1;

    ble_gap_test_disc_event_type = -1;
    memset(&ble_gap_test_disc_desc, 0xff, sizeof ble_gap_test_disc_desc);
    ble_gap_test_disc_arg = (void *)-1;
}

static void
ble_gap_test_util_init(void)
{
    ble_hs_test_util_init();
    ble_hs_test_util_set_static_rnd_addr((uint8_t[6]){ 1, 2, 3, 4, 5, 0xc0 });
    ble_gap_test_util_reset_cb_info();
}

static int
ble_gap_test_util_disc_cb(struct ble_gap_event *event, void *arg)
{
    ble_gap_test_disc_event_type = event->type;
    ble_gap_test_disc_arg = arg;

    if (event->type == BLE_GAP_EVENT_DISC) {
        ble_gap_test_disc_desc = event->disc;
    }

    return 0;
}

static int
ble_gap_test_util_connect_cb(struct ble_gap_event *event, void *arg)
{
    int *fail_reason;
    int ret;

    ble_gap_test_event = *event;
    ble_gap_test_conn_arg = arg;

    switch (event->type) {
    case BLE_GAP_EVENT_CONNECT:
        ble_gap_test_conn_status = event->connect.status;
        ret = ble_gap_conn_find(event->connect.conn_handle,
                                &ble_gap_test_conn_desc);
        TEST_ASSERT_FATAL(ble_gap_test_conn_status || ret == 0);
        break;

    case BLE_GAP_EVENT_DISCONNECT:
        ble_gap_test_conn_status = event->disconnect.reason;
        ble_gap_test_conn_desc = event->disconnect.conn;
        break;

    case BLE_GAP_EVENT_CONN_UPDATE:
        ble_gap_test_conn_status = event->conn_update.status;
        ret = ble_gap_conn_find(event->conn_update.conn_handle,
                               &ble_gap_test_conn_desc);
        TEST_ASSERT_FATAL(ret == 0);
        break;

    case BLE_GAP_EVENT_TERM_FAILURE:
        ble_gap_test_conn_status = event->term_failure.status;
        ret = ble_gap_conn_find(event->term_failure.conn_handle,
                                &ble_gap_test_conn_desc);
        TEST_ASSERT_FATAL(ret == 0);
        break;

    case BLE_GAP_EVENT_ADV_COMPLETE:
        ble_gap_test_conn_arg = arg;
        break;

    case BLE_GAP_EVENT_CONN_UPDATE_REQ:
        ble_gap_test_conn_peer_params = *event->conn_update_req.peer_params;
        *event->conn_update_req.self_params = ble_gap_test_conn_self_params;
        ret = ble_gap_conn_find(event->conn_update_req.conn_handle,
                                &ble_gap_test_conn_desc);
        TEST_ASSERT_FATAL(ret == 0);

        fail_reason = arg;
        if (fail_reason == NULL) {
            return 0;
        } else {
            return *fail_reason;
        }
        break;

    case BLE_GAP_EVENT_MTU:
        break;

    default:
        TEST_ASSERT_FATAL(0);
        break;
    }

    return 0;
}

static int
ble_gap_test_util_copy_cb(struct ble_gap_event *event, void *arg)
{
    ble_gap_test_event = *event;
    ble_gap_test_conn_arg = arg;

    return 0;
}

static void
ble_gap_test_util_verify_tx_clear_wl(void)
{
    uint8_t param_len;

    ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                   BLE_HCI_OCF_LE_CLEAR_WHITE_LIST,
                                   &param_len);
    TEST_ASSERT(param_len == 0);
}

static void
ble_gap_test_util_verify_tx_add_wl(ble_addr_t *addr)
{
    uint8_t param_len;
    uint8_t *param;
    int i;

    param = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                           BLE_HCI_OCF_LE_ADD_WHITE_LIST,
                                           &param_len);
    TEST_ASSERT(param_len == 7);
    TEST_ASSERT(param[0] == addr->type);
    for (i = 0; i < 6; i++) {
        TEST_ASSERT(param[1 + i] == addr->val[i]);
    }
}

static void
ble_gap_test_util_verify_tx_set_scan_params(uint8_t own_addr_type,
                                            uint8_t scan_type,
                                            uint16_t itvl,
                                            uint16_t scan_window,
                                            uint8_t filter_policy)
{
    uint8_t param_len;
    uint8_t *param;

    param = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                           BLE_HCI_OCF_LE_SET_SCAN_PARAMS,
                                           &param_len);
    TEST_ASSERT(param_len == BLE_HCI_SET_SCAN_PARAM_LEN);
    TEST_ASSERT(param[0] == scan_type);
    TEST_ASSERT(get_le16(param + 1) == itvl);
    TEST_ASSERT(get_le16(param + 3) == scan_window);
    TEST_ASSERT(param[5] == own_addr_type);
    TEST_ASSERT(param[6] == filter_policy);
}

static void
ble_gap_test_util_verify_tx_scan_enable(uint8_t enable,
                                        uint8_t filter_duplicates)
{
    uint8_t param_len;
    uint8_t *param;

    param = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                           BLE_HCI_OCF_LE_SET_SCAN_ENABLE,
                                           &param_len);
    TEST_ASSERT(param_len == BLE_HCI_SET_SCAN_ENABLE_LEN);
    TEST_ASSERT(param[0] == enable);
    TEST_ASSERT(param[1] == filter_duplicates);
}

static void
ble_hs_test_util_hci_verify_tx_create_conn_cancel(void)
{
    uint8_t param_len;

    ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                   BLE_HCI_OCF_LE_CREATE_CONN_CANCEL,
                                   &param_len);
    TEST_ASSERT(param_len == 0);
}

static void
ble_gap_test_util_verify_tx_disconnect(void)
{
    uint8_t param_len;
    uint8_t *param;

    param = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LINK_CTRL,
                                           BLE_HCI_OCF_DISCONNECT_CMD,
                                           &param_len);
    TEST_ASSERT(param_len == BLE_HCI_DISCONNECT_CMD_LEN);
    TEST_ASSERT(get_le16(param + 0) == 2);
    TEST_ASSERT(param[2] == BLE_ERR_REM_USER_CONN_TERM);
}

static void
ble_gap_test_util_verify_tx_adv_params(void)
{
    uint8_t param_len;

    ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                   BLE_HCI_OCF_LE_SET_ADV_PARAMS,
                                   &param_len);
    TEST_ASSERT(param_len == BLE_HCI_SET_ADV_PARAM_LEN);

    /* Note: Content of message verified in ble_hs_adv_test.c. */
}

static void
ble_gap_test_util_verify_tx_adv_data(void)
{
    uint8_t param_len;

    ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                   BLE_HCI_OCF_LE_SET_ADV_DATA,
                                   &param_len);
    /* Note: Content of message verified in ble_hs_adv_test.c. */
}

#if 0
static void
ble_gap_test_util_verify_tx_rsp_data(void)
{
    uint8_t param_len;
    uint8_t *param;

    param = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                           BLE_HCI_OCF_LE_SET_SCAN_RSP_DATA,
                                           &param_len);
    (void)param; /* XXX: Verify other fields. */
}
#endif

static void
ble_gap_test_util_verify_tx_adv_enable(int enabled)
{
    uint8_t param_len;
    uint8_t *param;

    param = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                           BLE_HCI_OCF_LE_SET_ADV_ENABLE,
                                           &param_len);
    TEST_ASSERT(param_len == BLE_HCI_SET_ADV_ENABLE_LEN);
    TEST_ASSERT(param[0] == !!enabled);
}

static void
ble_gap_test_util_verify_tx_update_conn(struct ble_gap_upd_params *params)
{
    uint8_t param_len;
    uint8_t *param;

    param = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                           BLE_HCI_OCF_LE_CONN_UPDATE,
                                           &param_len);
    TEST_ASSERT(param_len == BLE_HCI_CONN_UPDATE_LEN);
    TEST_ASSERT(get_le16(param + 0) == 2);
    TEST_ASSERT(get_le16(param + 2) == params->itvl_min);
    TEST_ASSERT(get_le16(param + 4) == params->itvl_max);
    TEST_ASSERT(get_le16(param + 6) == params->latency);
    TEST_ASSERT(get_le16(param + 8) == params->supervision_timeout);
    TEST_ASSERT(get_le16(param + 10) == params->min_ce_len);
    TEST_ASSERT(get_le16(param + 12) == params->max_ce_len);
}

static void
ble_gap_test_util_verify_tx_params_reply_pos(void)
{
    uint8_t param_len;
    uint8_t *param;

    param = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                           BLE_HCI_OCF_LE_REM_CONN_PARAM_RR,
                                           &param_len);
    TEST_ASSERT(param_len == BLE_HCI_CONN_PARAM_REPLY_LEN);
    TEST_ASSERT(get_le16(param + 0) == 2);
    TEST_ASSERT(get_le16(param + 2) == ble_gap_test_conn_self_params.itvl_min);
    TEST_ASSERT(get_le16(param + 4) == ble_gap_test_conn_self_params.itvl_max);
    TEST_ASSERT(get_le16(param + 6) == ble_gap_test_conn_self_params.latency);
    TEST_ASSERT(get_le16(param + 8) ==
                ble_gap_test_conn_self_params.supervision_timeout);
    TEST_ASSERT(get_le16(param + 10) ==
                ble_gap_test_conn_self_params.min_ce_len);
    TEST_ASSERT(get_le16(param + 12) ==
                ble_gap_test_conn_self_params.max_ce_len);
}

static void
ble_gap_test_util_verify_tx_params_reply_neg(uint8_t reason)
{
    uint8_t param_len;
    uint8_t *param;

    param = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                           BLE_HCI_OCF_LE_REM_CONN_PARAM_NRR,
                                           &param_len);
    TEST_ASSERT(param_len == BLE_HCI_CONN_PARAM_NEG_REPLY_LEN);
    TEST_ASSERT(get_le16(param + 0) == 2);
    TEST_ASSERT(param[2] == reason);
}

static void
ble_gap_test_util_rx_update_complete(
    uint8_t status,
    const struct ble_gap_upd_params *params)
{
    struct ble_hci_ev_le_subev_conn_upd_complete evt;

    evt.subev_code = BLE_HCI_LE_SUBEV_CONN_UPD_COMPLETE;
    evt.status = status;
    evt.conn_handle = htole16(2);
    evt.conn_itvl = htole16(params->itvl_max);
    evt.conn_latency = htole16(params->latency);
    evt.supervision_timeout = htole16(params->supervision_timeout);

    ble_gap_rx_update_complete(&evt);
}

static int
ble_gap_test_util_rx_param_req(struct ble_gap_upd_params *params, int pos,
                               int *cmd_idx, int cmd_fail_idx,
                               uint8_t fail_status)
{
    struct ble_hci_ev_le_subev_rem_conn_param_req evt;
    uint16_t opcode;
    uint8_t hci_status;

    evt.subev_code = BLE_HCI_LE_SUBEV_REM_CONN_PARM_REQ;
    evt.conn_handle = htole16(2);
    evt.min_interval = htole16(params->itvl_min);
    evt.max_interval = htole16(params->itvl_max);
    evt.latency = htole16(params->latency);
    evt.timeout = params->supervision_timeout;

    if (pos) {
        opcode = ble_hs_hci_util_opcode_join(
            BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_REM_CONN_PARAM_RR);
    } else {
        opcode = ble_hs_hci_util_opcode_join(
            BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_REM_CONN_PARAM_NRR);
    }
    if (*cmd_idx == cmd_fail_idx) {
        hci_status = fail_status;
    } else {
        hci_status = 0;
    }
    (*cmd_idx)++;

    ble_hs_test_util_hci_ack_set(opcode, hci_status);
    ble_gap_rx_param_req(&evt);

    return hci_status;
}

/*****************************************************************************
 * $white list                                                               *
 *****************************************************************************/

static void
ble_gap_test_util_wl_set(ble_addr_t *addrs, int addrs_count, int cmd_fail_idx,
                         uint8_t fail_status)
{
    int cmd_idx;
    int rc;
    int i;

    ble_gap_test_util_init();
    cmd_idx = 0;

    rc = ble_hs_test_util_wl_set(addrs, addrs_count, cmd_fail_idx,
                                 fail_status);
    TEST_ASSERT(rc == BLE_HS_HCI_ERR(fail_status));

    /* Verify tx of clear white list command. */
    ble_gap_test_util_verify_tx_clear_wl();
    if (cmd_idx >= cmd_fail_idx) {
        return;
    }
    cmd_idx++;

    /* Verify tx of add white list commands. */
    for (i = 0; i < addrs_count; i++) {
        ble_gap_test_util_verify_tx_add_wl(addrs + i);
        if (cmd_idx >= cmd_fail_idx) {
            return;
        }
        cmd_idx++;
    }
}

TEST_CASE_SELF(ble_gap_test_case_wl_bad_args)
{
    int rc;

    ble_gap_test_util_init();

    /*** 0 white list entries. */
    rc = ble_hs_test_util_wl_set(NULL, 0, 0, 0);
    TEST_ASSERT(rc == BLE_HS_EINVAL);

    /*** Invalid address type. */
    rc = ble_hs_test_util_wl_set(
        ((ble_addr_t[]) { {
            5, { 1, 2, 3, 4, 5, 6 }
        }, }),
        1, 0, 0);
    TEST_ASSERT(rc == BLE_HS_EINVAL);

    /*** White-list-using connection in progress. */
    rc = ble_hs_test_util_connect(BLE_OWN_ADDR_PUBLIC, NULL, 0, NULL,
                                  ble_gap_test_util_connect_cb, NULL, 0);
    TEST_ASSERT(rc == 0);

    rc = ble_hs_test_util_wl_set(
        ((ble_addr_t[]) { {
            BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 }
        }, }),
        1, 0, 0);
    TEST_ASSERT(rc == BLE_HS_EBUSY);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_wl_ctlr_fail)
{
    int i;

    ble_addr_t addrs[] = {
        { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 } },
        { BLE_ADDR_PUBLIC, { 2, 3, 4, 5, 6, 7 } },
        { BLE_ADDR_PUBLIC, { 3, 4, 5, 6, 7, 8 } },
        { BLE_ADDR_PUBLIC, { 4, 5, 6, 7, 8, 9 } },
    };
    int addrs_count = sizeof addrs / sizeof addrs[0];

    for (i = 0; i < 5; i++) {
        ble_gap_test_util_wl_set(addrs, addrs_count, i,
                                 BLE_ERR_UNSPECIFIED);
    }

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_wl_good)
{
    ble_addr_t addrs[] = {
        { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 } },
        { BLE_ADDR_PUBLIC, { 2, 3, 4, 5, 6, 7 } },
        { BLE_ADDR_PUBLIC, { 3, 4, 5, 6, 7, 8 } },
        { BLE_ADDR_PUBLIC, { 4, 5, 6, 7, 8, 9 } },
    };
    int addrs_count = sizeof addrs / sizeof addrs[0];

    ble_gap_test_util_wl_set(addrs, addrs_count, 0, 0);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_SUITE(ble_gap_test_suite_wl)
{
    ble_gap_test_case_wl_good();
    ble_gap_test_case_wl_bad_args();
    ble_gap_test_case_wl_ctlr_fail();
}

/*****************************************************************************
 * $discovery                                                                *
 *****************************************************************************/

static int
ble_gap_test_util_disc(uint8_t own_addr_type,
                       const struct ble_gap_disc_params *disc_params,
                       struct ble_gap_disc_desc *desc, int cmd_fail_idx,
                       uint8_t fail_status)
{
    int rc;

    ble_gap_test_util_init();

    TEST_ASSERT(!ble_gap_disc_active());

    /* Begin the discovery procedure. */
    rc = ble_hs_test_util_disc(own_addr_type, BLE_HS_FOREVER, disc_params,
                               ble_gap_test_util_disc_cb, NULL, cmd_fail_idx,
                               fail_status);
    TEST_ASSERT(rc == BLE_HS_HCI_ERR(fail_status));
    if (rc == 0) {
        TEST_ASSERT(ble_gap_master_in_progress());
        ble_gap_rx_adv_report(desc);
    } else {
        TEST_ASSERT(ble_gap_test_disc_event_type == -1);
    }

    if (cmd_fail_idx > 0) {
        /* Verify tx of set scan parameters command. */
        ble_gap_test_util_verify_tx_set_scan_params(
            own_addr_type,
            disc_params->passive ?
                BLE_HCI_SCAN_TYPE_PASSIVE :
                BLE_HCI_SCAN_TYPE_ACTIVE,
            disc_params->itvl,
            disc_params->window,
            disc_params->filter_policy);
    }

    if (cmd_fail_idx > 1) {
        /* Verify tx of scan enable command. */
        ble_gap_test_util_verify_tx_scan_enable(
            1, disc_params->filter_duplicates);
    }

    if (rc == 0) {
        TEST_ASSERT(ble_gap_disc_active());
    }

    return rc;
}

TEST_CASE_SELF(ble_gap_test_case_disc_bad_args)
{
    struct ble_gap_disc_params params;
    int rc;

    params.itvl = 0;
    params.window = 0;
    params.filter_policy = BLE_HCI_SCAN_FILT_NO_WL;
    params.limited = 0;
    params.passive = 0;
    params.filter_duplicates = 0;

    ble_gap_test_util_init();

    /*** Invalid filter policy. */
    params.filter_policy = 6;
    rc = ble_gap_disc(BLE_OWN_ADDR_PUBLIC, 0, &params,
                      ble_gap_test_util_disc_cb, NULL);
    TEST_ASSERT(rc == BLE_HS_EINVAL);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_disc_good)
{
    uint8_t adv_data[32];
    uint8_t flags;
    uint8_t own_addr_type;
    int passive;
    int limited;
    int rc;

    struct ble_gap_disc_desc desc = {
        .event_type = BLE_HCI_ADV_TYPE_ADV_IND,
        .addr = { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 } },
        .length_data = 0,
        .rssi = 0,
        .data = adv_data,
    };
    struct ble_gap_disc_params disc_params = {
        .itvl = BLE_GAP_SCAN_SLOW_INTERVAL1,
        .window = BLE_GAP_SCAN_SLOW_WINDOW1,
        .filter_policy = BLE_HCI_CONN_FILT_NO_WL,
        .limited = 0,
        .passive = 0,
        .filter_duplicates = 0,
    };

    flags = BLE_HS_ADV_F_DISC_LTD;
    rc = ble_hs_adv_set_flat(BLE_HS_ADV_TYPE_FLAGS, 1, &flags,
                             adv_data, &desc.length_data,
                             sizeof adv_data);
    TEST_ASSERT_FATAL(rc == 0);

    for (own_addr_type = 0;
         own_addr_type <= BLE_OWN_ADDR_RPA_RANDOM_DEFAULT;
         own_addr_type++)
    for (passive = 0; passive <= 1; passive++)
    for (limited = 0; limited <= 1; limited++) {
        disc_params.passive = passive;
        disc_params.limited = limited;
        ble_gap_test_util_disc(own_addr_type, &disc_params, &desc, -1, 0);

        TEST_ASSERT(ble_gap_master_in_progress());
        TEST_ASSERT(ble_gap_test_disc_event_type == BLE_GAP_EVENT_DISC);
        TEST_ASSERT(ble_gap_test_disc_desc.event_type ==
                    BLE_HCI_ADV_TYPE_ADV_IND);
        TEST_ASSERT(ble_gap_test_disc_desc.addr.type ==
                    BLE_ADDR_PUBLIC);
        TEST_ASSERT(ble_gap_test_disc_desc.length_data == 3);
        TEST_ASSERT(ble_gap_test_disc_desc.rssi == 0);
        TEST_ASSERT(memcmp(ble_gap_test_disc_desc.addr.val, desc.addr.val,
                    6) == 0);
        TEST_ASSERT(ble_gap_test_disc_arg == NULL);

    }

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_disc_ltd_mismatch)
{
    int rc;
    struct ble_gap_disc_desc desc_gen = {
        .event_type = BLE_HCI_ADV_TYPE_ADV_IND,
        .length_data = 3,
        .rssi = 0,
        .addr = { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 } },
        .data = (uint8_t[BLE_HS_ADV_MAX_SZ]){
            2,
            BLE_HS_ADV_TYPE_FLAGS,
            BLE_HS_ADV_F_DISC_GEN,
        },
    };

    struct ble_gap_disc_desc desc_lim = {
        .event_type = BLE_HCI_ADV_TYPE_ADV_IND,
        .length_data = 3,
        .rssi = 0,
        .addr = { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 } },
        .data = (uint8_t[BLE_HS_ADV_MAX_SZ]){
            2,
            BLE_HS_ADV_TYPE_FLAGS,
            BLE_HS_ADV_F_DISC_LTD,
        },
    };

    struct ble_gap_disc_params disc_params = {
        .itvl = BLE_GAP_SCAN_SLOW_INTERVAL1,
        .window = BLE_GAP_SCAN_SLOW_WINDOW1,
        .filter_policy = BLE_HCI_CONN_FILT_NO_WL,
        .limited = 1,
        .passive = 0,
        .filter_duplicates = 0,
    };

    rc = ble_gap_test_util_disc(BLE_OWN_ADDR_PUBLIC, &disc_params, &desc_gen,
                                -1, 0);
    TEST_ASSERT(rc == 0);
    TEST_ASSERT(ble_gap_master_in_progress());

    /* Verify that the report was ignored because of a mismatched LTD flag. */
    TEST_ASSERT(ble_gap_test_disc_event_type == -1);

    /* Stop the scan and swap the flags. */
    rc = ble_hs_test_util_disc_cancel(0);
    TEST_ASSERT(rc == 0);

    disc_params.limited = 0;
    rc = ble_gap_test_util_disc(BLE_OWN_ADDR_PUBLIC, &disc_params, &desc_lim,
                                -1, 0);
    TEST_ASSERT(rc == 0);
    TEST_ASSERT(ble_gap_master_in_progress());

    /* This time we should have reported the advertisement; general discovery
     * hears everything.
     */
    TEST_ASSERT(ble_gap_test_disc_event_type == BLE_GAP_EVENT_DISC);

}

TEST_CASE_SELF(ble_gap_test_case_disc_hci_fail)
{
    int fail_idx;
    int limited;
    int rc;

    struct ble_gap_disc_desc desc = {
        .event_type = BLE_HCI_ADV_TYPE_ADV_IND,
        .length_data = 0,
        .rssi = 0,
        .addr = { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 } },
        .data = NULL,
    };
    struct ble_gap_disc_params disc_params = {
        .itvl = BLE_GAP_SCAN_SLOW_INTERVAL1,
        .window = BLE_GAP_SCAN_SLOW_WINDOW1,
        .filter_policy = BLE_HCI_CONN_FILT_NO_WL,
        .limited = 0,
        .passive = 0,
        .filter_duplicates = 0,
    };

    for (limited = 0; limited <= 1; limited++) {
        disc_params.limited = limited;

        for (fail_idx = 0; fail_idx < 2; fail_idx++) {
            rc = ble_gap_test_util_disc(BLE_OWN_ADDR_PUBLIC, &disc_params,
                                        &desc, fail_idx, BLE_ERR_UNSUPPORTED);
            TEST_ASSERT(rc == BLE_HS_HCI_ERR(BLE_ERR_UNSUPPORTED));
            TEST_ASSERT(!ble_gap_master_in_progress());
        }
    }

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

static void
ble_gap_test_util_disc_dflts_once(int limited)
{
    struct ble_gap_disc_params params;
    uint16_t exp_window;
    uint16_t exp_itvl;
    int rc;

    ble_gap_test_util_init();

    memset(&params, 0, sizeof params);
    params.limited = limited;

    rc = ble_hs_test_util_disc(BLE_OWN_ADDR_PUBLIC, 0, &params,
                               ble_gap_test_util_disc_cb, NULL, -1, 0);
    TEST_ASSERT_FATAL(rc == 0);

    if (limited) {
        exp_itvl = BLE_GAP_LIM_DISC_SCAN_INT;
        exp_window = BLE_GAP_LIM_DISC_SCAN_WINDOW;
    } else {
        exp_itvl = BLE_GAP_SCAN_FAST_INTERVAL_MIN;
        exp_window = BLE_GAP_SCAN_FAST_WINDOW;
    }
    ble_gap_test_util_verify_tx_set_scan_params(
        BLE_OWN_ADDR_PUBLIC,
        BLE_HCI_SCAN_TYPE_ACTIVE,
        exp_itvl,
        exp_window,
        BLE_HCI_SCAN_FILT_NO_WL);

    ble_gap_test_util_verify_tx_scan_enable(1, 0);
}

TEST_CASE_SELF(ble_gap_test_case_disc_dflts)
{
    ble_gap_test_util_disc_dflts_once(0);
    ble_gap_test_util_disc_dflts_once(1);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_disc_already)
{
    static const struct ble_gap_disc_params disc_params = { 0 };
    int rc;

    ble_gap_test_util_init();

    /* Start a discovery procedure. */
    rc = ble_hs_test_util_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER,
                               &disc_params, ble_gap_test_util_disc_cb,
                               NULL, -1, 0);
    TEST_ASSERT_FATAL(rc == 0);

    /* Ensure host indicates BLE_HS_EALREADY if we try to discover. */
    rc = ble_gap_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER, &disc_params,
                               ble_gap_test_util_disc_cb, NULL);
    TEST_ASSERT(rc == BLE_HS_EALREADY);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_disc_busy)
{
    static const struct ble_gap_disc_params disc_params = { 0 };
    static const ble_addr_t peer_addr = {
        BLE_ADDR_PUBLIC,
        { 1, 2, 3, 4, 5, 6 }
    };
    int rc;

    ble_gap_test_util_init();

    /* Start a connect procedure. */
    rc = ble_hs_test_util_connect(BLE_OWN_ADDR_PUBLIC, &peer_addr, 0, NULL,
                                  ble_gap_test_util_connect_cb, NULL, 0);
    TEST_ASSERT_FATAL(rc == 0);

    /* Ensure host indicates BLE_HS_EBUSY if we try to discover. */
    rc = ble_gap_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER, &disc_params,
                               ble_gap_test_util_disc_cb, NULL);
    TEST_ASSERT(rc == BLE_HS_EBUSY);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_SUITE(ble_gap_test_suite_disc)
{
    ble_gap_test_case_disc_bad_args();
    ble_gap_test_case_disc_good();
    ble_gap_test_case_disc_ltd_mismatch();
    ble_gap_test_case_disc_hci_fail();
    ble_gap_test_case_disc_dflts();
    ble_gap_test_case_disc_already();
    ble_gap_test_case_disc_busy();
}

/*****************************************************************************
 * $direct connect                                                           *
 *****************************************************************************/

TEST_CASE_SELF(ble_gap_test_case_conn_gen_good)
{
    struct ble_gap_conn_complete evt;
    struct ble_gap_conn_params params;
    int rc;

    ble_addr_t peer_addr = { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 }};

    ble_gap_test_util_init();

    TEST_ASSERT(!ble_gap_master_in_progress());
    TEST_ASSERT(!ble_gap_conn_active());

    params.scan_itvl = 0x12;
    params.scan_window = 0x11;
    params.itvl_min = 25;
    params.itvl_max = 26;
    params.latency = 1;
    params.supervision_timeout = 20;
    params.min_ce_len = 3;
    params.max_ce_len = 4;

    rc = ble_hs_test_util_connect(BLE_OWN_ADDR_PUBLIC,
                                  &peer_addr, 0, &params,
                                  ble_gap_test_util_connect_cb, NULL, 0);
    TEST_ASSERT(rc == 0);

    TEST_ASSERT(ble_gap_master_in_progress());
    TEST_ASSERT(ble_gap_conn_active());

    TEST_ASSERT(ble_gap_master_in_progress());
    TEST_ASSERT(ble_hs_atomic_conn_flags(2, NULL) == BLE_HS_ENOTCONN);

    /* ble_gap_rx_conn_complete() will send extra HCI command, need phony
     * ack */
    ble_hs_test_util_hci_ack_set(ble_hs_hci_util_opcode_join(BLE_HCI_OGF_LE,
                             BLE_HCI_OCF_LE_RD_REM_FEAT), 0);

    /* Receive connection complete event. */
    memset(&evt, 0, sizeof evt);
    evt.status = BLE_ERR_SUCCESS;
    evt.connection_handle = 2;
    evt.role = BLE_HCI_LE_CONN_COMPLETE_ROLE_MASTER;
    memcpy(evt.peer_addr, peer_addr.val, 6);
    rc = ble_gap_rx_conn_complete(&evt, 0);
    TEST_ASSERT(rc == 0);

    TEST_ASSERT(!ble_gap_master_in_progress());

    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONNECT);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
    TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                       peer_addr.val, 6) == 0);

    TEST_ASSERT(ble_hs_atomic_conn_flags(2, NULL) == 0);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_gen_bad_args)
{
    int rc;

    ble_gap_test_util_init();

    TEST_ASSERT(!ble_gap_master_in_progress());

    /*** Invalid address type. */
    rc = ble_gap_connect(BLE_OWN_ADDR_PUBLIC,
                         &((ble_addr_t) { 5, { 1, 2, 3, 4, 5, 6 }}), 0, NULL,
                         ble_gap_test_util_connect_cb, NULL);
    TEST_ASSERT(rc == BLE_HS_EINVAL);
    TEST_ASSERT(!ble_gap_master_in_progress());

    /*** Connection already in progress. */
    rc = ble_hs_test_util_connect(
        BLE_OWN_ADDR_PUBLIC,
        &((ble_addr_t) { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 }}),
        0, NULL, ble_gap_test_util_connect_cb,
        NULL, 0);
    TEST_ASSERT(rc == 0);
    TEST_ASSERT(ble_gap_master_in_progress());

    rc = ble_gap_connect(
        BLE_OWN_ADDR_PUBLIC,
        &((ble_addr_t) { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 }}),
        0, NULL, ble_gap_test_util_connect_cb, NULL);
    TEST_ASSERT(rc == BLE_HS_EALREADY);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_gen_dflt_params)
{
    static const ble_addr_t peer_addr = {
        BLE_ADDR_PUBLIC,
        { 2, 3, 8, 6, 6, 1 }
    };
    int rc;

    ble_gap_test_util_init();

    rc = ble_hs_test_util_connect(BLE_OWN_ADDR_PUBLIC,
                                  &peer_addr, 0, NULL,
                                  ble_gap_test_util_connect_cb, NULL, 0);
    TEST_ASSERT(rc == 0);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_gen_already)
{
    static const struct ble_gap_conn_params conn_params = { 0 };
    static const ble_addr_t peer_addr = {
        BLE_ADDR_PUBLIC,
        { 1, 2, 3, 4, 5, 6 }
    };
    int rc;

    ble_gap_test_util_init();

    /* Start a connect procedure. */
    rc = ble_hs_test_util_connect(BLE_OWN_ADDR_PUBLIC, &peer_addr, 0, NULL,
                                  ble_gap_test_util_connect_cb, NULL, 0);
    TEST_ASSERT_FATAL(rc == 0);

    /* Ensure host indicates BLE_HS_EALREADY if we try to connect. */
    rc = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &peer_addr, BLE_HS_FOREVER,
                         &conn_params, ble_gap_test_util_connect_cb, NULL);
    TEST_ASSERT(rc == BLE_HS_EALREADY);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_gen_done)
{
    static const struct ble_gap_conn_params conn_params = { 0 };
    static const ble_addr_t peer_addr = {
        BLE_ADDR_PUBLIC,
        { 1, 2, 3, 4, 5, 6 }
    };
    int rc;

    ble_gap_test_util_init();

    /* Successfully connect to the peer. */
    ble_hs_test_util_create_conn(2, peer_addr.val,
                                 ble_gap_test_util_connect_cb, NULL);

    /* Ensure host indicates BLE_HS_EDONE if we try to connect to the same
     * peer.
     */
    rc = ble_gap_connect(BLE_OWN_ADDR_PUBLIC,  &peer_addr, BLE_HS_FOREVER,
                         &conn_params, ble_gap_test_util_connect_cb, NULL);
    TEST_ASSERT(rc == BLE_HS_EDONE);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_gen_busy)
{
    static const struct ble_gap_disc_params disc_params = { 0 };
    static const struct ble_gap_conn_params conn_params = { 0 };
    static const ble_addr_t peer_addr = {
        BLE_ADDR_PUBLIC,
        { 1, 2, 3, 4, 5, 6 }
    };
    int rc;

    ble_gap_test_util_init();

    /* Start a discovery procedure. */
    rc = ble_hs_test_util_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER,
                               &disc_params, ble_gap_test_util_disc_cb,
                               NULL, -1, 0);
    TEST_ASSERT_FATAL(rc == 0);

    /* Ensure host indicates BLE_HS_EBUSY if we try to connect. */
    rc = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &peer_addr, BLE_HS_FOREVER,
                         &conn_params, ble_gap_test_util_connect_cb, NULL);
    TEST_ASSERT(rc == BLE_HS_EBUSY);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_gen_fail_evt)
{
    static const ble_addr_t peer_addr = {BLE_ADDR_PUBLIC, {1, 2, 3, 4, 5, 6}};
    struct ble_gap_conn_complete evt;
    struct ble_hci_ev_disconn_cmp disc_evt;
    int rc;

    ble_gap_test_util_init();

    /* Start a connect procedure. */
    rc = ble_hs_test_util_connect(BLE_OWN_ADDR_PUBLIC, &peer_addr, 0, NULL,
                                  ble_gap_test_util_copy_cb, NULL, 0);
    TEST_ASSERT_FATAL(rc == 0);

    /* Controller indicates failure via connect complete event. */
    memset(&evt, 0, sizeof evt);
    evt.status = BLE_ERR_SUCCESS;
    evt.connection_handle = 6;
    evt.role = BLE_HCI_LE_CONN_COMPLETE_ROLE_MASTER;
    evt.peer_addr_type = BLE_ADDR_PUBLIC;
    memcpy(evt.peer_addr, peer_addr.val, 6);

    rc = ble_gap_rx_conn_complete(&evt, 0);
    TEST_ASSERT_FATAL(rc == 0);

    /* Ensure failed connect was reported to application. */
    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONNECT);
    TEST_ASSERT(ble_gap_test_event.connect.status ==
                BLE_HS_HCI_ERR(BLE_ERR_SUCCESS));

    memset(&disc_evt, 0, sizeof disc_evt);
    disc_evt.conn_handle = htole16(6);
    disc_evt.status = BLE_ERR_SUCCESS;
    disc_evt.reason = BLE_ERR_CONN_ESTABLISHMENT;

    ble_gap_rx_disconn_complete(&disc_evt);

    /* Ensure failed connect was reported to application. */
    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_DISCONNECT);
    TEST_ASSERT(ble_gap_test_event.disconnect.reason ==
                BLE_HS_HCI_ERR(BLE_ERR_CONN_ESTABLISHMENT));

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_SUITE(ble_gap_test_suite_conn_gen)
{
    ble_gap_test_case_conn_gen_good();
    ble_gap_test_case_conn_gen_bad_args();
    ble_gap_test_case_conn_gen_dflt_params();
    ble_gap_test_case_conn_gen_already();
    ble_gap_test_case_conn_gen_done();
    ble_gap_test_case_conn_gen_busy();
    ble_gap_test_case_conn_gen_fail_evt();
}

/*****************************************************************************
 * $cancel                                                                   *
 *****************************************************************************/

static void
ble_gap_test_util_conn_cancel(uint8_t hci_status)
{
    struct ble_gap_conn_complete evt;
    int rc;

    /* Initiate cancel procedure. */
    rc = ble_hs_test_util_conn_cancel(hci_status);
    TEST_ASSERT(rc == BLE_HS_HCI_ERR(hci_status));

    /* Verify tx of cancel create connection command. */
    ble_hs_test_util_hci_verify_tx_create_conn_cancel();
    if (rc != 0) {
        return;
    }
    TEST_ASSERT(ble_gap_master_in_progress());

    /* Receive connection complete event. */
    memset(&evt, 0, sizeof evt);
    evt.status = BLE_ERR_UNK_CONN_ID;
    /* test if host correctly ignores other fields if status is error */
    evt.connection_handle = 0x0fff;

    rc = ble_gap_rx_conn_complete(&evt, 0);
    TEST_ASSERT(rc == 0);
    TEST_ASSERT(!ble_gap_master_in_progress());

    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONNECT);
    TEST_ASSERT(ble_gap_test_event.connect.status == BLE_HS_EAPP);
}

static void
ble_gap_test_util_conn_and_cancel(uint8_t *peer_addr, uint8_t hci_status)
{
    ble_addr_t addr = { BLE_ADDR_PUBLIC };
    int rc;

    ble_gap_test_util_init();

    memcpy(addr.val, peer_addr, 6);

    /* Begin creating a connection. */
    rc = ble_hs_test_util_connect(BLE_OWN_ADDR_PUBLIC, &addr, 0, NULL,
                                  ble_gap_test_util_connect_cb, NULL, 0);
    TEST_ASSERT(rc == 0);
    TEST_ASSERT(ble_gap_master_in_progress());

    /* Initiate cancel procedure. */
    ble_gap_test_util_conn_cancel(hci_status);
    TEST_ASSERT(ble_hs_atomic_conn_flags(2, NULL) == BLE_HS_ENOTCONN);
}

TEST_CASE_SELF(ble_gap_test_case_conn_cancel_bad_args)
{
    int rc;

    ble_gap_test_util_init();

    /* Initiate cancel procedure with no connection in progress. */
    TEST_ASSERT(!ble_gap_master_in_progress());
    rc = ble_hs_test_util_conn_cancel(0);
    TEST_ASSERT(rc == BLE_HS_EALREADY);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_cancel_good)
{
    uint8_t peer_addr[6] = { 1, 2, 3, 4, 5, 6 };

    ble_gap_test_util_conn_and_cancel(peer_addr, 0);

    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONNECT);
    TEST_ASSERT(ble_gap_test_event.connect.status == BLE_HS_EAPP);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == BLE_HS_CONN_HANDLE_NONE);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_cancel_ctlr_fail)
{
    struct ble_gap_conn_complete evt;
    int rc;

    uint8_t peer_addr[6] = { 1, 2, 3, 4, 5, 6 };

    ble_gap_test_util_conn_and_cancel(peer_addr, BLE_ERR_REPEATED_ATTEMPTS);

    /* Make sure the host didn't invoke the application callback.  The cancel
     * failure was indicated via the return code from the gap call.
     */
    TEST_ASSERT(ble_gap_test_event.type == 0xff);

    /* ble_gap_rx_conn_complete() will send extra HCI command, need phony
     * ack
     */
    ble_hs_test_util_hci_ack_set(ble_hs_hci_util_opcode_join(BLE_HCI_OGF_LE,
                             BLE_HCI_OCF_LE_RD_REM_FEAT), 0);

    /* Allow connection complete to succeed. */
    memset(&evt, 0, sizeof evt);
    evt.status = BLE_ERR_SUCCESS;
    evt.connection_handle = 2;
    evt.role = BLE_HCI_LE_CONN_COMPLETE_ROLE_MASTER;
    memcpy(evt.peer_addr, peer_addr, 6);
    rc = ble_gap_rx_conn_complete(&evt, 0);
    TEST_ASSERT(rc == 0);

    TEST_ASSERT(!ble_gap_master_in_progress());

    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONNECT);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
    TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                       peer_addr, 6) == 0);

    TEST_ASSERT(ble_hs_atomic_conn_flags(2, NULL) == 0);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_SUITE(ble_gap_test_suite_conn_cancel)
{
    ble_gap_test_case_conn_cancel_good();
    ble_gap_test_case_conn_cancel_bad_args();
    ble_gap_test_case_conn_cancel_ctlr_fail();
}

/*****************************************************************************
 * $terminate                                                                *
 *****************************************************************************/

static void
ble_gap_test_util_terminate(uint8_t *peer_addr, uint8_t hci_status)
{
    struct ble_hci_ev_disconn_cmp evt;
    int rc;

    ble_gap_test_util_init();

    /* Create a connection. */
    ble_hs_test_util_create_conn(2, peer_addr, ble_gap_test_util_connect_cb,
                                 NULL);

    /* Reset the callback event code; we don't care about the successful
     * connection in this test.
     */
    ble_gap_test_event.type = -1;

    /* Terminate the connection. */
    rc = ble_hs_test_util_conn_terminate(2, hci_status);
    TEST_ASSERT(rc == BLE_HS_HCI_ERR(hci_status));
    TEST_ASSERT(!ble_gap_master_in_progress());

    /* Verify tx of disconnect command. */
    ble_gap_test_util_verify_tx_disconnect();

    if (hci_status == 0) {
        /* Receive disconnection complete event. */
        evt.conn_handle = htole16(2);
        evt.status = 0;
        evt.reason = BLE_ERR_CONN_TERM_LOCAL;
        ble_gap_rx_disconn_complete(&evt);
    }
}

TEST_CASE_SELF(ble_gap_test_case_conn_terminate_bad_args)
{
    int rc;

    ble_gap_test_util_init();

    /*** Nonexistent connection. */
    rc = ble_hs_test_util_conn_terminate(2, 0);
    TEST_ASSERT(rc == BLE_HS_ENOTCONN);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_terminate_good)
{
    uint8_t peer_addr[6] = { 1, 2, 3, 4, 5, 6 };

    ble_gap_test_util_terminate(peer_addr, 0);

    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_DISCONNECT);
    TEST_ASSERT(ble_gap_test_conn_status ==
                BLE_HS_HCI_ERR(BLE_ERR_CONN_TERM_LOCAL));
    TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
    TEST_ASSERT(ble_gap_test_conn_desc.peer_id_addr.type ==
                BLE_ADDR_PUBLIC);
    TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                       peer_addr, 6) == 0);
    TEST_ASSERT(ble_gap_test_conn_arg == NULL);

    TEST_ASSERT(ble_hs_atomic_conn_flags(2, NULL) == BLE_HS_ENOTCONN);
    TEST_ASSERT(!ble_gap_master_in_progress());

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_terminate_ctlr_fail)
{
    struct ble_hci_ev_disconn_cmp evt;
    int rc;

    uint8_t peer_addr[6] = { 1, 2, 3, 4, 5, 6 };

    ble_gap_test_util_init();

    /* Create a connection. */
    ble_hs_test_util_create_conn(2, peer_addr, ble_gap_test_util_connect_cb,
                                 NULL);

    /* Terminate the connection. */
    rc = ble_hs_test_util_conn_terminate(2, 0);
    TEST_ASSERT(rc == 0);
    TEST_ASSERT(!ble_gap_master_in_progress());

    /* Verify tx of disconnect command. */
    ble_gap_test_util_verify_tx_disconnect();

    /* Receive failed disconnection complete event. */
    evt.conn_handle = htole16(2);
    evt.status = BLE_ERR_UNSUPPORTED;
    evt.reason = 0;
    ble_gap_rx_disconn_complete(&evt);

    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_TERM_FAILURE);
    TEST_ASSERT(ble_gap_test_conn_status ==
                BLE_HS_HCI_ERR(BLE_ERR_UNSUPPORTED));
    TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
    TEST_ASSERT(ble_gap_test_conn_desc.peer_id_addr.type ==
                BLE_ADDR_PUBLIC);
    TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                       peer_addr, 6) == 0);
    TEST_ASSERT(ble_gap_test_conn_arg == NULL);

    TEST_ASSERT(ble_hs_atomic_conn_flags(2, NULL) == 0);
    TEST_ASSERT(!ble_gap_master_in_progress());

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_terminate_hci_fail)
{
    uint8_t peer_addr[6] = { 1, 2, 3, 4, 5, 6 };

    ble_gap_test_util_terminate(peer_addr, BLE_ERR_REPEATED_ATTEMPTS);

    TEST_ASSERT(ble_gap_test_event.type == 0xff);
    TEST_ASSERT(ble_hs_atomic_conn_flags(2, NULL) == 0);
    TEST_ASSERT(!ble_gap_master_in_progress());

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_SUITE(ble_gap_test_suite_conn_terminate)
{
    ble_gap_test_case_conn_terminate_bad_args();
    ble_gap_test_case_conn_terminate_good();
    ble_gap_test_case_conn_terminate_ctlr_fail();
    ble_gap_test_case_conn_terminate_hci_fail();
}

/*****************************************************************************
 * $conn find                                                                *
 *****************************************************************************/

TEST_CASE_SELF(ble_gap_test_case_conn_find)
{

    struct ble_gap_conn_desc desc;
    struct ble_hs_conn *conn;
    uint8_t pub_addr[6];
    int rc;

    /*** We are master; public addresses. */
    ble_gap_test_util_init();

    rc = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, pub_addr, NULL);
    TEST_ASSERT_FATAL(rc == 0);

    ble_hs_test_util_create_rpa_conn(8,
                                     BLE_OWN_ADDR_PUBLIC,
                                     ((uint8_t[6]){0,0,0,0,0,0}),
                                     BLE_ADDR_PUBLIC,
                                     ((uint8_t[6]){2,3,4,5,6,7}),
                                     ((uint8_t[6]){0,0,0,0,0,0}),
                                     BLE_HS_TEST_CONN_FEAT_ALL,
                                     ble_gap_test_util_connect_cb,
                                     NULL);

    rc = ble_gap_conn_find(8, &desc);
    TEST_ASSERT_FATAL(rc == 0);
    TEST_ASSERT(desc.conn_handle == 8);
    TEST_ASSERT(desc.our_id_addr.type == BLE_ADDR_PUBLIC);
    TEST_ASSERT(desc.our_ota_addr.type == BLE_ADDR_PUBLIC);
    TEST_ASSERT(desc.peer_ota_addr.type == BLE_ADDR_PUBLIC);
    TEST_ASSERT(desc.role == BLE_GAP_ROLE_MASTER);
    TEST_ASSERT(memcmp(desc.our_ota_addr.val, pub_addr, 6) == 0);
    TEST_ASSERT(memcmp(desc.our_id_addr.val, pub_addr, 6) == 0);
    TEST_ASSERT(memcmp(desc.peer_ota_addr.val,
                       ((uint8_t[6]){2,3,4,5,6,7}), 6) == 0);
    TEST_ASSERT(memcmp(desc.peer_id_addr.val,
                       ((uint8_t[6]){2,3,4,5,6,7}), 6) == 0);
    TEST_ASSERT(desc.conn_itvl == BLE_GAP_INITIAL_CONN_ITVL_MAX);
    TEST_ASSERT(desc.conn_latency == BLE_GAP_INITIAL_CONN_LATENCY);
    TEST_ASSERT(desc.supervision_timeout ==
                BLE_GAP_INITIAL_SUPERVISION_TIMEOUT);
    TEST_ASSERT(desc.master_clock_accuracy == 0);
    TEST_ASSERT(!desc.sec_state.encrypted);
    TEST_ASSERT(!desc.sec_state.authenticated);
    TEST_ASSERT(!desc.sec_state.bonded);

    /*** Swap roles. */
    ble_hs_lock();
    conn = ble_hs_conn_find(8);
    conn->bhc_flags &= ~BLE_HS_CONN_F_MASTER;
    ble_hs_unlock();

    rc = ble_gap_conn_find(8, &desc);
    TEST_ASSERT_FATAL(rc == 0);
    TEST_ASSERT(desc.role == BLE_GAP_ROLE_SLAVE);

    /*** We are master; RPAs. */
    ble_gap_test_util_init();

    rc = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, pub_addr, NULL);
    TEST_ASSERT_FATAL(rc == 0);

    ble_hs_test_util_create_rpa_conn(54,
                                     BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT,
                                     ((uint8_t[6]){0x40,1,2,3,4,5}),
                                     BLE_ADDR_RANDOM_ID,
                                     ((uint8_t[6]){3,4,5,6,7,8}),
                                     ((uint8_t[6]){0x50,1,2,3,4,5}),
                                     BLE_HS_TEST_CONN_FEAT_ALL,
                                     ble_gap_test_util_connect_cb,
                                     NULL);

    rc = ble_gap_conn_find(54, &desc);
    TEST_ASSERT_FATAL(rc == 0);
    TEST_ASSERT(desc.conn_handle == 54);
    TEST_ASSERT(desc.our_id_addr.type == BLE_ADDR_PUBLIC);
    TEST_ASSERT(desc.our_ota_addr.type == BLE_ADDR_RANDOM);
    TEST_ASSERT(desc.peer_ota_addr.type == BLE_ADDR_RANDOM);
    TEST_ASSERT(desc.role == BLE_GAP_ROLE_MASTER);
    TEST_ASSERT(memcmp(desc.our_ota_addr.val,
                       ((uint8_t[6]){0x40,1,2,3,4,5}), 6) == 0);
    TEST_ASSERT(memcmp(desc.our_id_addr.val, pub_addr, 6) == 0);
    TEST_ASSERT(memcmp(desc.peer_ota_addr.val,
                       ((uint8_t[6]){0x50,1,2,3,4,5}), 6) == 0);
    TEST_ASSERT(memcmp(desc.peer_id_addr.val,
                       ((uint8_t[6]){3,4,5,6,7,8}), 6) == 0);
    TEST_ASSERT(desc.conn_itvl == BLE_GAP_INITIAL_CONN_ITVL_MAX);
    TEST_ASSERT(desc.conn_latency == BLE_GAP_INITIAL_CONN_LATENCY);
    TEST_ASSERT(desc.supervision_timeout ==
                BLE_GAP_INITIAL_SUPERVISION_TIMEOUT);
    TEST_ASSERT(desc.master_clock_accuracy == 0);
    TEST_ASSERT(!desc.sec_state.encrypted);
    TEST_ASSERT(!desc.sec_state.authenticated);
    TEST_ASSERT(!desc.sec_state.bonded);

    /*** Swap roles. */
    ble_hs_lock();
    conn = ble_hs_conn_find(54);
    conn->bhc_flags &= ~BLE_HS_CONN_F_MASTER;
    ble_hs_unlock();

    rc = ble_gap_conn_find(54, &desc);
    TEST_ASSERT_FATAL(rc == 0);
    TEST_ASSERT(desc.role == BLE_GAP_ROLE_SLAVE);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_SUITE(ble_gap_test_suite_conn_find)
{
    ble_gap_test_case_conn_find();
}

/*****************************************************************************
 * $advertise                                                                *
 *****************************************************************************/

static void
ble_gap_test_util_adv(uint8_t own_addr_type,
                      const ble_addr_t *peer_addr, uint8_t conn_mode,
                      uint8_t disc_mode, int connect_status,
                      int cmd_fail_idx, uint8_t fail_status)
{
    struct ble_gap_conn_complete evt;
    struct ble_gap_adv_params adv_params;
    struct ble_hs_adv_fields adv_fields;
    uint8_t hci_status;
    int cmd_idx;
    int rc;

    ble_gap_test_util_init();

    adv_params = ble_hs_test_util_adv_params;
    adv_params.conn_mode = conn_mode;
    adv_params.disc_mode = disc_mode;

    TEST_ASSERT(!ble_gap_adv_active());

    cmd_idx = 0;

    memset(&adv_fields, 0, sizeof adv_fields);
    adv_fields.tx_pwr_lvl_is_present = 1;
    adv_fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;

    rc = ble_hs_test_util_adv_set_fields(&adv_fields, cmd_fail_idx,
                                         fail_status);
    if (cmd_fail_idx < 2) {
        hci_status = fail_status;
    } else {
        hci_status = 0;
    }
    TEST_ASSERT_FATAL(rc == BLE_HS_HCI_ERR(hci_status));
    cmd_idx += 2;

    if (rc == 0) {
        ble_gap_test_util_verify_tx_adv_data();
    }

    if (fail_status == 0 || cmd_fail_idx >= cmd_idx) {
        rc = ble_hs_test_util_adv_start(own_addr_type,
                                        peer_addr, &adv_params, BLE_HS_FOREVER,
                                        ble_gap_test_util_connect_cb, NULL,
                                        cmd_fail_idx - cmd_idx, fail_status);

        TEST_ASSERT(rc == BLE_HS_HCI_ERR(fail_status));
        cmd_idx++;
    }

    if (fail_status == 0 || cmd_fail_idx >= cmd_idx) {
        /* Verify tx of set advertising params command. */
        ble_gap_test_util_verify_tx_adv_params();
    }
    cmd_idx++;

    if (fail_status == 0 || cmd_fail_idx >= cmd_idx) {
        /* Verify tx of set advertise enable command. */
        ble_gap_test_util_verify_tx_adv_enable(1);
    }
    cmd_idx++;

    if (connect_status != -1 &&
        (fail_status == 0 || cmd_fail_idx >= cmd_idx)) {

        TEST_ASSERT(ble_gap_adv_active());

        /* Receive a connection complete event. */
        if (conn_mode != BLE_GAP_CONN_MODE_NON) {
            if (connect_status == BLE_ERR_SUCCESS) {
                /*
                 * ble_gap_rx_conn_complete() will send extra HCI command, need
                 * phony ack
                 */
                ble_hs_test_util_hci_ack_set(
                    ble_hs_hci_util_opcode_join(BLE_HCI_OGF_LE,
                                                BLE_HCI_OCF_LE_RD_REM_FEAT),
                    0);
            }

            memset(&evt, 0, sizeof evt);
            evt.status = connect_status;

            if (connect_status == BLE_ERR_SUCCESS) {
                evt.connection_handle = 2;
                evt.role = BLE_HCI_LE_CONN_COMPLETE_ROLE_SLAVE;
                memcpy(evt.peer_addr, peer_addr->val, 6);
            } else {
                /* test if host correctly ignores other fields if status is
                 * error
                 */
                evt.connection_handle = 0x0fff;
            }

            rc = ble_gap_rx_conn_complete(&evt, 0);
            TEST_ASSERT(rc == 0);

            if (connect_status == 0 ||
                connect_status == BLE_ERR_DIR_ADV_TMO) {

                TEST_ASSERT(!ble_gap_adv_active());
            } else {
                TEST_ASSERT(ble_gap_adv_active());
            }
        }
    }
}

TEST_CASE_SELF(ble_gap_test_case_adv_bad_args)
{
    struct ble_gap_adv_params adv_params;
    ble_addr_t peer_addr = { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 }};
    ble_addr_t peer_addr_inv = { 12, { 1, 2, 3, 4, 5, 6 }};
    int rc;

    ble_gap_test_util_init();

    TEST_ASSERT(!ble_gap_adv_active());

    /*** Invalid discoverable mode. */
    adv_params = ble_hs_test_util_adv_params;
    adv_params.disc_mode = 43;
    rc = ble_hs_test_util_adv_start(BLE_OWN_ADDR_PUBLIC,
                                    &peer_addr, &adv_params, BLE_HS_FOREVER,
                                    ble_gap_test_util_connect_cb, NULL, 0, 0);
    TEST_ASSERT(rc == BLE_HS_EINVAL);
    TEST_ASSERT(!ble_gap_adv_active());

    /*** Invalid connectable mode. */
    adv_params = ble_hs_test_util_adv_params;
    adv_params.conn_mode = 27;
    rc = ble_hs_test_util_adv_start(BLE_OWN_ADDR_PUBLIC,
                                    &peer_addr, &adv_params, BLE_HS_FOREVER,
                                    ble_gap_test_util_connect_cb, NULL, 0, 0);
    TEST_ASSERT(rc == BLE_HS_EINVAL);
    TEST_ASSERT(!ble_gap_adv_active());

    /*** Invalid peer address type with directed advertisable mode. */
    adv_params = ble_hs_test_util_adv_params;
    adv_params.conn_mode = BLE_GAP_CONN_MODE_DIR;
    rc = ble_hs_test_util_adv_start(
        BLE_OWN_ADDR_PUBLIC,
        &peer_addr_inv, &adv_params, BLE_HS_FOREVER,
        ble_gap_test_util_connect_cb, NULL, 0, 0);
    TEST_ASSERT(rc == BLE_HS_EINVAL);
    TEST_ASSERT(!ble_gap_adv_active());

    /*** Advertising already in progress. */
    adv_params = ble_hs_test_util_adv_params;
    rc = ble_hs_test_util_adv_start(BLE_OWN_ADDR_PUBLIC,
                                    &peer_addr, &adv_params, BLE_HS_FOREVER,
                                    ble_gap_test_util_connect_cb, NULL, 0, 0);
    TEST_ASSERT(rc == 0);
    TEST_ASSERT(ble_gap_adv_active());

    rc = ble_hs_test_util_adv_start(BLE_OWN_ADDR_PUBLIC,
                                    &peer_addr, &adv_params, BLE_HS_FOREVER,
                                    ble_gap_test_util_connect_cb, NULL, 0, 0);
    TEST_ASSERT(rc == BLE_HS_EALREADY);
    TEST_ASSERT(ble_gap_adv_active());

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

static void
ble_gap_test_util_adv_verify_dflt_params(uint8_t own_addr_type,
                                         const ble_addr_t *peer_addr,
                                         uint8_t conn_mode,
                                         uint8_t disc_mode)
{
    struct ble_hci_le_set_adv_params_cp hci_cmd;
    struct ble_gap_adv_params adv_params;
    uint8_t *hci_buf;
    uint8_t hci_param_len;
    int rc;

    ble_gap_test_util_init();

    TEST_ASSERT(!ble_gap_adv_active());

    adv_params = ble_hs_test_util_adv_params;
    adv_params.conn_mode = conn_mode;
    adv_params.disc_mode = disc_mode;

    /* Let stack calculate all default parameters. */
    adv_params.itvl_min = 0;
    adv_params.itvl_max = 0;
    adv_params.channel_map = 0;
    adv_params.filter_policy = 0;
    adv_params.high_duty_cycle = 0;

    rc = ble_hs_test_util_adv_start(BLE_OWN_ADDR_PUBLIC, peer_addr,
                                    &adv_params, BLE_HS_FOREVER,
                                    ble_gap_test_util_connect_cb, NULL, 0, 0);
    TEST_ASSERT_FATAL(rc == 0);

    /* Ensure default parameters properly filled in. */
    hci_buf = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                             BLE_HCI_OCF_LE_SET_ADV_PARAMS,
                                             &hci_param_len);
    TEST_ASSERT_FATAL(hci_buf != NULL);
    TEST_ASSERT_FATAL(hci_param_len == BLE_HCI_SET_ADV_PARAM_LEN);

    hci_cmd.min_interval = get_le16(hci_buf + 0);
    hci_cmd.max_interval = get_le16(hci_buf + 2);
    hci_cmd.type = hci_buf[4];
    hci_cmd.own_addr_type = hci_buf[5];
    hci_cmd.peer_addr_type = hci_buf[6];
    memcpy(hci_cmd.peer_addr, hci_buf + 7, 6);
    hci_cmd.chan_map = hci_buf[13];
    hci_cmd.filter_policy = hci_buf[14];

    if (conn_mode == BLE_GAP_CONN_MODE_NON) {
        TEST_ASSERT(hci_cmd.min_interval == BLE_GAP_ADV_FAST_INTERVAL2_MIN);
        TEST_ASSERT(hci_cmd.max_interval == BLE_GAP_ADV_FAST_INTERVAL2_MAX);
    } else {
        TEST_ASSERT(hci_cmd.min_interval == BLE_GAP_ADV_FAST_INTERVAL1_MIN);
        TEST_ASSERT(hci_cmd.max_interval == BLE_GAP_ADV_FAST_INTERVAL1_MAX);
    }

    if (conn_mode == BLE_GAP_CONN_MODE_NON) {
        if (disc_mode == BLE_GAP_DISC_MODE_NON) {
            TEST_ASSERT(hci_cmd.type == BLE_HCI_ADV_TYPE_ADV_NONCONN_IND);
        } else {
            TEST_ASSERT(hci_cmd.type == BLE_HCI_ADV_TYPE_ADV_SCAN_IND);
        }
    } else if (conn_mode == BLE_GAP_CONN_MODE_UND) {
        TEST_ASSERT(hci_cmd.type == BLE_HCI_ADV_TYPE_ADV_IND);
    } else {
        TEST_ASSERT(hci_cmd.type == BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD);
    }
}

TEST_CASE_SELF(ble_gap_test_case_adv_dflt_params)
{
    ble_addr_t peer_addr = { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 }};

    int d;
    int c;

    for (c = BLE_GAP_CONN_MODE_NON; c < BLE_GAP_CONN_MODE_MAX; c++) {
        for (d = BLE_GAP_DISC_MODE_NON; d < BLE_GAP_DISC_MODE_MAX; d++) {
            ble_gap_test_util_adv_verify_dflt_params(
                BLE_OWN_ADDR_PUBLIC, &peer_addr, c, d);
        }
    }

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_adv_good)
{
    ble_addr_t peer_addr = { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 }};
    int d;
    int c;

    for (c = BLE_GAP_CONN_MODE_NON; c < BLE_GAP_CONN_MODE_MAX; c++) {
        for (d = BLE_GAP_DISC_MODE_NON; d < BLE_GAP_DISC_MODE_MAX; d++) {
            ble_gap_test_util_adv(BLE_OWN_ADDR_PUBLIC,
                                  &peer_addr, c, d, BLE_ERR_SUCCESS, -1, 0);

            if (c != BLE_GAP_CONN_MODE_NON) {
                TEST_ASSERT(!ble_gap_adv_active());
                TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONNECT);
                TEST_ASSERT(ble_gap_test_conn_status == 0);
                TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
                TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                                   peer_addr.val, 6) == 0);
                TEST_ASSERT(ble_gap_test_conn_arg == NULL);
            }
        }
    }

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_adv_ctlr_fail)
{
    ble_addr_t peer_addr = { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 }};
    int d;
    int c;

    for (c = BLE_GAP_CONN_MODE_NON + 1; c < BLE_GAP_CONN_MODE_MAX; c++) {
        for (d = BLE_GAP_DISC_MODE_NON; d < BLE_GAP_DISC_MODE_MAX; d++) {
            ble_gap_test_util_adv(BLE_OWN_ADDR_PUBLIC,
                                  &peer_addr, c, d, BLE_ERR_DIR_ADV_TMO,
                                  -1, 0);

            TEST_ASSERT(!ble_gap_adv_active());
            TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_ADV_COMPLETE);
            TEST_ASSERT(ble_gap_test_conn_desc.conn_handle ==
                        BLE_HS_CONN_HANDLE_NONE);
            TEST_ASSERT(ble_gap_test_conn_arg == NULL);
        }
    }

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_adv_hci_fail)
{
    ble_addr_t peer_addr = { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 }};
    int fail_idx;
    int d;
    int c;

    for (c = BLE_GAP_CONN_MODE_NON; c < BLE_GAP_CONN_MODE_MAX; c++) {
        for (d = BLE_GAP_DISC_MODE_NON; d < BLE_GAP_DISC_MODE_MAX; d++) {
            for (fail_idx = 0; fail_idx < 4; fail_idx++) {
                ble_gap_test_util_adv(BLE_OWN_ADDR_PUBLIC,
                                      &peer_addr,
                                      c, d, 0, fail_idx, BLE_ERR_UNSUPPORTED);

                TEST_ASSERT(!ble_gap_adv_active());
                TEST_ASSERT(ble_gap_test_event.type == 0xff);
            }
        }
    }

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_SUITE(ble_gap_test_suite_adv)
{
    ble_gap_test_case_adv_bad_args();
    ble_gap_test_case_adv_dflt_params();
    ble_gap_test_case_adv_good();
    ble_gap_test_case_adv_ctlr_fail();
    ble_gap_test_case_adv_hci_fail();
}

/*****************************************************************************
 * $stop advertise                                                           *
 *****************************************************************************/

static void
ble_gap_test_util_stop_adv(const ble_addr_t *peer_addr,
                           uint8_t conn_mode, uint8_t disc_mode,
                           int cmd_fail_idx, uint8_t fail_status)
{
    uint8_t hci_status;
    int rc;

    ble_gap_test_util_init();

    /* Start advertising; don't rx a successful connection event. */
    ble_gap_test_util_adv(BLE_OWN_ADDR_PUBLIC, peer_addr,
                          conn_mode, disc_mode, -1, -1, 0);

    TEST_ASSERT(ble_gap_adv_active());

    /* Stop advertising. */
    hci_status = cmd_fail_idx == 0 ? fail_status : 0;

    rc = ble_hs_test_util_adv_stop(hci_status);
    TEST_ASSERT(rc == BLE_HS_HCI_ERR(hci_status));

    /* Verify tx of advertising enable command. */
    ble_gap_test_util_verify_tx_adv_enable(0);
}

TEST_CASE_SELF(ble_gap_test_case_stop_adv_good)
{
    ble_addr_t peer_addr = { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 }};
    int d;
    int c;

    for (c = BLE_GAP_CONN_MODE_NON; c < BLE_GAP_CONN_MODE_MAX; c++) {
        for (d = BLE_GAP_DISC_MODE_NON; d < BLE_GAP_DISC_MODE_MAX; d++) {
            ble_gap_test_util_stop_adv(&peer_addr, c, d, -1, 0);
            TEST_ASSERT(!ble_gap_adv_active());
            TEST_ASSERT(ble_gap_test_event.type == 0xff);
            TEST_ASSERT(ble_gap_test_conn_status == -1);
            TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == (uint16_t)-1);
            TEST_ASSERT(ble_gap_test_conn_arg == (void *)-1);
        }
    }

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_stop_adv_hci_fail)
{
    ble_addr_t peer_addr = { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 }};
    int d;
    int c;

    for (c = BLE_GAP_CONN_MODE_NON; c < BLE_GAP_CONN_MODE_MAX; c++) {
        for (d = BLE_GAP_DISC_MODE_NON; d < BLE_GAP_DISC_MODE_MAX; d++) {
            ble_gap_test_util_stop_adv(&peer_addr, c, d,
                                       0, BLE_ERR_UNSUPPORTED);
            TEST_ASSERT(ble_gap_adv_active());
            TEST_ASSERT(ble_gap_test_event.type == 0xff);
            TEST_ASSERT(ble_gap_test_conn_status == -1);
            TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == (uint16_t)-1);
            TEST_ASSERT(ble_gap_test_conn_arg == (void *)-1);
        }
    }

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_SUITE(ble_gap_test_suite_stop_adv)
{
    ble_gap_test_case_stop_adv_good();
    ble_gap_test_case_stop_adv_hci_fail();
}

/*****************************************************************************
 * $update connection                                                        *
 *****************************************************************************/

static void
ble_gap_test_util_update_verify_params(struct ble_gap_upd_params *params,
                                      uint8_t ble_hs_err)
{
    int rc;

    uint8_t peer_addr[6] = { 1, 2, 3, 4, 5, 6 };

    ble_gap_test_util_init();

    ble_hs_test_util_create_conn(2, peer_addr, ble_gap_test_util_connect_cb,
                                 NULL);

    rc = ble_hs_test_util_conn_update(2, params, 0);
    TEST_ASSERT(rc == ble_hs_err);
}

static void
ble_gap_test_util_update_no_l2cap(struct ble_gap_upd_params *params,
                                  int master,
                                  uint8_t hci_status, int event_status)
{
    struct ble_hs_conn *conn;
    int rc;

    uint8_t peer_addr[6] = { 1, 2, 3, 4, 5, 6 };

    ble_gap_test_util_init();

    ble_hs_test_util_create_conn(2, peer_addr, ble_gap_test_util_connect_cb,
                                 NULL);

    if (!master) {
        ble_hs_lock();
        conn = ble_hs_conn_find(2);
        TEST_ASSERT_FATAL(conn != NULL);
        conn->bhc_flags &= ~BLE_HS_CONN_F_MASTER;
        ble_hs_unlock();
    }

    /* Erase callback info reported during connection establishment; we only
     * care about updates.
     */
    ble_gap_test_util_reset_cb_info();

    TEST_ASSERT(!ble_gap_master_in_progress());

    rc = ble_hs_test_util_conn_update(2, params, hci_status);
    TEST_ASSERT(rc == BLE_HS_HCI_ERR(hci_status));
    TEST_ASSERT(!ble_gap_master_in_progress());

    /* Verify tx of connection update command. */
    ble_gap_test_util_verify_tx_update_conn(params);

    if (rc == 0) {
        TEST_ASSERT(ble_gap_dbg_update_active(2));

        /* Attempt two duplicate updates; ensure BLE_HS_EALREADY gets returned
         * both times.  Make sure initial update still completes successfully
         * (MYNEWT-702).
         */
        rc = ble_hs_test_util_conn_update(2, params, 0);
        TEST_ASSERT(rc == BLE_HS_EALREADY);
        rc = ble_hs_test_util_conn_update(2, params, 0);
        TEST_ASSERT(rc == BLE_HS_EALREADY);

        /* Receive connection update complete event. */
        ble_gap_test_util_rx_update_complete(event_status, params);

        TEST_ASSERT(!ble_gap_master_in_progress());
        TEST_ASSERT(!ble_gap_dbg_update_active(2));

        TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONN_UPDATE);
        TEST_ASSERT(ble_gap_test_conn_status == BLE_HS_HCI_ERR(event_status));
        if (event_status == 0) {
            TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
            TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                               peer_addr, 6) == 0);
            TEST_ASSERT(ble_gap_test_conn_desc.conn_itvl == params->itvl_max);
            TEST_ASSERT(ble_gap_test_conn_desc.conn_latency ==
                        params->latency);
            TEST_ASSERT(ble_gap_test_conn_desc.supervision_timeout ==
                        params->supervision_timeout);
        }
    } else {
        TEST_ASSERT(!ble_gap_master_in_progress());
        TEST_ASSERT(!ble_gap_dbg_update_active(2));

        TEST_ASSERT(ble_gap_test_event.type == 0xff);
    }
}

static void
ble_gap_test_util_update_l2cap(struct ble_gap_upd_params *params,
                               uint16_t l2cap_result)
{
    struct ble_l2cap_sig_update_params l2cap_params;
    struct ble_hs_conn *conn;
    uint8_t id;
    int rc;

    uint8_t peer_addr[6] = { 1, 2, 3, 4, 5, 6 };

    ble_gap_test_util_init();

    ble_hs_test_util_create_conn_feat(2, peer_addr,
                                      BLE_HS_TEST_CONN_FEAT_NO_CONN_PARAM,
                                      ble_gap_test_util_connect_cb, NULL);

    ble_hs_lock();
    conn = ble_hs_conn_find(2);
    TEST_ASSERT_FATAL(conn != NULL);
    conn->bhc_flags &= ~BLE_HS_CONN_F_MASTER;
    ble_hs_unlock();

    /* Erase callback info reported during connection establishment; we only
     * care about updates.
     */
    ble_gap_test_util_reset_cb_info();

    rc = ble_hs_test_util_conn_update(2, params, 0xFF);
    TEST_ASSERT(rc == 0);

    TEST_ASSERT(ble_gap_dbg_update_active(2));

    l2cap_params.itvl_min = params->itvl_min;
    l2cap_params.itvl_max = params->itvl_max;
    l2cap_params.slave_latency = params->latency;
    l2cap_params.timeout_multiplier = params->supervision_timeout;
    id = ble_hs_test_util_verify_tx_l2cap_update_req(&l2cap_params);

    /* Receive l2cap connection parameter update response. */
    ble_hs_test_util_rx_l2cap_update_rsp(2, id, l2cap_result);
    TEST_ASSERT(!ble_gap_dbg_update_active(2));

    if (l2cap_result == BLE_L2CAP_SIG_UPDATE_RSP_RESULT_ACCEPT) {
        /* Receive connection update complete event. */
        ble_gap_test_util_rx_update_complete(0, params);
    }

    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONN_UPDATE);
    if (l2cap_result != BLE_L2CAP_SIG_UPDATE_RSP_RESULT_ACCEPT) {
        TEST_ASSERT(ble_gap_test_conn_status == BLE_HS_EREJECT);
    } else {
        TEST_ASSERT(ble_gap_test_conn_status == 0);
        TEST_ASSERT(ble_gap_test_conn_desc.conn_itvl == params->itvl_max);
        TEST_ASSERT(ble_gap_test_conn_desc.conn_latency == params->latency);
        TEST_ASSERT(ble_gap_test_conn_desc.supervision_timeout ==
                    params->supervision_timeout);
    }

    TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
    TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                       peer_addr, 6) == 0);
}

static void
ble_gap_test_util_update_no_l2cap_tmo(struct ble_gap_upd_params *params,
                                      int master)
{
    struct ble_hs_conn *conn;
    int rc;

    uint8_t peer_addr[6] = { 1, 2, 3, 4, 5, 6 };

    ble_gap_test_util_init();

    ble_hs_test_util_create_conn(2, peer_addr, ble_gap_test_util_connect_cb,
                                 NULL);

    if (!master) {
        ble_hs_lock();
        conn = ble_hs_conn_find(2);
        TEST_ASSERT_FATAL(conn != NULL);
        conn->bhc_flags &= ~BLE_HS_CONN_F_MASTER;
        ble_hs_unlock();
    }

    /* Erase callback info reported during connection establishment; we only
     * care about updates.
     */
    ble_gap_test_util_reset_cb_info();

    TEST_ASSERT(!ble_gap_master_in_progress());

    rc = ble_hs_test_util_conn_update(2, params, 0);
    TEST_ASSERT(rc == 0);
    TEST_ASSERT(!ble_gap_master_in_progress());

    /* Verify tx of connection update command. */
    ble_gap_test_util_verify_tx_update_conn(params);

    /* Ensure no update event reported. */
    TEST_ASSERT(ble_gap_test_event.type == 0xff);

    /* Advance 39 seconds; ensure no timeout reported. */
    os_time_advance(39 * OS_TICKS_PER_SEC);
    ble_gap_timer();
    TEST_ASSERT(ble_gap_test_event.type == 0xff);

    /* Advance 40th second; ensure timeout reported. */
    os_time_advance(1 * OS_TICKS_PER_SEC);

    /* Timeout will result in a terminate HCI command being sent; schedule ack
     * from controller.
     */
    ble_hs_test_util_hci_ack_set_disconnect(0);

    ble_gap_timer();

    /* Verify terminate was sent. */
    ble_gap_test_util_verify_tx_disconnect();

    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONN_UPDATE);
    TEST_ASSERT(ble_gap_test_conn_status == BLE_HS_ETIMEOUT);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
    TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                       peer_addr, 6) == 0);
}

static void
ble_gap_test_util_update_l2cap_tmo(struct ble_gap_upd_params *params,
                                   uint8_t hci_status, uint8_t event_status,
                                   int rx_l2cap)
{
    struct ble_l2cap_sig_update_params l2cap_params;
    struct ble_hs_conn *conn;
    uint8_t id;
    int rc;

    uint8_t peer_addr[6] = { 1, 2, 3, 4, 5, 6 };

    ble_gap_test_util_init();

    ble_hs_test_util_create_conn_feat(2, peer_addr,
                                      BLE_HS_TEST_CONN_FEAT_NO_CONN_PARAM,
                                      ble_gap_test_util_connect_cb, NULL);

    ble_hs_lock();
    conn = ble_hs_conn_find(2);
    TEST_ASSERT_FATAL(conn != NULL);
    conn->bhc_flags &= ~BLE_HS_CONN_F_MASTER;
    ble_hs_unlock();

    /* Erase callback info reported during connection establishment; we only
     * care about updates.
     */
    ble_gap_test_util_reset_cb_info();

    rc = ble_hs_test_util_conn_update(2, params, 0xFF);
    TEST_ASSERT(rc == 0);

    TEST_ASSERT(ble_gap_dbg_update_active(2));

    if (rx_l2cap) {
        l2cap_params.itvl_min = params->itvl_min;
        l2cap_params.itvl_max = params->itvl_max;
        l2cap_params.slave_latency = params->latency;
        l2cap_params.timeout_multiplier = params->supervision_timeout;
        id = ble_hs_test_util_verify_tx_l2cap_update_req(&l2cap_params);

        /* Receive l2cap connection parameter update response. */
        ble_hs_test_util_rx_l2cap_update_rsp(
            2, id, BLE_L2CAP_SIG_UPDATE_RSP_RESULT_ACCEPT);

        TEST_ASSERT(!ble_gap_dbg_update_active(2));
    } else {
        TEST_ASSERT(ble_gap_dbg_update_active(2));
    }

    /* Ensure no update event reported. */
    TEST_ASSERT(ble_gap_test_event.type == 0xff);

    /* Advance 29 seconds; ensure no timeout reported.
     * Note: L2CAP signaling timeout is 30 sec, GAP update timeout is 40 sec
     */
    os_time_advance(29 * OS_TICKS_PER_SEC);
    ble_gap_timer();
    ble_l2cap_sig_timer();
    TEST_ASSERT(ble_gap_test_event.type == 0xff);

    /* Advance 30th second; ensure timeout reported. */
    os_time_advance(1 * OS_TICKS_PER_SEC);

    /* If L2CAP response has been received, GAP Timer is removed */
     if (!rx_l2cap) {

         /* Timeout will result in a terminate HCI command being sent; schedule ack
          * from controller.
          */
         ble_hs_test_util_hci_ack_set_disconnect(0);

         ble_gap_timer();
         ble_l2cap_sig_timer();

         /* Verify terminate was sent. */
         ble_gap_test_util_verify_tx_disconnect();

        TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONN_UPDATE);
        TEST_ASSERT(ble_gap_test_conn_status == BLE_HS_ETIMEOUT);
        TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
        TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                           peer_addr, 6) == 0);
    } else {
        ble_gap_timer();
        ble_l2cap_sig_timer();

        TEST_ASSERT(ble_gap_test_event.type == 0xff);
    }
}

static void
ble_gap_test_util_update_peer(uint8_t status,
                              struct ble_gap_upd_params *params)
{
    uint8_t peer_addr[6] = { 1, 2, 3, 4, 5, 6 };

    ble_gap_test_util_init();

    ble_hs_test_util_create_conn(2, peer_addr, ble_gap_test_util_connect_cb,
                                 NULL);

    TEST_ASSERT(!ble_gap_master_in_progress());

    /* Receive connection update complete event. */
    ble_gap_test_util_rx_update_complete(status, params);

    TEST_ASSERT(!ble_gap_master_in_progress());

    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONN_UPDATE);
    TEST_ASSERT(ble_gap_test_conn_status == BLE_HS_HCI_ERR(status));
    TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
    TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                       peer_addr, 6) == 0);

    if (status == 0) {
        TEST_ASSERT(ble_gap_test_conn_desc.conn_itvl == params->itvl_max);
        TEST_ASSERT(ble_gap_test_conn_desc.conn_latency == params->latency);
        TEST_ASSERT(ble_gap_test_conn_desc.supervision_timeout ==
                    params->supervision_timeout);
    }

    TEST_ASSERT(!ble_gap_dbg_update_active(2));
}

static void
ble_gap_test_util_update_req_pos(struct ble_gap_upd_params *peer_params,
                                 struct ble_gap_upd_params *self_params,
                                 int cmd_fail_idx, uint8_t hci_status)
{
    int cmd_idx;
    int rc;

    uint8_t peer_addr[6] = { 1, 2, 3, 4, 5, 6 };

    ble_gap_test_util_init();
    cmd_idx = 0;

    ble_hs_test_util_create_conn(2, peer_addr, ble_gap_test_util_connect_cb,
                                 NULL);

    TEST_ASSERT(!ble_gap_master_in_progress());

    ble_gap_test_conn_self_params = *self_params;
    rc = ble_gap_test_util_rx_param_req(peer_params, 1, &cmd_idx, cmd_fail_idx,
                                        hci_status);
    if (rc != 0) {
        goto hci_fail;
    }
    TEST_ASSERT(!ble_gap_master_in_progress());

    /* We don't maintain an update entry when the peer initiates. */
    TEST_ASSERT(!ble_gap_dbg_update_active(2));

    /* Verify tx of connection parameters reply command. */
    ble_gap_test_util_verify_tx_params_reply_pos();

    TEST_ASSERT(!ble_gap_master_in_progress());
    TEST_ASSERT(!ble_gap_dbg_update_active(2));

    /* Receive connection update complete event. */
    ble_gap_test_util_rx_update_complete(0, self_params);

    TEST_ASSERT(!ble_gap_master_in_progress());
    TEST_ASSERT(!ble_gap_dbg_update_active(2));

    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONN_UPDATE);
    TEST_ASSERT(ble_gap_test_conn_status == 0);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
    TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                       peer_addr, 6) == 0);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_itvl == self_params->itvl_max);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_latency == self_params->latency);
    TEST_ASSERT(ble_gap_test_conn_desc.supervision_timeout ==
                self_params->supervision_timeout);

    return;

hci_fail:
    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONN_UPDATE);
    TEST_ASSERT(ble_gap_test_conn_status == BLE_HS_HCI_ERR(hci_status));
    TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
    TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                       peer_addr, 6) == 0);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_itvl ==
                BLE_GAP_INITIAL_CONN_ITVL_MAX);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_latency ==
                BLE_GAP_INITIAL_CONN_LATENCY);
    TEST_ASSERT(ble_gap_test_conn_desc.supervision_timeout ==
                BLE_GAP_INITIAL_SUPERVISION_TIMEOUT);
}

static void
ble_gap_test_util_update_req_neg(struct ble_gap_upd_params *peer_params,
                                 int cmd_fail_idx, uint8_t hci_status)
{
    int cmd_idx;
    int reason;
    int rc;

    uint8_t peer_addr[6] = { 1, 2, 3, 4, 5, 6 };

    ble_gap_test_util_init();
    cmd_idx = 0;

    reason = BLE_ERR_UNSPECIFIED;
    ble_hs_test_util_create_conn(2, peer_addr, ble_gap_test_util_connect_cb,
                                 &reason);

    TEST_ASSERT(!ble_gap_master_in_progress());
    TEST_ASSERT(!ble_gap_dbg_update_active(2));

    rc = ble_gap_test_util_rx_param_req(peer_params, 0, &cmd_idx, cmd_fail_idx,
                                        hci_status);
    if (rc != 0) {
        goto hci_fail;
    }
    TEST_ASSERT(!ble_gap_master_in_progress());
    TEST_ASSERT(!ble_gap_dbg_update_active(2));

    /* Verify tx of connection parameters negative reply command. */
    ble_gap_test_util_verify_tx_params_reply_neg(reason);

    TEST_ASSERT(!ble_gap_master_in_progress());
    TEST_ASSERT(!ble_gap_dbg_update_active(2));

    return;

hci_fail:
    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONN_UPDATE);
    TEST_ASSERT(ble_gap_test_conn_status == BLE_HS_HCI_ERR(hci_status));
    TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
    TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                       peer_addr, 6) == 0);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_itvl ==
                BLE_GAP_INITIAL_CONN_ITVL_MAX);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_latency ==
                BLE_GAP_INITIAL_CONN_LATENCY);
    TEST_ASSERT(ble_gap_test_conn_desc.supervision_timeout ==
                BLE_GAP_INITIAL_SUPERVISION_TIMEOUT);
}

static void
ble_gap_test_util_update_req_concurrent(
    struct ble_gap_upd_params *init_params,
    struct ble_gap_upd_params *peer_params,
    struct ble_gap_upd_params *self_params,
    int cmd_fail_idx,
    uint8_t fail_status)
{
    uint8_t hci_status;
    int cmd_idx;
    int rc;

    uint8_t peer_addr[6] = { 1, 2, 3, 4, 5, 6 };

    ble_gap_test_util_init();

    ble_hs_test_util_create_conn(2, peer_addr, ble_gap_test_util_connect_cb,
                                 NULL);

    TEST_ASSERT(!ble_gap_master_in_progress());
    TEST_ASSERT(!ble_gap_dbg_update_active(2));

    hci_status = cmd_fail_idx == 0 ? fail_status : 0;
    rc = ble_hs_test_util_conn_update(2, init_params, hci_status);
    TEST_ASSERT(rc == BLE_HS_HCI_ERR(hci_status));

    TEST_ASSERT(!ble_gap_master_in_progress());

    /* Verify tx of connection update command. */
    ble_gap_test_util_verify_tx_update_conn(init_params);

    if (rc == 0) {
        TEST_ASSERT(ble_gap_dbg_update_active(2));
    } else {
        TEST_ASSERT(!ble_gap_dbg_update_active(2));
        return;
    }

    TEST_ASSERT(!ble_gap_master_in_progress());
    TEST_ASSERT(ble_gap_dbg_update_active(2));

    /* Receive connection parameter update request from peer. */
    ble_gap_test_conn_self_params = *self_params;
    rc = ble_gap_test_util_rx_param_req(peer_params, 1, &cmd_idx, cmd_fail_idx,
                                        hci_status);
    if (rc != 0) {
        goto hci_fail;
    }
    TEST_ASSERT(!ble_gap_master_in_progress());
    TEST_ASSERT(ble_gap_dbg_update_active(2));

    /* Verify tx of connection parameters reply command. */
    ble_gap_test_util_verify_tx_params_reply_pos();

    TEST_ASSERT(!ble_gap_master_in_progress());
    TEST_ASSERT(ble_gap_dbg_update_active(2));

    /* Receive connection update complete event. */
    ble_gap_test_util_rx_update_complete(0, self_params);

    TEST_ASSERT(!ble_gap_master_in_progress());
    TEST_ASSERT(!ble_gap_dbg_update_active(2));

    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONN_UPDATE);
    TEST_ASSERT(ble_gap_test_conn_status == 0);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
    TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                       peer_addr, 6) == 0);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_itvl == self_params->itvl_max);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_latency == self_params->latency);
    TEST_ASSERT(ble_gap_test_conn_desc.supervision_timeout ==
                self_params->supervision_timeout);

    return;

hci_fail:
    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONN_UPDATE);
    TEST_ASSERT(ble_gap_test_conn_status == BLE_HS_HCI_ERR(fail_status));
    TEST_ASSERT(ble_gap_test_conn_desc.conn_handle == 2);
    TEST_ASSERT(memcmp(ble_gap_test_conn_desc.peer_id_addr.val,
                       peer_addr, 6) == 0);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_itvl ==
                BLE_GAP_INITIAL_CONN_ITVL_MAX);
    TEST_ASSERT(ble_gap_test_conn_desc.conn_latency ==
                BLE_GAP_INITIAL_CONN_LATENCY);
    TEST_ASSERT(ble_gap_test_conn_desc.supervision_timeout ==
                BLE_GAP_INITIAL_SUPERVISION_TIMEOUT);
}

TEST_CASE_SELF(ble_gap_test_case_update_conn_good)
{
    ble_gap_test_util_update_no_l2cap(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 10,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 123,
            .max_ce_len = 456,
        }}),
        1, 0, 0);

    ble_gap_test_util_update_no_l2cap(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 100,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 554,
            .max_ce_len = 554,
        }}),
        1, 0, 0);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_update_conn_verify_params)
{
    /* GOOD */
    ble_gap_test_util_update_verify_params(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 100,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 554,
            .max_ce_len = 554,
        }}),
        0);

    /* BAD */
    ble_gap_test_util_update_verify_params(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 1,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 554,
            .max_ce_len = 554,
        }}),
        BLE_HS_EINVAL);

    ble_gap_test_util_update_verify_params(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 0x0C80 + 1,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 554,
            .max_ce_len = 554,
        }}),
        BLE_HS_EINVAL);

    ble_gap_test_util_update_verify_params(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 100,
            .itvl_max = 50,
            .supervision_timeout = 200,
            .min_ce_len = 554,
            .max_ce_len = 554,
        }}),
        BLE_HS_EINVAL);

    ble_gap_test_util_update_verify_params(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 100,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .latency = 0x01F4,
            .min_ce_len = 554,
            .max_ce_len = 554,
        }}),
        BLE_HS_EINVAL);

    ble_gap_test_util_update_verify_params(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 100,
            .itvl_max = 600,
            .supervision_timeout = 300,
            .latency = 1,
            .min_ce_len = 554,
            .max_ce_len = 554,
        }}),
        BLE_HS_EINVAL);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_update_conn_bad)
{
    ble_gap_test_util_update_no_l2cap(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 10,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 123,
            .max_ce_len = 456,
        }}),
        1, 0, BLE_ERR_LMP_COLLISION);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_update_conn_hci_fail)
{
    ble_gap_test_util_update_no_l2cap(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 10,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 123,
            .max_ce_len = 456,
        }}),
        1, BLE_ERR_UNSUPPORTED, 0);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_update_conn_l2cap)
{
    struct ble_gap_upd_params params = {
        .itvl_min = 10,
        .itvl_max = 100,
        .supervision_timeout = 200,
        .min_ce_len = 123,
        .max_ce_len = 456,
    };

    /* Accepted L2CAP. */
    ble_gap_test_util_update_l2cap(&params,
                                   BLE_L2CAP_SIG_UPDATE_RSP_RESULT_ACCEPT);

    /* Rejected L2CAP. */
    ble_gap_test_util_update_l2cap(&params,
                                   BLE_L2CAP_SIG_UPDATE_RSP_RESULT_REJECT);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_update_peer_good)
{
    ble_gap_test_util_update_peer(0,
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 10,
            .itvl_max = 100,
            .supervision_timeout = 0,
            .min_ce_len = 123,
            .max_ce_len = 456,
        }}));

    ble_gap_test_util_update_peer(0,
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 100,
            .itvl_max = 100,
            .supervision_timeout = 100,
            .min_ce_len = 554,
            .max_ce_len = 554,
        }}));

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_update_req_good)
{
    ble_gap_test_util_update_req_pos(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 50,
            .itvl_max = 500,
            .supervision_timeout = 800,
            .min_ce_len = 555,
            .max_ce_len = 888,
        }}),
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 10,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 123,
            .max_ce_len = 456,
        }}),
        -1, 0);

    ble_gap_test_util_update_req_pos(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 50,
            .itvl_max = 500,
            .supervision_timeout = 800,
            .min_ce_len = 555,
            .max_ce_len = 888,
        }}),
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 100,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 554,
            .max_ce_len = 554,
        }}),
        -1, 0);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_update_req_hci_fail)
{
    ble_gap_test_util_update_req_pos(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 50,
            .itvl_max = 500,
            .supervision_timeout = 800,
            .min_ce_len = 555,
            .max_ce_len = 888,
        }}),
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 10,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 123,
            .max_ce_len = 456,
        }}),
        0, BLE_ERR_UNSUPPORTED);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_update_req_reject)
{
    ble_gap_test_util_update_req_neg(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 50,
            .itvl_max = 500,
            .supervision_timeout = 800,
            .min_ce_len = 555,
            .max_ce_len = 888,
        }}),
        -1, 0);

    ble_gap_test_util_update_req_neg(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 50,
            .itvl_max = 500,
            .supervision_timeout = 800,
            .min_ce_len = 555,
            .max_ce_len = 888,
        }}),
        -1, 0);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_update_concurrent_good)
{
    ble_gap_test_util_update_req_concurrent(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 10,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 123,
            .max_ce_len = 456,
        }}),
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 50,
            .itvl_max = 500,
            .supervision_timeout = 800,
            .min_ce_len = 555,
            .max_ce_len = 888,
        }}),
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 10,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 123,
            .max_ce_len = 456,
        }}),
        -1, 0);

    ble_gap_test_util_update_req_concurrent(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 10,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 123,
            .max_ce_len = 456,
        }}),
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 50,
            .itvl_max = 500,
            .supervision_timeout = 800,
            .min_ce_len = 555,
            .max_ce_len = 888,
        }}),
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 20,
            .itvl_max = 200,
            .supervision_timeout = 350,
            .min_ce_len = 111,
            .max_ce_len = 222,
        }}),
        -1, 0);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_update_concurrent_hci_fail)
{
    ble_gap_test_util_update_req_concurrent(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 10,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 123,
            .max_ce_len = 456,
        }}),
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 50,
            .itvl_max = 500,
            .supervision_timeout = 800,
            .min_ce_len = 555,
            .max_ce_len = 888,
        }}),
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 20,
            .itvl_max = 200,
            .supervision_timeout = 350,
            .min_ce_len = 111,
            .max_ce_len = 222,
        }}),
        0, BLE_ERR_UNSUPPORTED);

    ble_gap_test_util_update_req_concurrent(
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 10,
            .itvl_max = 100,
            .supervision_timeout = 200,
            .min_ce_len = 123,
            .max_ce_len = 456,
        }}),
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 50,
            .itvl_max = 500,
            .supervision_timeout = 800,
            .min_ce_len = 555,
            .max_ce_len = 888,
        }}),
        ((struct ble_gap_upd_params[]) { {
            .itvl_min = 20,
            .itvl_max = 200,
            .supervision_timeout = 350,
            .min_ce_len = 111,
            .max_ce_len = 222,
        }}),
        1, BLE_ERR_UNSUPPORTED);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_SUITE(ble_gap_test_suite_update_conn)
{
    ble_gap_test_case_update_conn_good();
    ble_gap_test_case_update_conn_bad();
    ble_gap_test_case_update_conn_hci_fail();
    ble_gap_test_case_update_conn_l2cap();
    ble_gap_test_case_update_peer_good();
    ble_gap_test_case_update_req_good();
    ble_gap_test_case_update_req_hci_fail();
    ble_gap_test_case_update_req_reject();
    ble_gap_test_case_update_concurrent_good();
    ble_gap_test_case_update_concurrent_hci_fail();
    ble_gap_test_case_update_conn_verify_params();
}

/*****************************************************************************
 * $timeout                                                                  *
 *****************************************************************************/

static void
ble_gap_test_util_conn_forever(void)
{
    int32_t ticks_from_now;

    /* Initiate a connect procedure with no timeout. */
    ble_hs_test_util_connect(
        BLE_OWN_ADDR_PUBLIC,
        &((ble_addr_t) { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 }}),
        BLE_HS_FOREVER,
        NULL, ble_gap_test_util_connect_cb,
        NULL, 0);

    /* Ensure no pending GAP event. */
    ticks_from_now = ble_gap_timer();
    TEST_ASSERT(ticks_from_now == BLE_HS_FOREVER);

    /* Advance 100 seconds; ensure no timeout reported. */
    os_time_advance(100 * OS_TICKS_PER_SEC);
    ble_gap_timer();
    TEST_ASSERT(ble_gap_test_event.type == 0xff);
    TEST_ASSERT(ble_gap_conn_active());
}

static void
ble_gap_test_util_conn_timeout(int32_t duration_ms)
{
    struct ble_gap_conn_complete evt;
    uint32_t duration_ticks;
    int32_t ticks_from_now;
    int rc;

    TEST_ASSERT_FATAL(duration_ms != BLE_HS_FOREVER);

    /* Initiate a connect procedure with the specified timeout. */
    ble_hs_test_util_connect(
        BLE_OWN_ADDR_PUBLIC,
        &((ble_addr_t) { BLE_ADDR_PUBLIC, { 1, 2, 3, 4, 5, 6 }}),
        duration_ms,
        NULL, ble_gap_test_util_connect_cb,
        NULL, 0);

    /* Ensure next GAP event is at the expected time. */
    rc = os_time_ms_to_ticks(duration_ms, &duration_ticks);
    TEST_ASSERT_FATAL(rc == 0);
    ticks_from_now = ble_gap_timer();
    TEST_ASSERT(ticks_from_now == duration_ticks);

    /* Advance duration ms; ensure timeout event does not get reported before
     * connection complete event rxed.
     */
    os_time_advance(duration_ms);

    ble_hs_test_util_hci_ack_set(
        ble_hs_hci_util_opcode_join(BLE_HCI_OGF_LE,
                                    BLE_HCI_OCF_LE_CREATE_CONN_CANCEL),
        0);

    TEST_ASSERT(ble_gap_test_event.type == 0xff);

    ticks_from_now = ble_gap_timer();
    TEST_ASSERT(ticks_from_now == BLE_HS_FOREVER);

    /* Ensure cancel create connection command was sent. */
    ble_hs_test_util_hci_verify_tx_create_conn_cancel();

    /* Ensure timer has been stopped. */
    ticks_from_now = ble_gap_timer();
    TEST_ASSERT(ticks_from_now == BLE_HS_FOREVER);

    /* Receive the connection complete event indicating a successful cancel. */
    memset(&evt, 0, sizeof evt);
    evt.status = BLE_ERR_UNK_CONN_ID;
    /* test if host correctly ignores other fields if status is error */
    evt.connection_handle = 0x0fff;

    rc = ble_gap_rx_conn_complete(&evt, 0);
    TEST_ASSERT_FATAL(rc == 0);

    /* Ensure the GAP event was triggered. */
    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_CONNECT);
    TEST_ASSERT(ble_gap_test_conn_status == BLE_HS_ETIMEOUT);

    /* Clear GAP event for remainder of test. */
    ble_gap_test_util_reset_cb_info();
}

static void
ble_gap_test_util_disc_forever(void)
{
    struct ble_gap_disc_params params;
    int32_t ticks_from_now;

    memset(&params, 0, sizeof params);

    /* Initiate a discovery procedure with no timeout. */
    ble_hs_test_util_disc(BLE_OWN_ADDR_PUBLIC,
                          BLE_HS_FOREVER, &params, ble_gap_test_util_disc_cb,
                          NULL, -1, 0);

    /* Ensure no pending GAP event. */
    ticks_from_now = ble_gap_timer();
    TEST_ASSERT(ticks_from_now == BLE_HS_FOREVER);

    /* Advance 100 seconds; ensure no timeout reported. */
    os_time_advance(100 * OS_TICKS_PER_SEC);
    TEST_ASSERT(ble_gap_test_disc_event_type == -1);
    TEST_ASSERT(ble_gap_disc_active());
}

static void
ble_gap_test_util_disc_timeout(int32_t duration_ms)
{
    struct ble_gap_disc_params params;
    uint32_t duration_ticks;
    int32_t ticks_from_now;
    int rc;

    TEST_ASSERT_FATAL(duration_ms != BLE_HS_FOREVER);

    memset(&params, 0, sizeof params);

    /* Initiate a discovery procedure with the specified timeout. */
    ble_hs_test_util_disc(BLE_OWN_ADDR_PUBLIC,
                          duration_ms, &params, ble_gap_test_util_disc_cb,
                          NULL, -1, 0);

    /* Ensure next GAP event is at the expected time. */
    rc = os_time_ms_to_ticks(duration_ms, &duration_ticks);
    TEST_ASSERT_FATAL(rc == 0);
    ticks_from_now = ble_gap_timer();
    TEST_ASSERT(ticks_from_now == duration_ticks);

    /* Advance duration ms; ensure timeout event was reported. */
    os_time_advance(duration_ms);

    ble_hs_test_util_hci_ack_set(
        ble_hs_hci_util_opcode_join(BLE_HCI_OGF_LE,
                                    BLE_HCI_OCF_LE_SET_SCAN_ENABLE),
        0);
    ticks_from_now = ble_gap_timer();
    TEST_ASSERT(ticks_from_now == BLE_HS_FOREVER);

    TEST_ASSERT(ble_gap_test_disc_event_type == BLE_GAP_EVENT_DISC_COMPLETE);

    /* Clear GAP event for remainder of test. */
    ble_gap_test_util_reset_cb_info();
}

TEST_CASE_SELF(ble_gap_test_case_update_timeout)
{
    struct ble_gap_upd_params params = {
        .itvl_min = 10,
        .itvl_max = 100,
        .supervision_timeout = 200,
        .min_ce_len = 123,
        .max_ce_len = 456,
    };

    /* No L2CAP. */
    ble_gap_test_util_update_no_l2cap_tmo(&params, 1);

    /* L2CAP - Local unsupported; L2CAP timeout. */
    ble_gap_test_util_update_l2cap_tmo(&params, BLE_ERR_UNKNOWN_HCI_CMD, 0, 0);

    /* L2CAP - Remote unsupported; L2CAP timeout. */
    ble_gap_test_util_update_l2cap_tmo(&params, 0, BLE_ERR_UNSUPP_REM_FEATURE,
                                       0);

    /* L2CAP - Remote unsupported; LL timeout. */
    ble_gap_test_util_update_l2cap_tmo(&params, 0, BLE_ERR_UNSUPP_REM_FEATURE,
                                       1);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_timeout_conn_forever)
{
    ble_gap_test_util_init();

    /* 30 ms. */
    ble_gap_test_util_conn_timeout(30);

    /* No timeout. */
    ble_gap_test_util_conn_forever();

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_timeout_conn_timeout)
{
    ble_gap_test_util_init();

    /* 30 ms. */
    ble_gap_test_util_conn_timeout(30);

    /* 20 ms. */
    ble_gap_test_util_conn_timeout(20);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_forever_conn_timeout)
{
    ble_gap_test_util_init();

    /* No timeout. */
    ble_gap_test_util_conn_forever();

    /* Cancel connect procedure manually. */
    ble_gap_test_util_conn_cancel(0);

    /* Clear GAP event for remainder of test. */
    ble_gap_test_util_reset_cb_info();

    /* 30 ms. */
    ble_gap_test_util_conn_timeout(30);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_disc_timeout_disc_forever)
{
    ble_gap_test_util_init();

    /* 30 ms. */
    ble_gap_test_util_disc_timeout(30);

    /* No timeout. */
    ble_gap_test_util_disc_forever();

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_disc_timeout_disc_timeout)
{
    ble_gap_test_util_init();

    /* 30 ms. */
    ble_gap_test_util_disc_timeout(30);

    /* 20 ms. */
    ble_gap_test_util_disc_timeout(20);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_disc_forever_disc_timeout)
{
    ble_gap_test_util_init();

    /* No timeout. */
    ble_gap_test_util_disc_forever();

    /* Cancel discovery procedure manually. */
    ble_hs_test_util_disc_cancel(0);

    /* 30 ms. */
    ble_gap_test_util_disc_timeout(30);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_conn_timeout_disc_timeout)
{
    ble_gap_test_util_init();

    /* 15 seconds. */
    ble_gap_test_util_conn_timeout(15000);

    /* 1280 ms. */
    ble_gap_test_util_disc_timeout(1280);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_SUITE(ble_gap_test_suite_timeout)
{
    ble_gap_test_case_conn_timeout_conn_forever();
    ble_gap_test_case_conn_timeout_conn_timeout();
    ble_gap_test_case_conn_forever_conn_timeout();

    ble_gap_test_case_disc_timeout_disc_forever();
    ble_gap_test_case_disc_timeout_disc_timeout();
    ble_gap_test_case_disc_forever_disc_timeout();

    ble_gap_test_case_conn_timeout_disc_timeout();

    ble_gap_test_case_update_timeout();
}

TEST_CASE_SELF(ble_gap_test_case_mtu_us)
{
    const uint8_t peer_addr[6] = { 1,2,3,4,5,6 };
    int rc;

    ble_gap_test_util_init();

    ble_hs_test_util_create_conn(2, peer_addr, ble_gap_test_util_connect_cb,
                                 NULL);

    ble_att_set_preferred_mtu(200);

    rc = ble_gattc_exchange_mtu(2, NULL, NULL);
    TEST_ASSERT_FATAL(rc == 0);
    ble_hs_test_util_verify_tx_mtu_cmd(1, 200);

    rc = ble_hs_test_util_rx_att_mtu_cmd(2, 0, 123);
    TEST_ASSERT_FATAL(rc == 0);
    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_MTU);
    TEST_ASSERT(ble_gap_test_event.mtu.conn_handle == 2);
    TEST_ASSERT(ble_gap_test_event.mtu.channel_id == BLE_L2CAP_CID_ATT);
    TEST_ASSERT(ble_gap_test_event.mtu.value == 123);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_mtu_peer)
{
    const uint8_t peer_addr[6] = { 1,2,3,4,5,6 };
    int rc;

    ble_gap_test_util_init();

    ble_hs_test_util_create_conn(2, peer_addr, ble_gap_test_util_connect_cb,
                                 NULL);

    ble_att_set_preferred_mtu(200);

    rc = ble_hs_test_util_rx_att_mtu_cmd(2, 1, 123);
    TEST_ASSERT_FATAL(rc == 0);
    ble_hs_test_util_verify_tx_mtu_cmd(0, 200);

    TEST_ASSERT(ble_gap_test_event.type == BLE_GAP_EVENT_MTU);
    TEST_ASSERT(ble_gap_test_event.mtu.conn_handle == 2);
    TEST_ASSERT(ble_gap_test_event.mtu.channel_id == BLE_L2CAP_CID_ATT);
    TEST_ASSERT(ble_gap_test_event.mtu.value == 123);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_SUITE(ble_gap_test_suite_mtu)
{
    ble_gap_test_case_mtu_us();
    ble_gap_test_case_mtu_peer();
}

/*****************************************************************************
 * $set cb                                                                   *
 *****************************************************************************/

static int
ble_gap_test_util_set_cb_event(struct ble_gap_event *event, void *arg)
{
    struct ble_gap_event *event_arg;

    event_arg = arg;

    *event_arg = *event;

    return 0;
}

TEST_CASE_SELF(ble_gap_test_case_set_cb_good)
{
    const uint8_t peer_addr[6] = { 1,2,3,4,5,6 };
    struct ble_gap_event event;
    int rc;

    ble_gap_test_util_init();

    ble_hs_test_util_create_conn(2, peer_addr, ble_gap_test_util_connect_cb,
                                 NULL);

    /* Reconfigure the callback. */
    rc = ble_gap_set_event_cb(2, ble_gap_test_util_set_cb_event, &event);
    TEST_ASSERT_FATAL(rc == 0);

    /* Terminate the connection and ensure the new callback gets called. */
    rc = ble_hs_test_util_conn_terminate(2, 0);
    TEST_ASSERT_FATAL(rc == 0);

    ble_hs_test_util_hci_rx_disconn_complete_event(2, 0, BLE_ERR_REM_USER_CONN_TERM);

    TEST_ASSERT(event.type == BLE_GAP_EVENT_DISCONNECT);
    TEST_ASSERT(event.disconnect.reason ==
                BLE_HS_HCI_ERR(BLE_ERR_REM_USER_CONN_TERM));
    TEST_ASSERT(event.disconnect.conn.conn_handle == 2);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gap_test_case_set_cb_bad)
{
    int rc;

    ble_gap_test_util_init();

    /* Ensure error is reported when specified connection doesn't exist. */
    rc = ble_gap_set_event_cb(123, ble_gap_test_util_set_cb_event, NULL);
    TEST_ASSERT(rc == BLE_HS_ENOTCONN);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_SUITE(ble_gap_test_suite_set_cb)
{
    ble_gap_test_case_set_cb_good();
    ble_gap_test_case_set_cb_bad();
}
