http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/c5901fcc/net/nimble/host/test/src/ble_gatts_notify_test.c
----------------------------------------------------------------------
diff --cc net/nimble/host/test/src/ble_gatts_notify_test.c
index 4516e66,0000000..4e2d587
mode 100644,000000..100644
--- a/net/nimble/host/test/src/ble_gatts_notify_test.c
+++ b/net/nimble/host/test/src/ble_gatts_notify_test.c
@@@ -1,983 -1,0 +1,1074 @@@
 +/**
 + * 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 "host/ble_uuid.h"
 +#include "host/ble_hs_test.h"
 +#include "ble_hs_test_util.h"
 +#include "ble_hs_test_util_store.h"
 +
 +#define BLE_GATTS_NOTIFY_TEST_CHR_1_UUID    0x1111
 +#define BLE_GATTS_NOTIFY_TEST_CHR_2_UUID    0x2222
 +
 +#define BLE_GATTS_NOTIFY_TEST_MAX_EVENTS    16
 +
 +static uint8_t ble_gatts_notify_test_peer_addr[6] = {2,3,4,5,6,7};
 +
 +static int
 +ble_gatts_notify_test_misc_access(uint16_t conn_handle,
 +                                  uint16_t attr_handle, 
 +                                  struct ble_gatt_access_ctxt *ctxt,
 +                                  void *arg);
 +static void
 +ble_gatts_notify_test_misc_reg_cb(struct ble_gatt_register_ctxt *ctxt,
 +                                  void *arg);
 +
 +static const struct ble_gatt_svc_def ble_gatts_notify_test_svcs[] = { {
 +    .type = BLE_GATT_SVC_TYPE_PRIMARY,
 +    .uuid128 = BLE_UUID16(0x1234),
 +    .characteristics = (struct ble_gatt_chr_def[]) { {
 +        .uuid128 = BLE_UUID16(BLE_GATTS_NOTIFY_TEST_CHR_1_UUID),
 +        .access_cb = ble_gatts_notify_test_misc_access,
 +        .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY |
 +                 BLE_GATT_CHR_F_INDICATE,
 +    }, {
 +        .uuid128 = BLE_UUID16(BLE_GATTS_NOTIFY_TEST_CHR_2_UUID),
 +        .access_cb = ble_gatts_notify_test_misc_access,
 +        .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY |
 +                 BLE_GATT_CHR_F_INDICATE,
 +    }, {
 +        0
 +    } },
 +}, {
 +    0
 +} };
 +
 +
 +static uint16_t ble_gatts_notify_test_chr_1_def_handle;
 +static uint8_t ble_gatts_notify_test_chr_1_val[1024];
 +static int ble_gatts_notify_test_chr_1_len;
 +static uint16_t ble_gatts_notify_test_chr_2_def_handle;
 +static uint8_t ble_gatts_notify_test_chr_2_val[1024];
 +static int ble_gatts_notify_test_chr_2_len;
 +
 +static struct ble_gap_event
 +ble_gatts_notify_test_events[BLE_GATTS_NOTIFY_TEST_MAX_EVENTS];
 +
 +static int ble_gatts_notify_test_num_events;
 +
 +typedef int ble_store_write_fn(int obj_type, union ble_store_value *val);
 +
 +typedef int ble_store_delete_fn(int obj_type, union ble_store_key *key);
 +
 +static int
 +ble_gatts_notify_test_util_gap_event(struct ble_gap_event *event, void *arg)
 +{
 +    switch (event->type) {
 +    case BLE_GAP_EVENT_NOTIFY_TX:
 +    case BLE_GAP_EVENT_SUBSCRIBE:
 +        TEST_ASSERT_FATAL(ble_gatts_notify_test_num_events <
 +                          BLE_GATTS_NOTIFY_TEST_MAX_EVENTS);
 +
 +        ble_gatts_notify_test_events[ble_gatts_notify_test_num_events++] =
 +            *event;
 +
 +    default:
 +        break;
 +    }
 +
 +    return 0;
 +}
 +
 +static uint16_t
 +ble_gatts_notify_test_misc_read_notify(uint16_t conn_handle,
 +                                       uint16_t chr_def_handle)
 +{
 +    struct ble_att_read_req req;
 +    struct os_mbuf *om;
 +    uint8_t buf[BLE_ATT_READ_REQ_SZ];
 +    uint16_t flags;
 +    int rc;
 +
 +    req.barq_handle = chr_def_handle + 2;
 +    ble_att_read_req_write(buf, sizeof buf, &req);
 +
 +    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn_handle, 
BLE_L2CAP_CID_ATT,
 +                                                buf, sizeof buf);
 +    TEST_ASSERT(rc == 0);
 +
 +    ble_hs_test_util_tx_all();
 +
 +    om = ble_hs_test_util_prev_tx_dequeue_pullup();
 +    TEST_ASSERT_FATAL(om != NULL);
 +    TEST_ASSERT_FATAL(om->om_len == 3);
 +    TEST_ASSERT_FATAL(om->om_data[0] == BLE_ATT_OP_READ_RSP);
 +
 +    flags = le16toh(om->om_data + 1);
 +    return flags;
 +}
 +
 +static void
- ble_gatts_notify_test_misc_enable_notify(uint16_t conn_handle,
-                                          uint16_t chr_def_handle,
-                                          uint16_t flags)
++ble_gatts_notify_test_misc_try_enable_notify(uint16_t conn_handle,
++                                             uint16_t chr_def_handle,
++                                             uint16_t flags, int fail)
 +{
 +    struct ble_att_write_req req;
 +    uint8_t buf[BLE_ATT_WRITE_REQ_BASE_SZ + 2];
 +    int rc;
 +
 +    req.bawq_handle = chr_def_handle + 2;
 +    ble_att_write_req_write(buf, sizeof buf, &req);
 +
 +    htole16(buf + BLE_ATT_WRITE_REQ_BASE_SZ, flags);
 +    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn_handle, 
BLE_L2CAP_CID_ATT,
 +                                                buf, sizeof buf);
-     TEST_ASSERT(rc == 0);
++    if (fail) {
++        TEST_ASSERT_FATAL(rc != 0);
++        ble_hs_test_util_verify_tx_err_rsp(BLE_ATT_OP_WRITE_REQ,
++                                           req.bawq_handle,
++                                           BLE_ATT_ERR_REQ_NOT_SUPPORTED);
++    } else {
++        TEST_ASSERT_FATAL(rc == 0);
++        ble_hs_test_util_verify_tx_write_rsp();
++    }
++}
++
++static void
++ble_gatts_notify_test_misc_enable_notify(uint16_t conn_handle,
++                                         uint16_t chr_def_handle,
++                                         uint16_t flags)
++{
++    ble_gatts_notify_test_misc_try_enable_notify(conn_handle,
++                                                 chr_def_handle,
++                                                 flags, 0);
 +}
 +
 +static void
 +ble_gatts_notify_test_util_next_event(struct ble_gap_event *event)
 +{
 +    TEST_ASSERT_FATAL(ble_gatts_notify_test_num_events > 0);
 +
 +    *event = *ble_gatts_notify_test_events;
 +
 +    ble_gatts_notify_test_num_events--;
 +    if (ble_gatts_notify_test_num_events > 0) {
 +        memmove(ble_gatts_notify_test_events + 0,
 +                ble_gatts_notify_test_events + 1,
 +                ble_gatts_notify_test_num_events * sizeof *event);
 +    }
 +}
 +
 +static void
 +ble_gatts_notify_test_util_verify_sub_event(uint16_t conn_handle,
 +                                            uint8_t attr_handle,
 +                                            uint8_t reason,
 +                                            uint8_t prevn, uint8_t curn,
 +                                            uint8_t previ, uint8_t curi)
 +{
 +    struct ble_gap_event event;
 +
 +    ble_gatts_notify_test_util_next_event(&event);
 +
 +    TEST_ASSERT(event.type == BLE_GAP_EVENT_SUBSCRIBE);
 +    TEST_ASSERT(event.subscribe.conn_handle == conn_handle);
 +    TEST_ASSERT(event.subscribe.attr_handle == attr_handle);
 +    TEST_ASSERT(event.subscribe.reason == reason);
 +    TEST_ASSERT(event.subscribe.prev_notify == prevn);
 +    TEST_ASSERT(event.subscribe.cur_notify == curn);
 +    TEST_ASSERT(event.subscribe.prev_indicate == previ);
 +    TEST_ASSERT(event.subscribe.cur_indicate == curi);
 +}
 +
 +static void
 +ble_gatts_notify_test_util_verify_tx_event(uint16_t conn_handle,
 +                                           uint8_t attr_handle,
 +                                           int status,
 +                                           int indication)
 +{
 +    struct ble_gap_event event;
 +
 +    ble_gatts_notify_test_util_next_event(&event);
 +
 +    TEST_ASSERT(event.type == BLE_GAP_EVENT_NOTIFY_TX);
 +    TEST_ASSERT(event.notify_tx.status == status);
 +    TEST_ASSERT(event.notify_tx.conn_handle == conn_handle);
 +    TEST_ASSERT(event.notify_tx.attr_handle == attr_handle);
 +    TEST_ASSERT(event.notify_tx.indication == indication);
 +}
 +
 +static void
 +ble_gatts_notify_test_util_verify_ack_event(uint16_t conn_handle,
 +                                            uint8_t attr_handle)
 +{
 +    ble_gatts_notify_test_util_verify_tx_event(conn_handle, attr_handle,
 +                                               BLE_HS_EDONE, 1);
 +}
 +
 +static void
 +ble_gatts_notify_test_misc_init(uint16_t *out_conn_handle, int bonding,
 +                                uint16_t chr1_flags, uint16_t chr2_flags)
 +{
 +    struct ble_hs_conn *conn;
 +    uint16_t flags;
 +    int exp_num_cccds;
 +    int rc;
 +
 +    ble_hs_test_util_init();
 +
 +    ble_gatts_notify_test_num_events = 0;
 +
 +    ble_hs_test_util_store_init(10, 10, 10);
 +    ble_hs_cfg.store_read_cb = ble_hs_test_util_store_read;
 +    ble_hs_cfg.store_write_cb = ble_hs_test_util_store_write;
 +
 +    rc = ble_gatts_register_svcs(ble_gatts_notify_test_svcs,
 +                                 ble_gatts_notify_test_misc_reg_cb, NULL);
 +    TEST_ASSERT_FATAL(rc == 0);
 +    TEST_ASSERT_FATAL(ble_gatts_notify_test_chr_1_def_handle != 0);
 +    TEST_ASSERT_FATAL(ble_gatts_notify_test_chr_2_def_handle != 0);
 +
 +    ble_gatts_start();
 +
 +    ble_hs_test_util_create_conn(2, ble_gatts_notify_test_peer_addr,
 +                                 ble_gatts_notify_test_util_gap_event, NULL);
 +    *out_conn_handle = 2;
 +
 +    if (bonding) {
 +        ble_hs_lock();
 +        conn = ble_hs_conn_find(2);
 +        TEST_ASSERT_FATAL(conn != NULL);
 +        conn->bhc_sec_state.encrypted = 1;
 +        conn->bhc_sec_state.authenticated = 1;
 +        conn->bhc_sec_state.bonded = 1;
 +        ble_hs_unlock();
 +    }
 +
 +    /* Ensure notifications disabled on new connection. */
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        2, ble_gatts_notify_test_chr_1_def_handle);
 +    TEST_ASSERT(flags == 0);
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        2, ble_gatts_notify_test_chr_2_def_handle);
 +    TEST_ASSERT(flags == 0);
 +
 +    /* Set initial notification / indication state and verify that 
subscription
 +     * callback gets executed.
 +     */
 +    if (chr1_flags != 0) {
 +        ble_gatts_notify_test_misc_enable_notify(
 +            2, ble_gatts_notify_test_chr_1_def_handle, chr1_flags);
 +
 +        ble_gatts_notify_test_util_verify_sub_event(
 +            *out_conn_handle,
 +            ble_gatts_notify_test_chr_1_def_handle + 1,
 +            BLE_GAP_SUBSCRIBE_REASON_WRITE,
 +            0, chr1_flags == BLE_GATTS_CLT_CFG_F_NOTIFY,
 +            0, chr1_flags == BLE_GATTS_CLT_CFG_F_INDICATE);
 +    }
 +    if (chr2_flags != 0) {
 +        ble_gatts_notify_test_misc_enable_notify(
 +            2, ble_gatts_notify_test_chr_2_def_handle, chr2_flags);
 +
 +        ble_gatts_notify_test_util_verify_sub_event(
 +            *out_conn_handle,
 +            ble_gatts_notify_test_chr_2_def_handle + 1,
 +            BLE_GAP_SUBSCRIBE_REASON_WRITE,
 +            0, chr2_flags == BLE_GATTS_CLT_CFG_F_NOTIFY,
 +            0, chr2_flags == BLE_GATTS_CLT_CFG_F_INDICATE);
 +    }
 +
 +    /* Ensure no extraneous subscription callbacks were executed. */
 +    TEST_ASSERT(ble_gatts_notify_test_num_events == 0);
 +
 +    /* Toss both write responses. */
 +    ble_hs_test_util_prev_tx_queue_clear();
 +
 +    /* Ensure notification / indication state reads back correctly. */
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        2, ble_gatts_notify_test_chr_1_def_handle);
 +    TEST_ASSERT(flags == chr1_flags);
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        2, ble_gatts_notify_test_chr_2_def_handle);
 +    TEST_ASSERT(flags == chr2_flags);
 +
 +    /* Ensure both CCCDs still persisted. */
 +    if (bonding) {
 +        exp_num_cccds = (chr1_flags != 0) + (chr2_flags != 0);
 +    } else {
 +        exp_num_cccds = 0;
 +    }
 +    TEST_ASSERT(ble_hs_test_util_store_num_cccds == exp_num_cccds);
 +}
 +
 +static void
 +ble_gatts_notify_test_disconnect(uint16_t conn_handle,
 +                                 uint8_t chr1_flags,
 +                                 uint8_t chr1_indicate_in_progress,
 +                                 uint8_t chr2_flags,
 +                                 uint8_t chr2_indicate_in_progress)
 +{
 +    ble_hs_test_util_conn_disconnect(conn_handle);
 +
 +    if (chr1_indicate_in_progress) {
 +        ble_gatts_notify_test_util_verify_tx_event(
 +            conn_handle,
 +            ble_gatts_notify_test_chr_1_def_handle + 1,
 +            BLE_HS_ENOTCONN,
 +            1);
 +    }
 +
 +    /* Verify subscription callback executed for each subscribed
 +     * characteristic.
 +     */
 +    if (chr1_flags != 0) {
 +        ble_gatts_notify_test_util_verify_sub_event(
 +            conn_handle,
 +            ble_gatts_notify_test_chr_1_def_handle + 1,
 +            BLE_GAP_SUBSCRIBE_REASON_TERM,
 +            chr1_flags == BLE_GATTS_CLT_CFG_F_NOTIFY, 0,
 +            chr1_flags == BLE_GATTS_CLT_CFG_F_INDICATE, 0);
 +    }
 +
 +    if (chr2_indicate_in_progress) {
 +        ble_gatts_notify_test_util_verify_tx_event(
 +            conn_handle,
 +            ble_gatts_notify_test_chr_2_def_handle + 1,
 +            BLE_HS_ENOTCONN,
 +            1);
 +    }
 +
 +    if (chr2_flags != 0) {
 +        ble_gatts_notify_test_util_verify_sub_event(
 +            conn_handle,
 +            ble_gatts_notify_test_chr_2_def_handle + 1,
 +            BLE_GAP_SUBSCRIBE_REASON_TERM,
 +            chr2_flags == BLE_GATTS_CLT_CFG_F_NOTIFY, 0,
 +            chr2_flags == BLE_GATTS_CLT_CFG_F_INDICATE, 0);
 +    }
 +}
 +
 +static void
 +ble_gatts_notify_test_misc_reg_cb(struct ble_gatt_register_ctxt *ctxt,
 +                                  void *arg)
 +{
 +    uint16_t uuid16;
 +
 +    if (ctxt->op == BLE_GATT_REGISTER_OP_CHR) {
 +        uuid16 = ble_uuid_128_to_16(ctxt->chr.chr_def->uuid128);
 +        switch (uuid16) {
 +        case BLE_GATTS_NOTIFY_TEST_CHR_1_UUID:
 +            ble_gatts_notify_test_chr_1_def_handle = ctxt->chr.def_handle;
 +            break;
 +
 +        case BLE_GATTS_NOTIFY_TEST_CHR_2_UUID:
 +            ble_gatts_notify_test_chr_2_def_handle = ctxt->chr.def_handle;
 +            break;
 +
 +        default:
 +            TEST_ASSERT_FATAL(0);
 +            break;
 +        }
 +    }
 +}
 +
 +static int
 +ble_gatts_notify_test_misc_access(uint16_t conn_handle,
 +                                  uint16_t attr_handle,
 +                                  struct ble_gatt_access_ctxt *ctxt,
 +                                  void *arg)
 +{
 +    int rc;
 +
 +    TEST_ASSERT_FATAL(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR);
 +    TEST_ASSERT(conn_handle == 0xffff);
 +
 +    if (attr_handle == ble_gatts_notify_test_chr_1_def_handle + 1) {
 +        TEST_ASSERT(ctxt->chr ==
 +                    &ble_gatts_notify_test_svcs[0].characteristics[0]);
 +        rc = os_mbuf_copyinto(ctxt->om, 0, ble_gatts_notify_test_chr_1_val,
 +                              ble_gatts_notify_test_chr_1_len);
 +        TEST_ASSERT_FATAL(rc == 0);
 +    } else if (attr_handle == ble_gatts_notify_test_chr_2_def_handle + 1) {
 +        TEST_ASSERT(ctxt->chr ==
 +                    &ble_gatts_notify_test_svcs[0].characteristics[1]);
 +        rc = os_mbuf_copyinto(ctxt->om, 0, ble_gatts_notify_test_chr_2_val,
 +                              ble_gatts_notify_test_chr_2_len);
 +        TEST_ASSERT_FATAL(rc == 0);
 +    } else {
 +        TEST_ASSERT_FATAL(0);
 +    }
 +
 +    return 0;
 +}
 +
 +static void
 +ble_gatts_notify_test_misc_rx_indicate_rsp(uint16_t conn_handle,
 +                                           uint16_t attr_handle)
 +{
 +    uint8_t buf[BLE_ATT_INDICATE_RSP_SZ];
 +    int rc;
 +
 +    ble_att_indicate_rsp_write(buf, sizeof buf);
 +
 +    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn_handle, 
BLE_L2CAP_CID_ATT,
 +                                                buf, sizeof buf);
 +    TEST_ASSERT(rc == 0);
 +
 +    ble_gatts_notify_test_util_verify_ack_event(conn_handle, attr_handle);
 +}
 +
 +
 +static void
 +ble_gatts_notify_test_misc_verify_tx_n(uint16_t conn_handle,
 +                                       uint16_t attr_handle,
 +                                       uint8_t *attr_data, int attr_len)
 +{
 +    struct ble_att_notify_req req;
 +    struct os_mbuf *om;
 +    int i;
 +
 +    ble_hs_test_util_tx_all();
 +
 +    om = ble_hs_test_util_prev_tx_dequeue_pullup();
 +    TEST_ASSERT_FATAL(om != NULL);
 +
 +    ble_att_notify_req_parse(om->om_data, om->om_len, &req);
 +    TEST_ASSERT(req.banq_handle == attr_handle);
 +
 +    for (i = 0; i < attr_len; i++) {
 +        TEST_ASSERT(om->om_data[BLE_ATT_NOTIFY_REQ_BASE_SZ + i] ==
 +                    attr_data[i]);
 +    }
 +
 +    ble_gatts_notify_test_util_verify_tx_event(conn_handle, attr_handle, 0, 
0);
 +}
 +
 +static void
 +ble_gatts_notify_test_misc_verify_tx_i(uint16_t conn_handle,
 +                                       uint16_t attr_handle,
 +                                       uint8_t *attr_data, int attr_len)
 +{
 +    struct ble_att_indicate_req req;
 +    struct os_mbuf *om;
 +    int i;
 +
 +    ble_hs_test_util_tx_all();
 +
 +    om = ble_hs_test_util_prev_tx_dequeue_pullup();
 +    TEST_ASSERT_FATAL(om != NULL);
 +
 +    ble_att_indicate_req_parse(om->om_data, om->om_len, &req);
 +    TEST_ASSERT(req.baiq_handle == attr_handle);
 +
 +    for (i = 0; i < attr_len; i++) {
 +        TEST_ASSERT(om->om_data[BLE_ATT_INDICATE_REQ_BASE_SZ + i] ==
 +                    attr_data[i]);
 +    }
 +
 +    ble_gatts_notify_test_util_verify_tx_event(conn_handle, attr_handle, 0, 
1);
 +}
 +
 +static void
 +ble_gatts_notify_test_misc_verify_tx_gen(uint16_t conn_handle, int attr_idx,
 +                                         uint8_t chr_flags)
 +{
 +    uint16_t attr_handle;
 +    uint16_t attr_len;
 +    void *attr_val;
 +
 +    switch (attr_idx) {
 +    case 1:
 +        attr_handle = ble_gatts_notify_test_chr_1_def_handle + 1;
 +        attr_len = ble_gatts_notify_test_chr_1_len;
 +        attr_val = ble_gatts_notify_test_chr_1_val;
 +        break;
 +
 +    case 2:
 +        attr_handle = ble_gatts_notify_test_chr_2_def_handle + 1;
 +        attr_len = ble_gatts_notify_test_chr_2_len;
 +        attr_val = ble_gatts_notify_test_chr_2_val;
 +        break;
 +
 +    default:
 +        TEST_ASSERT_FATAL(0);
 +        break;
 +    }
 +
 +    switch (chr_flags) {
 +    case 0:
 +        break;
 +
 +    case BLE_GATTS_CLT_CFG_F_NOTIFY:
 +        ble_gatts_notify_test_misc_verify_tx_n(conn_handle, attr_handle,
 +                                               attr_val, attr_len);
 +        break;
 +
 +    case BLE_GATTS_CLT_CFG_F_INDICATE:
 +        ble_gatts_notify_test_misc_verify_tx_i(conn_handle, attr_handle,
 +                                               attr_val, attr_len);
 +        break;
 +
 +    default:
 +        TEST_ASSERT_FATAL(0);
 +        break;
 +    }
 +}
 +
 +static void
 +ble_gatts_notify_test_restore_bonding(uint16_t conn_handle,
 +                                      uint8_t chr1_flags, uint8_t chr1_tx,
 +                                      uint8_t chr2_flags, uint8_t chr2_tx)
 +{
 +    struct ble_hs_conn *conn;
 +
 +    ble_hs_lock();
 +    conn = ble_hs_conn_find(conn_handle);
 +    TEST_ASSERT_FATAL(conn != NULL);
 +    conn->bhc_sec_state.encrypted = 1;
 +    conn->bhc_sec_state.authenticated = 1;
 +    conn->bhc_sec_state.bonded = 1;
 +    ble_hs_unlock();
 +
 +    ble_gatts_bonding_restored(conn_handle);
 +
 +    /* Verify subscription callback executed for each subscribed
 +     * characteristic.
 +     */
 +    if (chr1_flags != 0) {
 +        ble_gatts_notify_test_util_verify_sub_event(
 +            conn_handle,
 +            ble_gatts_notify_test_chr_1_def_handle + 1,
 +            BLE_GAP_SUBSCRIBE_REASON_RESTORE,
 +            0, chr1_flags == BLE_GATTS_CLT_CFG_F_NOTIFY,
 +            0, chr1_flags == BLE_GATTS_CLT_CFG_F_INDICATE);
 +
 +    }
 +    if (chr1_tx) {
 +        ble_gatts_notify_test_misc_verify_tx_gen(conn_handle, 1, chr1_flags);
 +    }
 +
 +
 +    if (chr2_flags != 0) {
 +        ble_gatts_notify_test_util_verify_sub_event(
 +            conn_handle,
 +            ble_gatts_notify_test_chr_2_def_handle + 1,
 +            BLE_GAP_SUBSCRIBE_REASON_RESTORE,
 +            0, chr2_flags == BLE_GATTS_CLT_CFG_F_NOTIFY,
 +            0, chr2_flags == BLE_GATTS_CLT_CFG_F_INDICATE);
 +    }
 +    if (chr2_tx) {
 +        ble_gatts_notify_test_misc_verify_tx_gen(conn_handle, 2, chr2_flags);
 +    }
 +}
 +
 +TEST_CASE(ble_gatts_notify_test_n)
 +{
 +    uint16_t conn_handle;
 +    uint16_t flags;
 +
 +    ble_gatts_notify_test_misc_init(&conn_handle, 0,
 +                                    BLE_GATTS_CLT_CFG_F_NOTIFY,
 +                                    BLE_GATTS_CLT_CFG_F_NOTIFY);
 +
 +    /* Ensure notifications read back as enabled. */
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_1_def_handle);
 +    TEST_ASSERT(flags == BLE_GATTS_CLT_CFG_F_NOTIFY);
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_2_def_handle);
 +    TEST_ASSERT(flags == BLE_GATTS_CLT_CFG_F_NOTIFY);
 +
 +    /* Update characteristic 1's value. */
 +    ble_gatts_notify_test_chr_1_len = 1;
 +    ble_gatts_notify_test_chr_1_val[0] = 0xab;
 +    ble_gatts_chr_updated(ble_gatts_notify_test_chr_1_def_handle + 1);
 +
 +    /* Verify notification sent properly. */
 +    ble_gatts_notify_test_misc_verify_tx_n(
 +        conn_handle,
 +        ble_gatts_notify_test_chr_1_def_handle + 1,
 +        ble_gatts_notify_test_chr_1_val,
 +        ble_gatts_notify_test_chr_1_len);
 +
 +    /* Update characteristic 2's value. */
 +    ble_gatts_notify_test_chr_2_len = 16;
 +    memcpy(ble_gatts_notify_test_chr_2_val,
 +           ((uint8_t[]){0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}), 16);
 +    ble_gatts_chr_updated(ble_gatts_notify_test_chr_2_def_handle + 1);
 +
 +    /* Verify notification sent properly. */
 +    ble_gatts_notify_test_misc_verify_tx_n(
 +        conn_handle,
 +        ble_gatts_notify_test_chr_2_def_handle + 1,
 +        ble_gatts_notify_test_chr_2_val,
 +        ble_gatts_notify_test_chr_2_len);
 +
 +    /***
 +     * Disconnect, modify characteristic values, and reconnect.  Ensure
 +     * notifications are not sent and are no longer enabled.
 +     */
 +
 +    ble_gatts_notify_test_disconnect(conn_handle,
 +                                     BLE_GATTS_CLT_CFG_F_NOTIFY, 0,
 +                                     BLE_GATTS_CLT_CFG_F_NOTIFY, 0);
 +
 +    /* Update characteristic 1's value. */
 +    ble_gatts_notify_test_chr_1_len = 1;
 +    ble_gatts_notify_test_chr_1_val[0] = 0xdd;
 +    ble_gatts_chr_updated(ble_gatts_notify_test_chr_1_def_handle + 1);
 +
 +    /* Update characteristic 2's value. */
 +    ble_gatts_notify_test_chr_2_len = 16;
 +    memcpy(ble_gatts_notify_test_chr_2_val,
 +           ((uint8_t[]){1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}), 16);
 +    ble_gatts_chr_updated(ble_gatts_notify_test_chr_2_def_handle + 1);
 +
 +    ble_hs_test_util_create_conn(conn_handle, ((uint8_t[]){2,3,4,5,6,7,8,9}),
 +                                 ble_gatts_notify_test_util_gap_event, NULL);
 +
 +    /* Ensure no notifications sent. */
 +    TEST_ASSERT(ble_hs_test_util_prev_tx_dequeue() == NULL);
 +
 +    /* Ensure notifications disabled. */
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_1_def_handle);
 +    TEST_ASSERT(flags == 0);
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_2_def_handle);
 +    TEST_ASSERT(flags == 0);
 +}
 +
 +TEST_CASE(ble_gatts_notify_test_i)
 +{
 +    uint16_t conn_handle;
 +    uint16_t flags;
 +
 +    ble_gatts_notify_test_misc_init(&conn_handle, 0,
 +                                    BLE_GATTS_CLT_CFG_F_INDICATE,
 +                                    BLE_GATTS_CLT_CFG_F_INDICATE);
 +
 +    /* Update characteristic 1's value. */
 +    ble_gatts_notify_test_chr_1_len = 1;
 +    ble_gatts_notify_test_chr_1_val[0] = 0xab;
 +    ble_gatts_chr_updated(ble_gatts_notify_test_chr_1_def_handle + 1);
 +
 +    /* Verify indication sent properly. */
 +    ble_gatts_notify_test_misc_verify_tx_i(
 +        conn_handle,
 +        ble_gatts_notify_test_chr_1_def_handle + 1,
 +        ble_gatts_notify_test_chr_1_val,
 +        ble_gatts_notify_test_chr_1_len);
 +
 +    /* Update characteristic 2's value. */
 +    ble_gatts_notify_test_chr_2_len = 16;
 +    memcpy(ble_gatts_notify_test_chr_2_val,
 +           ((uint8_t[]){0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}), 16);
 +    ble_gatts_chr_updated(ble_gatts_notify_test_chr_2_def_handle + 1);
 +
 +    /* Verify the second indication doesn't get sent until the first is
 +     * confirmed.
 +     */
 +    ble_hs_test_util_tx_all();
 +    TEST_ASSERT(ble_hs_test_util_prev_tx_queue_sz() == 0);
 +
 +    /* Receive the confirmation for the first indication. */
 +    ble_gatts_notify_test_misc_rx_indicate_rsp(
 +        conn_handle,
 +        ble_gatts_notify_test_chr_1_def_handle + 1);
 +
 +    /* Verify indication sent properly. */
 +    ble_hs_test_util_tx_all();
 +    ble_gatts_notify_test_misc_verify_tx_i(
 +        conn_handle,
 +        ble_gatts_notify_test_chr_2_def_handle + 1,
 +        ble_gatts_notify_test_chr_2_val,
 +        ble_gatts_notify_test_chr_2_len);
 +
 +    /* Receive the confirmation for the second indication. */
 +    ble_gatts_notify_test_misc_rx_indicate_rsp(
 +        conn_handle,
 +        ble_gatts_notify_test_chr_2_def_handle + 1);
 +
 +    /* Verify no pending GATT jobs. */
 +    TEST_ASSERT(!ble_gattc_any_jobs());
 +
 +    /***
 +     * Disconnect, modify characteristic values, and reconnect.  Ensure
 +     * indications are not sent and are no longer enabled.
 +     */
 +
 +    ble_gatts_notify_test_disconnect(conn_handle,
 +                                     BLE_GATTS_CLT_CFG_F_INDICATE, 0,
 +                                     BLE_GATTS_CLT_CFG_F_INDICATE, 0);
 +
 +    /* Update characteristic 1's value. */
 +    ble_gatts_notify_test_chr_1_len = 1;
 +    ble_gatts_notify_test_chr_1_val[0] = 0xdd;
 +    ble_gatts_chr_updated(ble_gatts_notify_test_chr_1_def_handle + 1);
 +
 +    /* Update characteristic 2's value. */
 +    ble_gatts_notify_test_chr_2_len = 16;
 +    memcpy(ble_gatts_notify_test_chr_2_val,
 +           ((uint8_t[]){1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}), 16);
 +    ble_gatts_chr_updated(ble_gatts_notify_test_chr_2_def_handle + 1);
 +
 +    ble_hs_test_util_create_conn(conn_handle, ((uint8_t[]){2,3,4,5,6,7,8,9}),
 +                                 ble_gatts_notify_test_util_gap_event, NULL);
 +
 +    /* Ensure no indications sent. */
 +    TEST_ASSERT(ble_hs_test_util_prev_tx_dequeue() == NULL);
 +
 +    /* Ensure indications disabled. */
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_1_def_handle);
 +    TEST_ASSERT(flags == 0);
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_2_def_handle);
 +    TEST_ASSERT(flags == 0);
 +}
 +
 +TEST_CASE(ble_gatts_notify_test_bonded_n)
 +{
 +    uint16_t conn_handle;
 +    uint16_t flags;
 +
 +    ble_gatts_notify_test_misc_init(&conn_handle, 1,
 +                                    BLE_GATTS_CLT_CFG_F_NOTIFY,
 +                                    BLE_GATTS_CLT_CFG_F_NOTIFY);
 +
 +    /* Disconnect. */
 +    ble_gatts_notify_test_disconnect(conn_handle,
 +                                     BLE_GATTS_CLT_CFG_F_NOTIFY, 0,
 +                                     BLE_GATTS_CLT_CFG_F_NOTIFY, 0);
 +
 +    /* Ensure both CCCDs still persisted. */
 +    TEST_ASSERT(ble_hs_test_util_store_num_cccds == 2);
 +
 +    /* Update characteristic 1's value. */
 +    ble_gatts_notify_test_chr_1_len = 1;
 +    ble_gatts_notify_test_chr_1_val[0] = 0xdd;
 +    ble_gatts_chr_updated(ble_gatts_notify_test_chr_1_def_handle + 1);
 +
 +    /* Update characteristic 2's value. */
 +    ble_gatts_notify_test_chr_2_len = 16;
 +    memcpy(ble_gatts_notify_test_chr_2_val,
 +           ((uint8_t[]){1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}), 16);
 +    ble_gatts_chr_updated(ble_gatts_notify_test_chr_2_def_handle + 1);
 +
 +    /* Reconnect; ensure notifications don't get sent while unbonded and that
 +     * notifications appear disabled.
 +     */
 +
 +    ble_hs_test_util_create_conn(conn_handle, ((uint8_t[]){2,3,4,5,6,7,8,9}),
 +                                 ble_gatts_notify_test_util_gap_event, NULL);
 +
 +    ble_gatts_notify_test_num_events = 0;
 +    /* Ensure no notifications sent. */
 +    TEST_ASSERT(ble_hs_test_util_prev_tx_dequeue() == NULL);
 +
 +    /* Ensure notifications disabled. */
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_1_def_handle);
 +    TEST_ASSERT(flags == 0);
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_2_def_handle);
 +    TEST_ASSERT(flags == 0);
 +
 +    /* Simulate a successful encryption procedure (bonding restoration). */
 +    ble_gatts_notify_test_restore_bonding(conn_handle,
 +                                          BLE_GATTS_CLT_CFG_F_NOTIFY, 1,
 +                                          BLE_GATTS_CLT_CFG_F_NOTIFY, 1);
 +
 +    /* Ensure notifications enabled. */
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_1_def_handle);
 +    TEST_ASSERT(flags == BLE_GATTS_CLT_CFG_F_NOTIFY);
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_2_def_handle);
 +    TEST_ASSERT(flags == BLE_GATTS_CLT_CFG_F_NOTIFY);
 +
 +    /* Ensure both CCCDs still persisted. */
 +    TEST_ASSERT(ble_hs_test_util_store_num_cccds == 2);
 +}
 +
 +TEST_CASE(ble_gatts_notify_test_bonded_i)
 +{
 +    uint16_t conn_handle;
 +    uint16_t flags;
 +
 +    ble_gatts_notify_test_misc_init(&conn_handle, 1,
 +                                    BLE_GATTS_CLT_CFG_F_INDICATE,
 +                                    BLE_GATTS_CLT_CFG_F_INDICATE);
 +
 +    /* Disconnect. */
 +    ble_gatts_notify_test_disconnect(conn_handle,
 +                                     BLE_GATTS_CLT_CFG_F_INDICATE, 0,
 +                                     BLE_GATTS_CLT_CFG_F_INDICATE, 0);
 +
 +    /* Ensure both CCCDs still persisted. */
 +    TEST_ASSERT(ble_hs_test_util_store_num_cccds == 2);
 +
 +    /* Update characteristic 1's value. */
 +    ble_gatts_notify_test_chr_1_len = 1;
 +    ble_gatts_notify_test_chr_1_val[0] = 0xab;
 +    ble_gatts_chr_updated(ble_gatts_notify_test_chr_1_def_handle + 1);
 +
 +    /* Update characteristic 2's value. */
 +    ble_gatts_notify_test_chr_2_len = 16;
 +    memcpy(ble_gatts_notify_test_chr_2_val,
 +           ((uint8_t[]){0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}), 16);
 +    ble_gatts_chr_updated(ble_gatts_notify_test_chr_2_def_handle + 1);
 +
 +    /* Reconnect; ensure notifications don't get sent while unbonded and that
 +     * notifications appear disabled.
 +     */
 +
 +    ble_hs_test_util_create_conn(conn_handle, ((uint8_t[]){2,3,4,5,6,7,8,9}),
 +                                 ble_gatts_notify_test_util_gap_event, NULL);
 +
 +    /* Ensure no indications sent. */
 +    TEST_ASSERT(ble_hs_test_util_prev_tx_dequeue() == NULL);
 +
 +    /* Ensure notifications disabled. */
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_1_def_handle);
 +    TEST_ASSERT(flags == 0);
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_2_def_handle);
 +    TEST_ASSERT(flags == 0);
 +
 +    /* Simulate a successful encryption procedure (bonding restoration). */
 +    ble_gatts_notify_test_restore_bonding(conn_handle,
 +                                          BLE_GATTS_CLT_CFG_F_INDICATE, 1,
 +                                          BLE_GATTS_CLT_CFG_F_INDICATE, 0);
 +
 +    /* Verify the second indication doesn't get sent until the first is
 +     * confirmed.
 +     */
 +    ble_hs_test_util_tx_all();
 +    TEST_ASSERT(ble_hs_test_util_prev_tx_queue_sz() == 0);
 +
 +    /* Receive the confirmation for the first indication. */
 +    ble_gatts_notify_test_misc_rx_indicate_rsp(
 +        conn_handle,
 +        ble_gatts_notify_test_chr_1_def_handle + 1);
 +
 +    /* Verify indication sent properly. */
 +    ble_hs_test_util_tx_all();
 +    ble_gatts_notify_test_misc_verify_tx_i(
 +        conn_handle,
 +        ble_gatts_notify_test_chr_2_def_handle + 1,
 +        ble_gatts_notify_test_chr_2_val,
 +        ble_gatts_notify_test_chr_2_len);
 +
 +    /* Receive the confirmation for the second indication. */
 +    ble_gatts_notify_test_misc_rx_indicate_rsp(
 +        conn_handle,
 +        ble_gatts_notify_test_chr_2_def_handle + 1);
 +
 +    /* Verify no pending GATT jobs. */
 +    TEST_ASSERT(!ble_gattc_any_jobs());
 +
 +    /* Ensure notifications enabled. */
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_1_def_handle);
 +    TEST_ASSERT(flags == BLE_GATTS_CLT_CFG_F_INDICATE);
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_2_def_handle);
 +    TEST_ASSERT(flags == BLE_GATTS_CLT_CFG_F_INDICATE);
 +
 +    /* Ensure both CCCDs still persisted. */
 +    TEST_ASSERT(ble_hs_test_util_store_num_cccds == 2);
 +}
 +
 +TEST_CASE(ble_gatts_notify_test_bonded_i_no_ack)
 +{
 +    struct ble_store_value_cccd value_cccd;
 +    struct ble_store_key_cccd key_cccd;
 +    uint16_t conn_handle;
 +    uint16_t flags;
 +    int rc;
 +
 +    ble_gatts_notify_test_misc_init(&conn_handle, 1,
 +                                    BLE_GATTS_CLT_CFG_F_INDICATE, 0);
 +
 +    /* Update characteristic 1's value. */
 +    ble_gatts_notify_test_chr_1_len = 1;
 +    ble_gatts_notify_test_chr_1_val[0] = 0xab;
 +    ble_gatts_chr_updated(ble_gatts_notify_test_chr_1_def_handle + 1);
 +
 +    /* Verify indication sent properly. */
 +    ble_hs_test_util_tx_all();
 +    ble_gatts_notify_test_misc_verify_tx_i(
 +        conn_handle,
 +        ble_gatts_notify_test_chr_1_def_handle + 1,
 +        ble_gatts_notify_test_chr_1_val,
 +        ble_gatts_notify_test_chr_1_len);
 +
 +    /* Verify 'updated' state is still persisted. */
 +    key_cccd.peer_addr_type = BLE_STORE_ADDR_TYPE_NONE;
 +    key_cccd.chr_val_handle = ble_gatts_notify_test_chr_1_def_handle + 1;
 +    key_cccd.idx = 0;
 +
 +    rc = ble_store_read_cccd(&key_cccd, &value_cccd);
 +    TEST_ASSERT_FATAL(rc == 0);
 +    TEST_ASSERT(value_cccd.value_changed);
 +
 +    /* Disconnect. */
 +    ble_gatts_notify_test_disconnect(conn_handle,
 +                                     BLE_GATTS_CLT_CFG_F_INDICATE, 1, 0, 0);
 +
 +    /* Ensure CCCD still persisted. */
 +    TEST_ASSERT(ble_hs_test_util_store_num_cccds == 1);
 +
 +    /* Reconnect. */
 +    ble_hs_test_util_create_conn(conn_handle, ((uint8_t[]){2,3,4,5,6,7,8,9}),
 +                                 ble_gatts_notify_test_util_gap_event, NULL);
 +
 +    /* Simulate a successful encryption procedure (bonding restoration). */
 +    ble_gatts_notify_test_restore_bonding(conn_handle,
 +                                          BLE_GATTS_CLT_CFG_F_INDICATE, 1,
 +                                          0, 0);
 +
 +    /* Receive the confirmation for the indication. */
 +    ble_gatts_notify_test_misc_rx_indicate_rsp(
 +        conn_handle,
 +        ble_gatts_notify_test_chr_1_def_handle + 1);
 +
 +    /* Verify no pending GATT jobs. */
 +    TEST_ASSERT(!ble_gattc_any_jobs());
 +
 +    /* Ensure indication enabled. */
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_1_def_handle);
 +    TEST_ASSERT(flags == BLE_GATTS_CLT_CFG_F_INDICATE);
 +    flags = ble_gatts_notify_test_misc_read_notify(
 +        conn_handle, ble_gatts_notify_test_chr_2_def_handle);
 +    TEST_ASSERT(flags == 0);
 +
 +    /* Ensure CCCD still persisted. */
 +    TEST_ASSERT(ble_hs_test_util_store_num_cccds == 1);
 +
 +    /* Verify 'updated' state is no longer persisted. */
 +    rc = ble_store_read_cccd(&key_cccd, &value_cccd);
 +    TEST_ASSERT_FATAL(rc == 0);
 +    TEST_ASSERT(!value_cccd.value_changed);
 +}
 +
