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, + ¶m_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, + ¶m_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(); +}