++TEST_CASE(ble_gatts_notify_test_disallowed)
++{
++    uint16_t chr1_val_handle;
++    uint16_t chr2_val_handle;
++    uint16_t chr3_val_handle;
++    int rc;
++
++    const struct ble_gatt_svc_def svcs[] = { {
++        .type = BLE_GATT_SVC_TYPE_PRIMARY,
++        .uuid128 = BLE_UUID16(0x1234),
++        .characteristics = (struct ble_gatt_chr_def[]) { {
++            .uuid128 = BLE_UUID16(1),
++            .access_cb = ble_gatts_notify_test_misc_access,
++            .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
++            .val_handle = &chr1_val_handle,
++        }, {
++            .uuid128 = BLE_UUID16(2),
++            .access_cb = ble_gatts_notify_test_misc_access,
++            .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_INDICATE,
++            .val_handle = &chr2_val_handle,
++        }, {
++            .uuid128 = BLE_UUID16(3),
++            .access_cb = ble_gatts_notify_test_misc_access,
++            .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY |
++                     BLE_GATT_CHR_F_INDICATE,
++            .val_handle = &chr3_val_handle,
++        }, {
++            0
++        } },
++    }, {
++        0
++    } };
++
++    ble_hs_test_util_init();
++
++    rc = ble_gatts_register_svcs(svcs, NULL, NULL);
++    TEST_ASSERT_FATAL(rc == 0);
++    TEST_ASSERT_FATAL(chr1_val_handle != 0);
++    TEST_ASSERT_FATAL(chr2_val_handle != 0);
++    TEST_ASSERT_FATAL(chr3_val_handle != 0);
++
++    ble_gatts_start();
++
++    ble_hs_test_util_create_conn(2, ble_gatts_notify_test_peer_addr,
++                                 ble_gatts_notify_test_util_gap_event, NULL);
++
++    /* Attempt to enable notifications on chr1 should succeed. */
++    ble_gatts_notify_test_misc_try_enable_notify(
++        2, chr1_val_handle - 1, BLE_GATTS_CLT_CFG_F_NOTIFY, 0);
++
++    /* Attempt to enable indications on chr1 should fail. */
++    ble_gatts_notify_test_misc_try_enable_notify(
++        2, chr1_val_handle - 1, BLE_GATTS_CLT_CFG_F_INDICATE, 1);
++
++    /* Attempt to enable notifications on chr2 should fail. */
++    ble_gatts_notify_test_misc_try_enable_notify(
++        2, chr2_val_handle - 1, BLE_GATTS_CLT_CFG_F_NOTIFY, 1);
++
++    /* Attempt to enable indications on chr2 should succeed. */
++    ble_gatts_notify_test_misc_try_enable_notify(
++        2, chr2_val_handle - 1, BLE_GATTS_CLT_CFG_F_INDICATE, 0);
++
++    /* Attempt to enable notifications on chr3 should succeed. */
++    ble_gatts_notify_test_misc_try_enable_notify(
++        2, chr3_val_handle - 1, BLE_GATTS_CLT_CFG_F_NOTIFY, 0);
++
++    /* Attempt to enable indications on chr3 should succeed. */
++    ble_gatts_notify_test_misc_try_enable_notify(
++        2, chr3_val_handle - 1, BLE_GATTS_CLT_CFG_F_INDICATE, 0);
++}
++
 +TEST_SUITE(ble_gatts_notify_suite)
 +{
 +    tu_suite_set_post_test_cb(ble_hs_test_util_post_test, NULL);
 +
 +    ble_gatts_notify_test_n();
 +    ble_gatts_notify_test_i();
 +
 +    ble_gatts_notify_test_bonded_n();
 +    ble_gatts_notify_test_bonded_i();
 +
 +    ble_gatts_notify_test_bonded_i_no_ack();
 +
++    ble_gatts_notify_test_disallowed();
++
 +    /* XXX: Test corner cases:
 +     *     o Bonding after CCCD configuration.
 +     *     o Disconnect prior to rx of indicate ack.
 +     */
 +}
 +
 +int
 +ble_gatts_notify_test_all(void)
 +{
 +    ble_gatts_notify_suite();
 +
 +    return tu_any_failed;
 +}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/c5901fcc/net/nimble/host/test/src/ble_hs_test_util.c
----------------------------------------------------------------------
diff --cc net/nimble/host/test/src/ble_hs_test_util.c
index eed1262,0000000..81e059a
mode 100644,000000..100644
--- a/net/nimble/host/test/src/ble_hs_test_util.c
+++ b/net/nimble/host/test/src/ble_hs_test_util.c
@@@ -1,1404 -1,0 +1,1604 @@@
 +/**
 + * 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 "stats/stats.h"
 +#include "testutil/testutil.h"
 +#include "nimble/ble.h"
 +#include "nimble/hci_common.h"
 +#include "nimble/ble_hci_trans.h"
 +#include "host/ble_hs_adv.h"
 +#include "host/ble_hs_id.h"
 +#include "transport/ram/ble_hci_ram.h"
 +#include "ble_hs_test_util.h"
 +
 +/* Our global device address. */
 +uint8_t g_dev_addr[BLE_DEV_ADDR_LEN];
 +
 +#define BLE_HS_TEST_UTIL_PUB_ADDR_VAL { 0x0a, 0x54, 0xab, 0x49, 0x7f, 0x06 }
 +
 +static const uint8_t ble_hs_test_util_pub_addr[BLE_DEV_ADDR_LEN] =
 +    BLE_HS_TEST_UTIL_PUB_ADDR_VAL;
 +
 +#define BLE_HS_TEST_UTIL_LE_OPCODE(ocf) \
 +    ble_hs_hci_util_opcode_join(BLE_HCI_OGF_LE, (ocf))
 +
 +struct os_eventq ble_hs_test_util_evq;
 +
 +static STAILQ_HEAD(, os_mbuf_pkthdr) ble_hs_test_util_prev_tx_queue;
 +struct os_mbuf *ble_hs_test_util_prev_tx_cur;
 +
 +#define BLE_HS_TEST_UTIL_PREV_HCI_TX_CNT      64
 +static uint8_t
 +ble_hs_test_util_prev_hci_tx[BLE_HS_TEST_UTIL_PREV_HCI_TX_CNT][260];
 +int ble_hs_test_util_num_prev_hci_txes;
 +
 +uint8_t ble_hs_test_util_cur_hci_tx[260];
 +
 +const struct ble_gap_adv_params ble_hs_test_util_adv_params = {
 +    .conn_mode = BLE_GAP_CONN_MODE_UND,
 +    .disc_mode = BLE_GAP_DISC_MODE_GEN,
 +
 +    .itvl_min = 0,
 +    .itvl_max = 0,
 +    .channel_map = 0,
 +    .filter_policy = 0,
 +    .high_duty_cycle = 0,
 +};
 +
 +void
 +ble_hs_test_util_prev_tx_enqueue(struct os_mbuf *om)
 +{
 +    struct os_mbuf_pkthdr *omp;
 +
 +    assert(OS_MBUF_IS_PKTHDR(om));
 +
 +    omp = OS_MBUF_PKTHDR(om);
 +    if (STAILQ_EMPTY(&ble_hs_test_util_prev_tx_queue)) {
 +        STAILQ_INSERT_HEAD(&ble_hs_test_util_prev_tx_queue, omp, omp_next);
 +    } else {
 +        STAILQ_INSERT_TAIL(&ble_hs_test_util_prev_tx_queue, omp, omp_next);
 +    }
 +}
 +
 +static struct os_mbuf *
 +ble_hs_test_util_prev_tx_dequeue_once(struct hci_data_hdr *out_hci_hdr)
 +{
 +    struct os_mbuf_pkthdr *omp;
 +    struct os_mbuf *om;
 +    int rc;
 +
 +    omp = STAILQ_FIRST(&ble_hs_test_util_prev_tx_queue);
 +    if (omp == NULL) {
 +        return NULL;
 +    }
 +    STAILQ_REMOVE_HEAD(&ble_hs_test_util_prev_tx_queue, omp_next);
 +
 +    om = OS_MBUF_PKTHDR_TO_MBUF(omp);
 +
 +    rc = ble_hs_hci_util_data_hdr_strip(om, out_hci_hdr);
 +    TEST_ASSERT_FATAL(rc == 0);
 +    TEST_ASSERT_FATAL(out_hci_hdr->hdh_len == OS_MBUF_PKTLEN(om));
 +
 +    return om;
 +}
 +
 +struct os_mbuf *
 +ble_hs_test_util_prev_tx_dequeue(void)
 +{
 +    struct ble_l2cap_hdr l2cap_hdr;
 +    struct hci_data_hdr hci_hdr;
 +    struct os_mbuf *om;
 +    uint8_t pb;
 +    int rc;
 +
 +    os_mbuf_free_chain(ble_hs_test_util_prev_tx_cur);
 +
 +    om = ble_hs_test_util_prev_tx_dequeue_once(&hci_hdr);
 +    if (om != NULL) {
 +        pb = BLE_HCI_DATA_PB(hci_hdr.hdh_handle_pb_bc);
 +        TEST_ASSERT_FATAL(pb == BLE_HCI_PB_FIRST_NON_FLUSH);
 +
 +        rc = ble_l2cap_parse_hdr(om, 0, &l2cap_hdr);
 +        TEST_ASSERT_FATAL(rc == 0);
 +
 +        os_mbuf_adj(om, BLE_L2CAP_HDR_SZ);
 +
 +        ble_hs_test_util_prev_tx_cur = om;
 +        while (OS_MBUF_PKTLEN(ble_hs_test_util_prev_tx_cur) <
 +               l2cap_hdr.blh_len) {
 +
 +            om = ble_hs_test_util_prev_tx_dequeue_once(&hci_hdr);
 +            TEST_ASSERT_FATAL(om != NULL);
 +
 +            pb = BLE_HCI_DATA_PB(hci_hdr.hdh_handle_pb_bc);
 +            TEST_ASSERT_FATAL(pb == BLE_HCI_PB_MIDDLE);
 +
 +            os_mbuf_concat(ble_hs_test_util_prev_tx_cur, om);
 +        }
 +    } else {
 +        ble_hs_test_util_prev_tx_cur = NULL;
 +    }
 +
 +    return ble_hs_test_util_prev_tx_cur;
 +}
 +
 +struct os_mbuf *
 +ble_hs_test_util_prev_tx_dequeue_pullup(void)
 +{
 +    struct os_mbuf *om;
 +
 +    om = ble_hs_test_util_prev_tx_dequeue();
 +    if (om != NULL) {
 +        om = os_mbuf_pullup(om, OS_MBUF_PKTLEN(om));
 +        TEST_ASSERT_FATAL(om != NULL);
 +        ble_hs_test_util_prev_tx_cur = om;
 +    }
 +
 +    return om;
 +}
 +
 +int
 +ble_hs_test_util_prev_tx_queue_sz(void)
 +{
 +    struct os_mbuf_pkthdr *omp;
 +    int cnt;
 +
 +    cnt = 0;
 +    STAILQ_FOREACH(omp, &ble_hs_test_util_prev_tx_queue, omp_next) {
 +        cnt++;
 +    }
 +
 +    return cnt;
 +}
 +
 +void
 +ble_hs_test_util_prev_tx_queue_clear(void)
 +{
 +    ble_hs_test_util_tx_all();
 +    while (!STAILQ_EMPTY(&ble_hs_test_util_prev_tx_queue)) {
 +        ble_hs_test_util_prev_tx_dequeue();
 +    }
 +}
 +
 +void *
 +ble_hs_test_util_get_first_hci_tx(void)
 +{
 +    if (ble_hs_test_util_num_prev_hci_txes == 0) {
 +        return NULL;
 +    }
 +
 +    memcpy(ble_hs_test_util_cur_hci_tx, ble_hs_test_util_prev_hci_tx[0],
 +           sizeof ble_hs_test_util_cur_hci_tx);
 +
 +    ble_hs_test_util_num_prev_hci_txes--;
 +    if (ble_hs_test_util_num_prev_hci_txes > 0) {
 +        memmove(
 +            ble_hs_test_util_prev_hci_tx, ble_hs_test_util_prev_hci_tx + 1,
 +            sizeof ble_hs_test_util_prev_hci_tx[0] *
 +            ble_hs_test_util_num_prev_hci_txes);
 +    }
 +
 +    return ble_hs_test_util_cur_hci_tx;
 +}
 +
 +void *
 +ble_hs_test_util_get_last_hci_tx(void)
 +{
 +    if (ble_hs_test_util_num_prev_hci_txes == 0) {
 +        return NULL;
 +    }
 +
 +    ble_hs_test_util_num_prev_hci_txes--;
 +    memcpy(ble_hs_test_util_cur_hci_tx,
 +           ble_hs_test_util_prev_hci_tx + ble_hs_test_util_num_prev_hci_txes,
 +           sizeof ble_hs_test_util_cur_hci_tx);
 +
 +    return ble_hs_test_util_cur_hci_tx;
 +}
 +
 +void
 +ble_hs_test_util_enqueue_hci_tx(void *cmd)
 +{
 +    TEST_ASSERT_FATAL(ble_hs_test_util_num_prev_hci_txes <
 +                      BLE_HS_TEST_UTIL_PREV_HCI_TX_CNT);
 +    memcpy(ble_hs_test_util_prev_hci_tx + ble_hs_test_util_num_prev_hci_txes,
 +           cmd, 260);
 +
 +    ble_hs_test_util_num_prev_hci_txes++;
 +}
 +
 +void
 +ble_hs_test_util_prev_hci_tx_clear(void)
 +{
 +    ble_hs_test_util_num_prev_hci_txes = 0;
 +}
 +
 +static void
 +ble_hs_test_util_rx_hci_evt(uint8_t *evt)
 +{
 +    uint8_t *evbuf;
 +    int totlen;
 +    int rc;
 +
 +    totlen = BLE_HCI_EVENT_HDR_LEN + evt[1];
 +    TEST_ASSERT_FATAL(totlen <= UINT8_MAX + BLE_HCI_EVENT_HDR_LEN);
 +
 +    if (os_started()) {
 +        evbuf = ble_hci_trans_buf_alloc(
 +            BLE_HCI_TRANS_BUF_EVT_LO);
 +        TEST_ASSERT_FATAL(evbuf != NULL);
 +
 +        memcpy(evbuf, evt, totlen);
 +        rc = ble_hci_trans_ll_evt_tx(evbuf);
 +    } else {
 +        rc = ble_hs_hci_evt_process(evt);
 +    }
 +
 +    TEST_ASSERT_FATAL(rc == 0);
 +}
 +
 +void
 +ble_hs_test_util_build_cmd_complete(uint8_t *dst, int len,
 +                                    uint8_t param_len, uint8_t num_pkts,
 +                                    uint16_t opcode)
 +{
 +    TEST_ASSERT(len >= BLE_HCI_EVENT_CMD_COMPLETE_HDR_LEN);
 +
 +    dst[0] = BLE_HCI_EVCODE_COMMAND_COMPLETE;
 +    dst[1] = 3 + param_len;
 +    dst[2] = num_pkts;
 +    htole16(dst + 3, opcode);
 +}
 +
 +void
 +ble_hs_test_util_build_cmd_status(uint8_t *dst, int len,
 +                                  uint8_t status, uint8_t num_pkts,
 +                                  uint16_t opcode)
 +{
 +    TEST_ASSERT(len >= BLE_HCI_EVENT_CMD_STATUS_LEN);
 +
 +    dst[0] = BLE_HCI_EVCODE_COMMAND_STATUS;
 +    dst[1] = BLE_HCI_EVENT_CMD_STATUS_LEN;
 +    dst[2] = status;
 +    dst[3] = num_pkts;
 +    htole16(dst + 4, opcode);
 +}
 +
 +#define BLE_HS_TEST_UTIL_PHONY_ACK_MAX  64
 +struct ble_hs_test_util_phony_ack {
 +    uint16_t opcode;
 +    uint8_t status;
 +    uint8_t evt_params[256];
 +    uint8_t evt_params_len;
 +};
 +
 +static struct ble_hs_test_util_phony_ack
 +ble_hs_test_util_phony_acks[BLE_HS_TEST_UTIL_PHONY_ACK_MAX];
 +static int ble_hs_test_util_num_phony_acks;
 +
 +static int
 +ble_hs_test_util_phony_ack_cb(uint8_t *ack, int ack_buf_len)
 +{
 +    struct ble_hs_test_util_phony_ack *entry;
 +
 +    if (ble_hs_test_util_num_phony_acks == 0) {
 +        return BLE_HS_ETIMEOUT_HCI;
 +    }
 +
 +    entry = ble_hs_test_util_phony_acks;
 +
 +    ble_hs_test_util_build_cmd_complete(ack, 256,
 +                                        entry->evt_params_len + 1, 1,
 +                                        entry->opcode);
 +    ack[BLE_HCI_EVENT_CMD_COMPLETE_HDR_LEN] = entry->status;
 +    memcpy(ack + BLE_HCI_EVENT_CMD_COMPLETE_HDR_LEN + 1, entry->evt_params,
 +           entry->evt_params_len);
 +
 +    ble_hs_test_util_num_phony_acks--;
 +    if (ble_hs_test_util_num_phony_acks > 0) {
 +        memmove(ble_hs_test_util_phony_acks, ble_hs_test_util_phony_acks + 1,
 +                sizeof *entry * ble_hs_test_util_num_phony_acks);
 +    }
 +
 +    return 0;
 +}
 +
 +void
 +ble_hs_test_util_set_ack_params(uint16_t opcode, uint8_t status, void *params,
 +                                uint8_t params_len)
 +{
 +    struct ble_hs_test_util_phony_ack *ack;
 +
 +    ack = ble_hs_test_util_phony_acks + 0;
 +    ack->opcode = opcode;
 +    ack->status = status;
 +
 +    if (params == NULL || params_len == 0) {
 +        ack->evt_params_len = 0;
 +    } else {
 +        memcpy(ack->evt_params, params, params_len);
 +        ack->evt_params_len = params_len;
 +    }
 +    ble_hs_test_util_num_phony_acks = 1;
 +
 +    ble_hs_hci_set_phony_ack_cb(ble_hs_test_util_phony_ack_cb);
 +}
 +
 +void
 +ble_hs_test_util_set_ack(uint16_t opcode, uint8_t status)
 +{
 +    ble_hs_test_util_set_ack_params(opcode, status, NULL, 0);
 +}
 +
 +static void
 +ble_hs_test_util_set_ack_seq(struct ble_hs_test_util_phony_ack *acks)
 +{
 +    int i;
 +
 +    for (i = 0; acks[i].opcode != 0; i++) {
 +        ble_hs_test_util_phony_acks[i] = acks[i];
 +    }
 +    ble_hs_test_util_num_phony_acks = i;
 +
 +    ble_hs_hci_set_phony_ack_cb(ble_hs_test_util_phony_ack_cb);
 +}
 +
 +void
 +ble_hs_test_util_create_rpa_conn(uint16_t handle, uint8_t own_addr_type,
 +                                 const uint8_t *our_rpa,
 +                                 uint8_t peer_addr_type,
 +                                 const uint8_t *peer_id_addr,
 +                                 const uint8_t *peer_rpa,
 +                                 ble_gap_event_fn *cb, void *cb_arg)
 +{
 +    struct hci_le_conn_complete evt;
 +    int rc;
 +
 +    ble_hs_test_util_connect(own_addr_type, peer_addr_type,
 +                             peer_id_addr, 0, NULL, cb, cb_arg, 0);
 +
 +    memset(&evt, 0, sizeof evt);
 +    evt.subevent_code = BLE_HCI_LE_SUBEV_CONN_COMPLETE;
 +    evt.status = BLE_ERR_SUCCESS;
 +    evt.connection_handle = handle;
 +    evt.role = BLE_HCI_LE_CONN_COMPLETE_ROLE_MASTER;
 +    evt.peer_addr_type = peer_addr_type;
 +    memcpy(evt.peer_addr, peer_id_addr, 6);
 +    evt.conn_itvl = BLE_GAP_INITIAL_CONN_ITVL_MAX;
 +    evt.conn_latency = BLE_GAP_INITIAL_CONN_LATENCY;
 +    evt.supervision_timeout = BLE_GAP_INITIAL_SUPERVISION_TIMEOUT;
 +    memcpy(evt.local_rpa, our_rpa, 6);
 +    memcpy(evt.peer_rpa, peer_rpa, 6);
 +
 +    rc = ble_gap_rx_conn_complete(&evt);
 +    TEST_ASSERT(rc == 0);
 +
 +    ble_hs_test_util_prev_hci_tx_clear();
 +}
 +
 +void
- ble_hs_test_util_create_conn(uint16_t handle, uint8_t *peer_id_addr,
++ble_hs_test_util_create_conn(uint16_t handle, const uint8_t *peer_id_addr,
 +                             ble_gap_event_fn *cb, void *cb_arg)
 +{
 +    static uint8_t null_addr[6];
 +
 +    ble_hs_test_util_create_rpa_conn(handle, BLE_ADDR_TYPE_PUBLIC, null_addr,
 +                                     BLE_ADDR_TYPE_PUBLIC, peer_id_addr,
 +                                     null_addr, cb, cb_arg);
 +}
 +
 +static void
 +ble_hs_test_util_conn_params_dflt(struct ble_gap_conn_params *conn_params)
 +{
 +    conn_params->scan_itvl = 0x0010;
 +    conn_params->scan_window = 0x0010;
 +    conn_params->itvl_min = BLE_GAP_INITIAL_CONN_ITVL_MIN;
 +    conn_params->itvl_max = BLE_GAP_INITIAL_CONN_ITVL_MAX;
 +    conn_params->latency = BLE_GAP_INITIAL_CONN_LATENCY;
 +    conn_params->supervision_timeout = BLE_GAP_INITIAL_SUPERVISION_TIMEOUT;
 +    conn_params->min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN;
 +    conn_params->max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN;
 +}
 +
 +static void
 +ble_hs_test_util_hcc_from_conn_params(
 +    struct hci_create_conn *hcc, uint8_t own_addr_type, uint8_t 
peer_addr_type,
 +    const uint8_t *peer_addr, const struct ble_gap_conn_params *conn_params)
 +{
 +    hcc->scan_itvl = conn_params->scan_itvl;
 +    hcc->scan_window = conn_params->scan_window;
 +
 +    if (peer_addr_type == BLE_GAP_ADDR_TYPE_WL) {
 +        hcc->filter_policy = BLE_HCI_CONN_FILT_USE_WL;
 +        hcc->peer_addr_type = 0;
 +        memset(hcc->peer_addr, 0, 6);
 +    } else {
 +        hcc->filter_policy = BLE_HCI_CONN_FILT_NO_WL;
 +        hcc->peer_addr_type = peer_addr_type;
 +        memcpy(hcc->peer_addr, peer_addr, 6);
 +    }
 +    hcc->own_addr_type = own_addr_type;
 +    hcc->conn_itvl_min = conn_params->itvl_min;
 +    hcc->conn_itvl_max = conn_params->itvl_max;
 +    hcc->conn_latency = conn_params->latency;
 +    hcc->supervision_timeout = conn_params->supervision_timeout;
 +    hcc->min_ce_len = conn_params->min_ce_len;
 +    hcc->max_ce_len = conn_params->max_ce_len;
 +}
 +
 +void
 +ble_hs_test_util_verify_tx_create_conn(const struct hci_create_conn *exp)
 +{
 +    uint8_t param_len;
 +    uint8_t *param;
 +
 +    param = ble_hs_test_util_verify_tx_hci(BLE_HCI_OGF_LE,
 +                                           BLE_HCI_OCF_LE_CREATE_CONN,
 +                                           &param_len);
 +    TEST_ASSERT(param_len == BLE_HCI_CREATE_CONN_LEN);
 +
 +    TEST_ASSERT(le16toh(param + 0) == exp->scan_itvl);
 +    TEST_ASSERT(le16toh(param + 2) == exp->scan_window);
 +    TEST_ASSERT(param[4] == exp->filter_policy);
 +    TEST_ASSERT(param[5] == exp->peer_addr_type);
 +    TEST_ASSERT(memcmp(param + 6, exp->peer_addr, 6) == 0);
 +    TEST_ASSERT(param[12] == exp->own_addr_type);
 +    TEST_ASSERT(le16toh(param + 13) == exp->conn_itvl_min);
 +    TEST_ASSERT(le16toh(param + 15) == exp->conn_itvl_max);
 +    TEST_ASSERT(le16toh(param + 17) == exp->conn_latency);
 +    TEST_ASSERT(le16toh(param + 19) == exp->supervision_timeout);
 +    TEST_ASSERT(le16toh(param + 21) == exp->min_ce_len);
 +    TEST_ASSERT(le16toh(param + 23) == exp->max_ce_len);
 +}
 +
 +int
 +ble_hs_test_util_connect(uint8_t own_addr_type, uint8_t peer_addr_type,
 +                         const uint8_t *peer_addr, int32_t duration_ms,
 +                         const struct ble_gap_conn_params *params,
 +                         ble_gap_event_fn *cb, void *cb_arg,
 +                         uint8_t ack_status)
 +{
 +    struct ble_gap_conn_params dflt_params;
 +    struct hci_create_conn hcc;
 +    int rc;
 +
 +    /* This function ensures the most recently sent HCI command is the 
expected
 +     * create connection command.  If the current test case has unverified HCI
 +     * commands, assume we are not interested in them and clear the queue.
 +     */
 +    ble_hs_test_util_prev_hci_tx_clear();
 +
 +    ble_hs_test_util_set_ack(
 +        ble_hs_hci_util_opcode_join(BLE_HCI_OGF_LE,
 +                                    BLE_HCI_OCF_LE_CREATE_CONN),
 +        ack_status);
 +
 +    rc = ble_gap_connect(own_addr_type, peer_addr_type, peer_addr, 
duration_ms,
 +                         params, cb, cb_arg);
 +
 +    TEST_ASSERT(rc == BLE_HS_HCI_ERR(ack_status));
 +
 +    if (params == NULL) {
 +        ble_hs_test_util_conn_params_dflt(&dflt_params);
 +        params = &dflt_params;
 +    }
 +
 +    ble_hs_test_util_hcc_from_conn_params(&hcc, own_addr_type,
 +                                          peer_addr_type, peer_addr, params);
 +    ble_hs_test_util_verify_tx_create_conn(&hcc);
 +
 +    return rc;
 +}
 +
 +int
 +ble_hs_test_util_conn_cancel(uint8_t ack_status)
 +{
 +    int rc;
 +
 +    ble_hs_test_util_set_ack(
 +        ble_hs_hci_util_opcode_join(BLE_HCI_OGF_LE,
 +                                    BLE_HCI_OCF_LE_CREATE_CONN_CANCEL),
 +        ack_status);
 +
 +    rc = ble_gap_conn_cancel();
 +    return rc;
 +}
 +
 +void
 +ble_hs_test_util_conn_cancel_full(void)
 +{
 +    struct hci_le_conn_complete evt;
 +    int rc;
 +
 +    ble_hs_test_util_conn_cancel(0);
 +
 +    memset(&evt, 0, sizeof evt);
 +    evt.subevent_code = BLE_HCI_LE_SUBEV_CONN_COMPLETE;
 +    evt.status = BLE_ERR_UNK_CONN_ID;
 +    evt.role = BLE_HCI_LE_CONN_COMPLETE_ROLE_MASTER;
 +
 +    rc = ble_gap_rx_conn_complete(&evt);
 +    TEST_ASSERT_FATAL(rc == 0);
 +}
 +
 +int
 +ble_hs_test_util_conn_terminate(uint16_t conn_handle, uint8_t hci_status)
 +{
++    struct hci_le_conn_complete evt;
 +    int rc;
 +
++    ble_hs_test_util_conn_cancel(0);
++
++    memset(&evt, 0, sizeof evt);
++    evt.subevent_code = BLE_HCI_LE_SUBEV_CONN_COMPLETE;
++    evt.status = BLE_ERR_UNK_CONN_ID;
++    evt.role = BLE_HCI_LE_CONN_COMPLETE_ROLE_MASTER;
++
++    rc = ble_gap_rx_conn_complete(&evt);
++    TEST_ASSERT_FATAL(rc == 0);
++}
++
++void
++ble_hs_test_util_set_ack_disconnect(uint8_t hci_status)
++{
 +    ble_hs_test_util_set_ack(
 +        ble_hs_hci_util_opcode_join(BLE_HCI_OGF_LINK_CTRL,
 +                                    BLE_HCI_OCF_DISCONNECT_CMD),
 +        hci_status);
++}
 +
++int
++ble_hs_test_util_conn_terminate(uint16_t conn_handle, uint8_t hci_status)
++{
++    int rc;
++
++    ble_hs_test_util_set_ack_disconnect(hci_status);
 +    rc = ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
 +    return rc;
 +}
 +
 +void
 +ble_hs_test_util_conn_disconnect(uint16_t conn_handle)
 +{
 +    struct hci_disconn_complete evt;
 +    int rc;
 +
 +    rc = ble_hs_test_util_conn_terminate(conn_handle, 0);
 +    TEST_ASSERT_FATAL(rc == 0);
 +
 +    /* Receive disconnection complete event. */
 +    evt.connection_handle = conn_handle;
 +    evt.status = 0;
 +    evt.reason = BLE_ERR_CONN_TERM_LOCAL;
 +    ble_gap_rx_disconn_complete(&evt);
 +}
 +
 +int
 +ble_hs_test_util_exp_hci_status(int cmd_idx, int fail_idx, uint8_t 
fail_status)
 +{
 +    if (cmd_idx == fail_idx) {
 +        return BLE_HS_HCI_ERR(fail_status);
 +    } else {
 +        return 0;
 +    }
 +}
 +
 +int
 +ble_hs_test_util_disc(uint8_t own_addr_type, int32_t duration_ms,
 +                      const struct ble_gap_disc_params *disc_params,
 +                      ble_gap_event_fn *cb, void *cb_arg, int fail_idx,
 +                      uint8_t fail_status)
 +{
 +    int rc;
 +
 +    ble_hs_test_util_set_ack_seq(((struct ble_hs_test_util_phony_ack[]) {
 +        {
 +            BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_SCAN_PARAMS),
 +            ble_hs_test_util_exp_hci_status(0, fail_idx, fail_status),
 +        },
 +        {
 +            BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_SCAN_ENABLE),
 +            ble_hs_test_util_exp_hci_status(1, fail_idx, fail_status),
 +        },
 +
 +        { 0 }
 +    }));
 +
 +    rc = ble_gap_disc(own_addr_type, duration_ms, disc_params,
 +                      cb, cb_arg);
 +    return rc;
 +}
 +
 +int
 +ble_hs_test_util_disc_cancel(uint8_t ack_status)
 +{
 +    int rc;
 +
 +    ble_hs_test_util_set_ack(
 +        ble_hs_hci_util_opcode_join(BLE_HCI_OGF_LE,
 +                                    BLE_HCI_OCF_LE_SET_SCAN_ENABLE),
 +        ack_status);
 +
 +    rc = ble_gap_disc_cancel();
 +    return rc;
 +}
 +
 +static void
 +ble_hs_test_util_verify_tx_rd_pwr(void)
 +{
 +    uint8_t param_len;
 +
 +    ble_hs_test_util_verify_tx_hci(BLE_HCI_OGF_LE,
 +                                   BLE_HCI_OCF_LE_RD_ADV_CHAN_TXPWR,
 +                                   &param_len);
 +    TEST_ASSERT(param_len == 0);
 +}
 +
 +int
 +ble_hs_test_util_adv_set_fields(struct ble_hs_adv_fields *adv_fields,
 +                                uint8_t hci_status)
 +{
 +    int auto_pwr;
 +    int rc;
 +
 +    auto_pwr = adv_fields->tx_pwr_lvl_is_present &&
 +               adv_fields->tx_pwr_lvl == BLE_HS_ADV_TX_PWR_LVL_AUTO;
 +
 +    if (auto_pwr) {
 +        ble_hs_test_util_set_ack_params(
 +            ble_hs_hci_util_opcode_join(BLE_HCI_OGF_LE,
 +                                        BLE_HCI_OCF_LE_RD_ADV_CHAN_TXPWR),
 +            hci_status,
 +            ((uint8_t[1]){0}), 1);
 +    }
 +
 +    rc = ble_gap_adv_set_fields(adv_fields);
 +    if (rc == 0 && auto_pwr) {
 +        /* Verify tx of set advertising params command. */
 +        ble_hs_test_util_verify_tx_rd_pwr();
 +    }
 +
 +    return rc;
 +}
 +
 +int
 +ble_hs_test_util_adv_start(uint8_t own_addr_type,
 +                           uint8_t peer_addr_type, const uint8_t *peer_addr, 
 +                           const struct ble_gap_adv_params *adv_params,
 +                           ble_gap_event_fn *cb, void *cb_arg,
 +                           int fail_idx, uint8_t fail_status)
 +{
 +    struct ble_hs_test_util_phony_ack acks[6];
 +    int rc;
 +    int i;
 +
 +    i = 0;
 +
 +    acks[i] = (struct ble_hs_test_util_phony_ack) {
 +        BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_ADV_PARAMS),
 +        fail_idx == i ? fail_status : 0,
 +    };
 +    i++;
 +
 +    if (adv_params->conn_mode != BLE_GAP_CONN_MODE_DIR) {
 +        acks[i] = (struct ble_hs_test_util_phony_ack) {
 +            BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_ADV_DATA),
 +            ble_hs_test_util_exp_hci_status(i, fail_idx, fail_status),
 +        };
 +        i++;
 +
 +        acks[i] = (struct ble_hs_test_util_phony_ack) {
 +            BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_SCAN_RSP_DATA),
 +            ble_hs_test_util_exp_hci_status(i, fail_idx, fail_status),
 +        };
 +        i++;
 +    }
 +
 +    acks[i] = (struct ble_hs_test_util_phony_ack) {
 +        BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_ADV_ENABLE),
 +        ble_hs_test_util_exp_hci_status(i, fail_idx, fail_status),
 +    };
 +    i++;
 +
 +    memset(acks + i, 0, sizeof acks[i]);
 +
 +    ble_hs_test_util_set_ack_seq(acks);
 +    
 +    rc = ble_gap_adv_start(own_addr_type, peer_addr_type, peer_addr, 
 +                           BLE_HS_FOREVER, adv_params, cb, cb_arg);
 +
 +    return rc;
 +}
 +
 +int
 +ble_hs_test_util_adv_stop(uint8_t hci_status)
 +{
 +    int rc;
 +
 +    ble_hs_test_util_set_ack(
 +        BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_ADV_ENABLE),
 +        hci_status);
 +
 +    rc = ble_gap_adv_stop();
 +    return rc;
 +}
 +
 +int
 +ble_hs_test_util_wl_set(struct ble_gap_white_entry *white_list,
 +                        uint8_t white_list_count,
 +                        int fail_idx, uint8_t fail_status)
 +{
 +    struct ble_hs_test_util_phony_ack acks[64];
 +    int cmd_idx;
 +    int rc;
 +    int i;
 +
 +    TEST_ASSERT_FATAL(white_list_count < 63);
 +
 +    cmd_idx = 0;
 +    acks[cmd_idx] = (struct ble_hs_test_util_phony_ack) {
 +        BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_CLEAR_WHITE_LIST),
 +        ble_hs_test_util_exp_hci_status(cmd_idx, fail_idx, fail_status),
 +    };
 +    cmd_idx++;
 +
 +    for (i = 0; i < white_list_count; i++) {
 +        acks[cmd_idx] = (struct ble_hs_test_util_phony_ack) {
 +            BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_ADD_WHITE_LIST),
 +            ble_hs_test_util_exp_hci_status(cmd_idx, fail_idx, fail_status),
 +        };
 +
 +        cmd_idx++;
 +    }
 +    memset(acks + cmd_idx, 0, sizeof acks[cmd_idx]);
 +
 +    ble_hs_test_util_set_ack_seq(acks);
 +    rc = ble_gap_wl_set(white_list, white_list_count);
 +    return rc;
 +}
 +
 +int
 +ble_hs_test_util_conn_update(uint16_t conn_handle,
 +                             struct ble_gap_upd_params *params,
 +                             uint8_t hci_status)
 +{
 +    int rc;
 +
 +    ble_hs_test_util_set_ack(
 +        BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_CONN_UPDATE), hci_status);
 +
 +    rc = ble_gap_update_params(conn_handle, params);
 +    return rc;
 +}
 +
 +int
 +ble_hs_test_util_set_our_irk(const uint8_t *irk, int fail_idx,
 +                             uint8_t hci_status)
 +{
 +    int rc;
 +
 +    ble_hs_test_util_set_ack_seq(((struct ble_hs_test_util_phony_ack[]) {
 +        {
 +            BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_ADDR_RES_EN),
 +            ble_hs_test_util_exp_hci_status(0, fail_idx, hci_status),
 +        },
 +        {
 +            BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_CLR_RESOLV_LIST),
 +            ble_hs_test_util_exp_hci_status(1, fail_idx, hci_status),
 +        },
 +        {
 +            BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_ADDR_RES_EN),
 +            ble_hs_test_util_exp_hci_status(2, fail_idx, hci_status),
 +        },
 +        {
 +            BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_ADD_RESOLV_LIST),
 +            ble_hs_test_util_exp_hci_status(3, fail_idx, hci_status),
 +        },
 +        {
 +            0
 +        }
 +    }));
 +
 +    rc = ble_hs_pvcy_set_our_irk(irk);
 +    return rc;
 +}
 +
 +int
 +ble_hs_test_util_security_initiate(uint16_t conn_handle, uint8_t hci_status)
 +{
 +    int rc;
 +
 +    ble_hs_test_util_set_ack(
 +        BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_START_ENCRYPT), hci_status);
 +
 +    rc = ble_gap_security_initiate(conn_handle);
 +    return rc;
 +}
 +
 +int
 +ble_hs_test_util_l2cap_rx_first_frag(uint16_t conn_handle, uint16_t cid,
 +                                     struct hci_data_hdr *hci_hdr,
 +                                     struct os_mbuf *om)
 +{
 +    int rc;
 +
 +    om = ble_l2cap_prepend_hdr(om, cid, OS_MBUF_PKTLEN(om));
 +    TEST_ASSERT_FATAL(om != NULL);
 +
 +    rc = ble_hs_test_util_l2cap_rx(conn_handle, hci_hdr, om);
 +    return rc;
 +}
 +
 +int
 +ble_hs_test_util_l2cap_rx(uint16_t conn_handle,
 +                          struct hci_data_hdr *hci_hdr,
 +                          struct os_mbuf *om)
 +{
 +    struct ble_hs_conn *conn;
 +    ble_l2cap_rx_fn *rx_cb;
 +    struct os_mbuf *rx_buf;
 +    int rc;
 +
 +    ble_hs_lock();
 +
 +    conn = ble_hs_conn_find(conn_handle);
 +    if (conn != NULL) {
 +        rc = ble_l2cap_rx(conn, hci_hdr, om, &rx_cb, &rx_buf);
 +    } else {
 +        os_mbuf_free_chain(om);
 +    }
 +
 +    ble_hs_unlock();
 +
 +    if (conn == NULL) {
 +        rc = BLE_HS_ENOTCONN;
 +    } else if (rc == 0) {
 +        TEST_ASSERT_FATAL(rx_cb != NULL);
 +        TEST_ASSERT_FATAL(rx_buf != NULL);
 +        rc = rx_cb(conn_handle, &rx_buf);
 +        os_mbuf_free_chain(rx_buf);
 +    } else if (rc == BLE_HS_EAGAIN) {
 +        /* More fragments on the way. */
 +        rc = 0;
 +    }
 +
 +    return rc;
 +}
 +
 +int
 +ble_hs_test_util_l2cap_rx_payload_flat(uint16_t conn_handle, uint16_t cid,
 +                                       const void *data, int len)
 +{
 +    struct hci_data_hdr hci_hdr;
 +    struct os_mbuf *om;
 +    int rc;
 +
 +    om = ble_hs_mbuf_l2cap_pkt();
 +    TEST_ASSERT_FATAL(om != NULL);
 +
 +    rc = os_mbuf_append(om, data, len);
 +    TEST_ASSERT_FATAL(rc == 0);
 +
 +    hci_hdr.hdh_handle_pb_bc =
 +        ble_hs_hci_util_handle_pb_bc_join(conn_handle,
 +                                          BLE_HCI_PB_FIRST_FLUSH, 0);
 +    hci_hdr.hdh_len = OS_MBUF_PKTHDR(om)->omp_len;
 +
 +    rc = ble_hs_test_util_l2cap_rx_first_frag(conn_handle, cid, &hci_hdr, om);
 +    return rc;
 +}
 +
 +void
++ble_hs_test_util_rx_att_mtu_cmd(uint16_t conn_handle, int is_req, uint16_t 
mtu)
++{
++    struct ble_att_mtu_cmd cmd;
++    uint8_t buf[BLE_ATT_MTU_CMD_SZ];
++    int rc;
++
++    cmd.bamc_mtu = mtu;
++
++    if (is_req) {
++        ble_att_mtu_req_write(buf, sizeof buf, &cmd);
++    } else {
++        ble_att_mtu_rsp_write(buf, sizeof buf, &cmd);
++    }
++
++    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn_handle, 
BLE_L2CAP_CID_ATT,
++                                                buf, sizeof buf);
++    TEST_ASSERT(rc == 0);
++}
++
++void
 +ble_hs_test_util_rx_att_err_rsp(uint16_t conn_handle, uint8_t req_op,
 +                                uint8_t error_code, uint16_t err_handle)
 +{
 +    struct ble_att_error_rsp rsp;
 +    uint8_t buf[BLE_ATT_ERROR_RSP_SZ];
 +    int rc;
 +
 +    rsp.baep_req_op = req_op;
 +    rsp.baep_handle = err_handle;
 +    rsp.baep_error_code = error_code;
 +
 +    ble_att_error_rsp_write(buf, sizeof buf, &rsp);
 +
 +    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn_handle, 
BLE_L2CAP_CID_ATT,
 +                                                buf, sizeof buf);
 +    TEST_ASSERT(rc == 0);
 +}
 +
 +void
 +ble_hs_test_util_set_startup_acks(void)
 +{
 +    /* Receive acknowledgements for the startup sequence.  We sent the
 +     * corresponding requests when the host task was started.
 +     */
 +    ble_hs_test_util_set_ack_seq(((struct ble_hs_test_util_phony_ack[]) {
 +        {
 +            .opcode = ble_hs_hci_util_opcode_join(BLE_HCI_OGF_CTLR_BASEBAND,
 +                                                  BLE_HCI_OCF_CB_RESET),
 +        },
 +        {
 +            .opcode = ble_hs_hci_util_opcode_join(
 +                BLE_HCI_OGF_CTLR_BASEBAND, BLE_HCI_OCF_CB_SET_EVENT_MASK),
 +        },
 +        {
 +            .opcode = ble_hs_hci_util_opcode_join(
 +                BLE_HCI_OGF_CTLR_BASEBAND, BLE_HCI_OCF_CB_SET_EVENT_MASK2),
 +        },
 +        {
 +            .opcode = ble_hs_hci_util_opcode_join(
 +                BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_EVENT_MASK),
 +        },
 +        {
 +            .opcode = ble_hs_hci_util_opcode_join(
 +                BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_RD_BUF_SIZE),
 +            /* Use a very low buffer size (16) to test fragmentation. */
 +            .evt_params = { 0x10, 0x00, 0x20 },
 +            .evt_params_len = 3,
 +        },
 +        {
 +            .opcode = ble_hs_hci_util_opcode_join(
 +                BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_RD_LOC_SUPP_FEAT),
 +            .evt_params = { 0 },
 +            .evt_params_len = 8,
 +        },
 +        {
 +            .opcode = ble_hs_hci_util_opcode_join(
 +                BLE_HCI_OGF_INFO_PARAMS, BLE_HCI_OCF_IP_RD_BD_ADDR),
 +            .evt_params = BLE_HS_TEST_UTIL_PUB_ADDR_VAL,
 +            .evt_params_len = 6,
 +        },
 +        {
 +            .opcode = ble_hs_hci_util_opcode_join(
 +                BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_ADDR_RES_EN),
 +        },
 +        {
 +            .opcode = ble_hs_hci_util_opcode_join(
 +                BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_CLR_RESOLV_LIST),
 +        },
 +        {
 +            .opcode = ble_hs_hci_util_opcode_join(
 +                BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_ADDR_RES_EN),
 +        },
 +        {
 +            .opcode = ble_hs_hci_util_opcode_join(
 +                BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_ADD_RESOLV_LIST),
 +        },
 +        { 0 }
 +    }));
 +}
 +
 +void
 +ble_hs_test_util_rx_num_completed_pkts_event(
 +    struct ble_hs_test_util_num_completed_pkts_entry *entries)
 +{
 +    struct ble_hs_test_util_num_completed_pkts_entry *entry;
 +    uint8_t buf[1024];
 +    int num_entries;
 +    int off;
 +    int i;
 +
 +    /* Count number of entries. */
 +    num_entries = 0;
 +    for (entry = entries; entry->handle_id != 0; entry++) {
 +        num_entries++;
 +    }
 +    TEST_ASSERT_FATAL(num_entries <= UINT8_MAX);
 +
 +    buf[0] = BLE_HCI_EVCODE_NUM_COMP_PKTS;
 +    buf[2] = num_entries;
 +
 +    off = 3;
 +    for (i = 0; i < num_entries; i++) {
 +        htole16(buf + off, entries[i].handle_id);
 +        off += 2;
 +    }
 +    for (i = 0; i < num_entries; i++) {
 +        htole16(buf + off, entries[i].num_pkts);
 +        off += 2;
 +    }
 +
 +    buf[1] = off - 2;
 +
 +    ble_hs_test_util_rx_hci_evt(buf);
 +}
 +
 +void
 +ble_hs_test_util_rx_disconn_complete_event(struct hci_disconn_complete *evt)
 +{
 +    uint8_t buf[BLE_HCI_EVENT_HDR_LEN + BLE_HCI_EVENT_DISCONN_COMPLETE_LEN];
 +
 +    buf[0] = BLE_HCI_EVCODE_DISCONN_CMP;
 +    buf[1] = BLE_HCI_EVENT_DISCONN_COMPLETE_LEN;
 +    buf[2] = evt->status;
 +    htole16(buf + 3, evt->connection_handle);
 +    buf[5] = evt->reason;
 +
 +    ble_hs_test_util_rx_hci_evt(buf);
 +}
 +
 +uint8_t *
 +ble_hs_test_util_verify_tx_hci(uint8_t ogf, uint16_t ocf,
 +                               uint8_t *out_param_len)
 +{
 +    uint16_t opcode;
 +    uint8_t *cmd;
 +
 +    cmd = ble_hs_test_util_get_first_hci_tx();
 +    TEST_ASSERT_FATAL(cmd != NULL);
 +
 +    opcode = le16toh(cmd);
 +    TEST_ASSERT(BLE_HCI_OGF(opcode) == ogf);
 +    TEST_ASSERT(BLE_HCI_OCF(opcode) == ocf);
 +
 +    if (out_param_len != NULL) {
 +        *out_param_len = cmd[2];
 +    }
 +
 +    return cmd + 3;
 +}
 +
 +void
 +ble_hs_test_util_tx_all(void)
 +{
 +    ble_hs_process_tx_data_queue();
 +}
 +
 +void
 +ble_hs_test_util_verify_tx_prep_write(uint16_t attr_handle, uint16_t offset,
 +                                      const void *data, int data_len)
 +{
 +    struct ble_att_prep_write_cmd req;
 +    struct os_mbuf *om;
 +
 +    ble_hs_test_util_tx_all();
 +    om = ble_hs_test_util_prev_tx_dequeue();
 +    TEST_ASSERT_FATAL(om != NULL);
 +    TEST_ASSERT(OS_MBUF_PKTLEN(om) ==
 +                BLE_ATT_PREP_WRITE_CMD_BASE_SZ + data_len);
 +
 +    om = os_mbuf_pullup(om, BLE_ATT_PREP_WRITE_CMD_BASE_SZ);
 +    TEST_ASSERT_FATAL(om != NULL);
 +
 +    ble_att_prep_write_req_parse(om->om_data, om->om_len, &req);
 +    TEST_ASSERT(req.bapc_handle == attr_handle);
 +    TEST_ASSERT(req.bapc_offset == offset);
 +    TEST_ASSERT(os_mbuf_cmpf(om, BLE_ATT_PREP_WRITE_CMD_BASE_SZ,
 +                             data, data_len) == 0);
 +}
 +
 +void
 +ble_hs_test_util_verify_tx_exec_write(uint8_t expected_flags)
 +{
 +    struct ble_att_exec_write_req req;
 +    struct os_mbuf *om;
 +
 +    ble_hs_test_util_tx_all();
 +    om = ble_hs_test_util_prev_tx_dequeue_pullup();
 +    TEST_ASSERT_FATAL(om != NULL);
 +    TEST_ASSERT(om->om_len == BLE_ATT_EXEC_WRITE_REQ_SZ);
 +
 +    ble_att_exec_write_req_parse(om->om_data, om->om_len, &req);
 +    TEST_ASSERT(req.baeq_flags == expected_flags);
 +}
 +
 +void
 +ble_hs_test_util_verify_tx_read_rsp_gen(uint8_t att_op,
 +                                        uint8_t *attr_data, int attr_len)
 +{
 +    struct os_mbuf *om;
 +    uint8_t u8;
 +    int rc;
 +    int i;
 +
 +    ble_hs_test_util_tx_all();
 +
 +    om = ble_hs_test_util_prev_tx_dequeue();
 +
 +    rc = os_mbuf_copydata(om, 0, 1, &u8);
 +    TEST_ASSERT(rc == 0);
 +    TEST_ASSERT(u8 == att_op);
 +
 +    for (i = 0; i < attr_len; i++) {
 +        rc = os_mbuf_copydata(om, i + 1, 1, &u8);
 +        TEST_ASSERT(rc == 0);
 +        TEST_ASSERT(u8 == attr_data[i]);
 +    }
 +
 +    rc = os_mbuf_copydata(om, i + 1, 1, &u8);
 +    TEST_ASSERT(rc != 0);
 +}
 +
 +void
 +ble_hs_test_util_verify_tx_read_rsp(uint8_t *attr_data, int attr_len)
 +{
 +    ble_hs_test_util_verify_tx_read_rsp_gen(BLE_ATT_OP_READ_RSP,
 +                                            attr_data, attr_len);
 +}
 +
 +void
 +ble_hs_test_util_verify_tx_read_blob_rsp(uint8_t *attr_data, int attr_len)
 +{
 +    ble_hs_test_util_verify_tx_read_rsp_gen(BLE_ATT_OP_READ_BLOB_RSP,
 +                                            attr_data, attr_len);
 +}
 +
 +void
++ble_hs_test_util_verify_tx_write_rsp(void)
++{
++    struct os_mbuf *om;
++    uint8_t u8;
++    int rc;
++
++    ble_hs_test_util_tx_all();
++
++    om = ble_hs_test_util_prev_tx_dequeue();
++
++    rc = os_mbuf_copydata(om, 0, 1, &u8);
++    TEST_ASSERT(rc == 0);
++    TEST_ASSERT(u8 == BLE_ATT_OP_WRITE_RSP);
++}
++
++void
++ble_hs_test_util_verify_tx_mtu_cmd(int is_req, uint16_t mtu)
++{
++    struct ble_att_mtu_cmd cmd;
++    struct os_mbuf *om;
++
++    ble_hs_test_util_tx_all();
++
++    om = ble_hs_test_util_prev_tx_dequeue_pullup();
++    TEST_ASSERT_FATAL(om != NULL);
++
++    if (is_req) {
++        ble_att_mtu_req_parse(om->om_data, om->om_len, &cmd);
++    } else {
++        ble_att_mtu_rsp_parse(om->om_data, om->om_len, &cmd);
++    }
++
++    TEST_ASSERT(cmd.bamc_mtu == mtu);
++}
++
++void
++ble_hs_test_util_verify_tx_err_rsp(uint8_t req_op, uint16_t handle,
++                                   uint8_t error_code)
++{
++    struct ble_att_error_rsp rsp;
++    struct os_mbuf *om;
++    uint8_t buf[BLE_ATT_ERROR_RSP_SZ];
++    int rc;
++
++    ble_hs_test_util_tx_all();
++
++    om = ble_hs_test_util_prev_tx_dequeue();
++
++    rc = os_mbuf_copydata(om, 0, sizeof buf, buf);
++    TEST_ASSERT(rc == 0);
++
++    ble_att_error_rsp_parse(buf, sizeof buf, &rsp);
++
++    TEST_ASSERT(rsp.baep_req_op == req_op);
++    TEST_ASSERT(rsp.baep_handle == handle);
++    TEST_ASSERT(rsp.baep_error_code == error_code);
++}
++
++static struct os_mbuf *
++ble_hs_test_util_verify_tx_l2cap_sig_hdr(uint8_t op, uint8_t id,
++                                   uint16_t payload_len,
++                                   struct ble_l2cap_sig_hdr *out_hdr)
++{
++    struct ble_l2cap_sig_hdr hdr;
++    struct os_mbuf *om;
++
++    om = ble_hs_test_util_prev_tx_dequeue();
++    TEST_ASSERT_FATAL(om != NULL);
++
++    TEST_ASSERT(OS_MBUF_PKTLEN(om) == BLE_L2CAP_SIG_HDR_SZ + payload_len);
++    ble_l2cap_sig_hdr_parse(om->om_data, om->om_len, &hdr);
++    TEST_ASSERT(hdr.op == op);
++    if (id != 0) {
++        TEST_ASSERT(hdr.identifier == id);
++    }
++    TEST_ASSERT(hdr.length == payload_len);
++
++    om->om_data += BLE_L2CAP_SIG_HDR_SZ;
++    om->om_len -= BLE_L2CAP_SIG_HDR_SZ;
++
++    if (out_hdr != NULL) {
++        *out_hdr = hdr;
++    }
++
++    return om;
++}
++
++/**
++ * @return                      The L2CAP sig identifier in the request.
++ */
++uint8_t
++ble_hs_test_util_verify_tx_l2cap_update_req(
++    struct ble_l2cap_sig_update_params *params)
++{
++    struct ble_l2cap_sig_update_req req;
++    struct ble_l2cap_sig_hdr hdr;
++    struct os_mbuf *om;
++
++    ble_hs_test_util_tx_all();
++
++    om = ble_hs_test_util_verify_tx_l2cap_sig_hdr(BLE_L2CAP_SIG_OP_UPDATE_REQ,
++                                                  0,
++                                                  BLE_L2CAP_SIG_UPDATE_REQ_SZ,
++                                                  &hdr);
++
++    /* Verify payload. */
++    ble_l2cap_sig_update_req_parse(om->om_data, om->om_len, &req);
++    TEST_ASSERT(req.itvl_min == params->itvl_min);
++    TEST_ASSERT(req.itvl_max == params->itvl_max);
++    TEST_ASSERT(req.slave_latency == params->slave_latency);
++    TEST_ASSERT(req.timeout_multiplier == params->timeout_multiplier);
++
++    return hdr.identifier;
++}
++
++int
++ble_hs_test_util_rx_l2cap_update_rsp(uint16_t conn_handle,
++                                     uint8_t id, uint16_t result)
++{
++    struct ble_l2cap_sig_update_rsp rsp;
++    struct hci_data_hdr hci_hdr;
++    struct os_mbuf *om;
++    void *v;
++    int rc;
++
++    hci_hdr = BLE_HS_TEST_UTIL_L2CAP_HCI_HDR(
++        2, BLE_HCI_PB_FIRST_FLUSH,
++        BLE_L2CAP_HDR_SZ + BLE_L2CAP_SIG_HDR_SZ + 
BLE_L2CAP_SIG_UPDATE_RSP_SZ);
++
++    rc = ble_l2cap_sig_init_cmd(BLE_L2CAP_SIG_OP_UPDATE_RSP, id,
++                                BLE_L2CAP_SIG_UPDATE_RSP_SZ, &om, &v);
++    TEST_ASSERT_FATAL(rc == 0);
++
++    rsp.result = result;
++    ble_l2cap_sig_update_rsp_write(v, BLE_L2CAP_SIG_UPDATE_RSP_SZ, &rsp);
++
++    rc = ble_hs_test_util_l2cap_rx_first_frag(conn_handle, BLE_L2CAP_CID_SIG,
++                                              &hci_hdr, om);
++    return rc;
++}
++
++void
++ble_hs_test_util_verify_tx_l2cap_update_rsp(uint8_t exp_id,
++                                            uint16_t exp_result)
++{
++    struct ble_l2cap_sig_update_rsp rsp;
++    struct os_mbuf *om;
++
++    om = ble_hs_test_util_verify_tx_l2cap_sig_hdr(BLE_L2CAP_SIG_OP_UPDATE_RSP,
++                                            exp_id,
++                                            BLE_L2CAP_SIG_UPDATE_RSP_SZ,
++                                            NULL);
++
++    ble_l2cap_sig_update_rsp_parse(om->om_data, om->om_len, &rsp);
++    TEST_ASSERT(rsp.result == exp_result);
++}
++
++void
 +ble_hs_test_util_set_static_rnd_addr(void)
 +{
 +    uint8_t addr[6] = { 1, 2, 3, 4, 5, 0xc1 };
 +    int rc;
 +
 +    ble_hs_test_util_set_ack(
 +        BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_RAND_ADDR), 0);
 +
 +    rc = ble_hs_id_set_rnd(addr);
 +    TEST_ASSERT_FATAL(rc == 0);
 +
 +    ble_hs_test_util_get_first_hci_tx();
 +}
 +
 +struct os_mbuf *
 +ble_hs_test_util_om_from_flat(const void *buf, uint16_t len)
 +{
 +    struct os_mbuf *om;
 +
 +    om = ble_hs_mbuf_from_flat(buf, len);
 +    TEST_ASSERT_FATAL(om != NULL);
 +
 +    return om;
 +}
 +
 +int
 +ble_hs_test_util_flat_attr_cmp(const struct ble_hs_test_util_flat_attr *a,
 +                               const struct ble_hs_test_util_flat_attr *b)
 +{
 +    if (a->handle != b->handle) {
 +        return -1;
 +    }
 +    if (a->offset != b->offset) {
 +        return -1;
 +    }
 +    if (a->value_len != b->value_len) {
 +        return -1;
 +    }
 +    return memcmp(a->value, b->value, a->value_len);
 +}
 +
 +void
 +ble_hs_test_util_attr_to_flat(struct ble_hs_test_util_flat_attr *flat,
 +                              const struct ble_gatt_attr *attr)
 +{
 +    int rc;
 +
 +    flat->handle = attr->handle;
 +    flat->offset = attr->offset;
 +    rc = ble_hs_mbuf_to_flat(attr->om, flat->value, sizeof flat->value,
 +                           &flat->value_len);
 +    TEST_ASSERT_FATAL(rc == 0);
 +}
 +
 +void
 +ble_hs_test_util_attr_from_flat(struct ble_gatt_attr *attr,
 +                                const struct ble_hs_test_util_flat_attr *flat)
 +{
 +    attr->handle = flat->handle;
 +    attr->offset = flat->offset;
 +    attr->om = ble_hs_test_util_om_from_flat(flat->value, flat->value_len);
 +}
 +
 +int
 +ble_hs_test_util_read_local_flat(uint16_t attr_handle, uint16_t max_len,
 +                                 void *buf, uint16_t *out_len)
 +{
 +    struct os_mbuf *om;
 +    int rc;
 +
 +    rc = ble_att_svr_read_local(attr_handle, &om);
 +    if (rc != 0) {
 +        return rc;
 +    }
 +
 +    TEST_ASSERT_FATAL(OS_MBUF_PKTLEN(om) <= max_len);
 +
 +    rc = os_mbuf_copydata(om, 0, OS_MBUF_PKTLEN(om), buf);
 +    TEST_ASSERT_FATAL(rc == 0);
 +
 +    *out_len = OS_MBUF_PKTLEN(om);
 +
 +    os_mbuf_free_chain(om);
 +    return 0;
 +}
 +
 +int
 +ble_hs_test_util_write_local_flat(uint16_t attr_handle,
 +                                  const void *buf, uint16_t buf_len)
 +{
 +    struct os_mbuf *om;
 +    int rc;
 +
 +    om = ble_hs_test_util_om_from_flat(buf, buf_len);
 +    rc = ble_att_svr_write_local(attr_handle, om);
 +    return rc;
 +}
 +
 +int
 +ble_hs_test_util_gatt_write_flat(uint16_t conn_handle, uint16_t attr_handle,
 +                                 const void *data, uint16_t data_len,
 +                                 ble_gatt_attr_fn *cb, void *cb_arg)
 +{
 +    struct os_mbuf *om;
 +    int rc;
 +
 +    om = ble_hs_test_util_om_from_flat(data, data_len);
 +    rc = ble_gattc_write(conn_handle, attr_handle, om, cb, cb_arg);
 +
 +    return rc;
 +}
 +
 +int
 +ble_hs_test_util_gatt_write_no_rsp_flat(uint16_t conn_handle,
 +                                        uint16_t attr_handle,
 +                                        const void *data, uint16_t data_len)
 +{
 +    struct os_mbuf *om;
 +    int rc;
 +
 +    om = ble_hs_test_util_om_from_flat(data, data_len);
 +    rc = ble_gattc_write_no_rsp(conn_handle, attr_handle, om);
 +
 +    return rc;
 +}
 +
 +int
 +ble_hs_test_util_gatt_write_long_flat(uint16_t conn_handle,
 +                                      uint16_t attr_handle,
 +                                      const void *data, uint16_t data_len,
 +                                      ble_gatt_attr_fn *cb, void *cb_arg)
 +{
 +    struct os_mbuf *om;
 +    int rc;
 +
 +    om = ble_hs_test_util_om_from_flat(data, data_len);
 +    rc = ble_gattc_write_long(conn_handle, attr_handle, om, cb, cb_arg);
 +
 +    return rc;
 +}
 +
 +static int
 +ble_hs_test_util_mbuf_chain_len(const struct os_mbuf *om)
 +{
 +    int count;
 +
 +    count = 0;
 +    while (om != NULL) {
 +        count++;
 +        om = SLIST_NEXT(om, om_next);
 +    }
 +
 +    return count;
 +}
 +
 +int
 +ble_hs_test_util_mbuf_count(const struct ble_hs_test_util_mbuf_params *params)
 +{
 +    const struct ble_att_prep_entry *prep;
 +    const struct os_mbuf_pkthdr *omp;
 +    const struct ble_l2cap_chan *chan;
 +    const struct ble_hs_conn *conn;
 +    const struct os_mbuf *om;
 +    int count;
 +    int i;
 +
 +    ble_hs_process_tx_data_queue();
 +    ble_hs_process_rx_data_queue();
 +
 +    count = os_msys_num_free();
 +
 +    if (params->prev_tx) {
 +        count += 
ble_hs_test_util_mbuf_chain_len(ble_hs_test_util_prev_tx_cur);
 +        STAILQ_FOREACH(omp, &ble_hs_test_util_prev_tx_queue, omp_next) {
 +            om = OS_MBUF_PKTHDR_TO_MBUF(omp);
 +            count += ble_hs_test_util_mbuf_chain_len(om);
 +        }
 +    }
 +
 +    ble_hs_lock();
 +    for (i = 0; ; i++) {
 +        conn = ble_hs_conn_find_by_idx(i);
 +        if (conn == NULL) {
 +            break;
 +        }
 +
 +        if (params->rx_queue) {
 +            SLIST_FOREACH(chan, &conn->bhc_channels, blc_next) {
 +                count += ble_hs_test_util_mbuf_chain_len(chan->blc_rx_buf);
 +            }
 +        }
 +
 +        if (params->prep_list) {
 +            SLIST_FOREACH(prep, &conn->bhc_att_svr.basc_prep_list, bape_next) 
{
 +                count += ble_hs_test_util_mbuf_chain_len(prep->bape_value);
 +            }
 +        }
 +    }
 +    ble_hs_unlock();
 +
 +    return count;
 +}
 +
 +void
 +ble_hs_test_util_assert_mbufs_freed(
 +    const struct ble_hs_test_util_mbuf_params *params)
 +{
 +    static const struct ble_hs_test_util_mbuf_params dflt = {
 +        .prev_tx = 1,
 +        .rx_queue = 1,
 +        .prep_list = 1,
 +    };
 +
 +    int count;
 +
 +    if (params == NULL) {
 +        params = &dflt;
 +    }
 +
 +    count = ble_hs_test_util_mbuf_count(params);
 +    TEST_ASSERT(count == os_msys_count());
 +}
 +
 +void
 +ble_hs_test_util_post_test(void *arg)
 +{
 +    ble_hs_test_util_assert_mbufs_freed(arg);
 +}
 +
 +static int
 +ble_hs_test_util_pkt_txed(struct os_mbuf *om, void *arg)
 +{
 +    ble_hs_test_util_prev_tx_enqueue(om);
 +    return 0;
 +}
 +
 +static int
 +ble_hs_test_util_hci_txed(uint8_t *cmdbuf, void *arg)
 +{
 +    ble_hs_test_util_enqueue_hci_tx(cmdbuf);
 +    ble_hci_trans_buf_free(cmdbuf);
 +    return 0;
 +}
 +
 +void
 +ble_hs_test_util_init_no_start(void)
 +{
 +    ble_hs_cfg.parent_evq = &ble_hs_test_util_evq;
 +
 +    tu_init();
 +
 +    os_eventq_init(&ble_hs_test_util_evq);
 +    STAILQ_INIT(&ble_hs_test_util_prev_tx_queue);
 +    ble_hs_test_util_prev_tx_cur = NULL;
 +
 +    ble_hs_hci_set_phony_ack_cb(NULL);
 +
 +    ble_hci_trans_cfg_ll(ble_hs_test_util_hci_txed, NULL,
 +                         ble_hs_test_util_pkt_txed, NULL);
 +
 +    ble_hs_test_util_set_startup_acks();
 +
 +    ble_hs_max_services = 16;
 +    ble_hs_max_client_configs = 32;
 +    ble_hs_max_attrs = 64;
 +
 +    ble_hs_test_util_prev_hci_tx_clear();
 +}
 +
 +void
 +ble_hs_test_util_init(void)
 +{
 +    int rc;
 +
 +    ble_hs_test_util_init_no_start();
 +
 +    rc = ble_hs_start();
 +    TEST_ASSERT_FATAL(rc == 0);
 +
 +    ble_hs_test_util_prev_hci_tx_clear();
 +}


Reply via email to