/* * 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 "testutil/testutil.h" #include "nimble/ble.h" #include "nimble/hci_common.h" #include "ble_hs_test_util.h" #define BLE_HCI_EVENT_CMD_COMPLETE_HDR_LEN (5) #define BLE_HCI_EVENT_CMD_STATUS_LEN (6) #define BLE_HCI_ADD_TO_RESOLV_LIST_LEN (39) #define BLE_HCI_LE_SET_PRIVACY_MODE_LEN (8) #define BLE_HCI_DISCONNECT_CMD_LEN (3) #define BLE_HCI_EVENT_HDR_LEN (2) #define BLE_HCI_EVENT_DISCONN_COMPLETE_LEN (4) #define BLE_HS_TEST_UTIL_PREV_HCI_TX_CNT 64 static uint8_t ble_hs_test_util_hci_out_queue[BLE_HS_TEST_UTIL_PREV_HCI_TX_CNT][260]; static int ble_hs_test_util_hci_out_queue_sz; static uint8_t ble_hs_test_util_hci_out_cur[260]; static struct ble_hs_test_util_hci_ack ble_hs_test_util_hci_acks[BLE_HS_TEST_UTIL_PHONY_ACK_MAX]; static int ble_hs_test_util_hci_num_acks; /***************************************************************************** * $tx queue * *****************************************************************************/ void ble_hs_test_util_hci_out_adj(int count) { if (count >= 0) { TEST_ASSERT(ble_hs_test_util_hci_out_queue_sz >= count); ble_hs_test_util_hci_out_queue_sz -= count; if (ble_hs_test_util_hci_out_queue_sz > 0) { memmove( ble_hs_test_util_hci_out_queue, ble_hs_test_util_hci_out_queue + count, sizeof ble_hs_test_util_hci_out_queue[0] * ble_hs_test_util_hci_out_queue_sz); } } else { TEST_ASSERT(ble_hs_test_util_hci_out_queue_sz >= -count); ble_hs_test_util_hci_out_adj( ble_hs_test_util_hci_out_queue_sz + count); } } void * ble_hs_test_util_hci_out_first(void) { if (ble_hs_test_util_hci_out_queue_sz == 0) { return NULL; } memcpy(ble_hs_test_util_hci_out_cur, ble_hs_test_util_hci_out_queue[0], sizeof ble_hs_test_util_hci_out_cur); ble_hs_test_util_hci_out_adj(1); return ble_hs_test_util_hci_out_cur; } void * ble_hs_test_util_hci_out_last(void) { if (ble_hs_test_util_hci_out_queue_sz == 0) { return NULL; } ble_hs_test_util_hci_out_queue_sz--; memcpy(ble_hs_test_util_hci_out_cur, ble_hs_test_util_hci_out_queue + ble_hs_test_util_hci_out_queue_sz, sizeof ble_hs_test_util_hci_out_cur); return ble_hs_test_util_hci_out_cur; } void ble_hs_test_util_hci_out_enqueue(void *cmd) { TEST_ASSERT_FATAL(ble_hs_test_util_hci_out_queue_sz < BLE_HS_TEST_UTIL_PREV_HCI_TX_CNT); memcpy(ble_hs_test_util_hci_out_queue + ble_hs_test_util_hci_out_queue_sz, cmd, 260); ble_hs_test_util_hci_out_queue_sz++; } void ble_hs_test_util_hci_out_clear(void) { ble_hs_test_util_hci_out_queue_sz = 0; } void ble_hs_test_util_hci_acks_clear(void) { ble_hs_test_util_hci_num_acks = 0; } /***************************************************************************** * $build * *****************************************************************************/ void ble_hs_test_util_hci_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; put_le16(dst + 3, opcode); } void ble_hs_test_util_hci_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; put_le16(dst + 4, opcode); } static void ble_hs_test_util_hci_build_ack_params( struct ble_hs_test_util_hci_ack *ack, uint16_t opcode, uint8_t status, void *params, uint8_t params_len) { 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; } } /***************************************************************************** * $ack * *****************************************************************************/ static int ble_hs_test_util_hci_ack_cb(uint8_t *ack, int ack_buf_len) { struct ble_hs_test_util_hci_ack *entry; if (ble_hs_test_util_hci_num_acks == 0) { return BLE_HS_ETIMEOUT_HCI; } entry = ble_hs_test_util_hci_acks; ble_hs_test_util_hci_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_hci_num_acks--; if (ble_hs_test_util_hci_num_acks > 0) { memmove(ble_hs_test_util_hci_acks, ble_hs_test_util_hci_acks + 1, sizeof *entry * ble_hs_test_util_hci_num_acks); } return 0; } void ble_hs_test_util_hci_ack_set_params(uint16_t opcode, uint8_t status, void *params, uint8_t params_len) { struct ble_hs_test_util_hci_ack *ack; ack = ble_hs_test_util_hci_acks + 0; ble_hs_test_util_hci_build_ack_params(ack, opcode, status, params, params_len); ble_hs_test_util_hci_num_acks = 1; ble_hs_hci_set_phony_ack_cb(ble_hs_test_util_hci_ack_cb); } void ble_hs_test_util_hci_ack_set(uint16_t opcode, uint8_t status) { ble_hs_test_util_hci_ack_set_params(opcode, status, NULL, 0); } void ble_hs_test_util_hci_ack_append_params(uint16_t opcode, uint8_t status, void *params, uint8_t params_len) { struct ble_hs_test_util_hci_ack *ack; ack = ble_hs_test_util_hci_acks + ble_hs_test_util_hci_num_acks; ble_hs_test_util_hci_build_ack_params(ack, opcode, status, params, params_len); ble_hs_test_util_hci_num_acks++; ble_hs_hci_set_phony_ack_cb(ble_hs_test_util_hci_ack_cb); } void ble_hs_test_util_hci_ack_append(uint16_t opcode, uint8_t status) { ble_hs_test_util_hci_ack_append_params(opcode, status, NULL, 0); } static const struct ble_hs_test_util_hci_ack hci_startup_seq[] = { { .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_INFO_PARAMS, BLE_HCI_OCF_IP_RD_LOCAL_VER), .evt_params = { 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, .evt_params_len = 8, }, { .opcode = ble_hs_hci_util_opcode_join( BLE_HCI_OGF_INFO_PARAMS, BLE_HCI_OCF_IP_RD_LOC_SUPP_FEAT), .evt_params = { 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00}, .evt_params_len = 8, }, { .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 (20) to test fragmentation. * Use a large num-pkts (200) to prevent controller buf exhaustion. */ .evt_params = { 0x14, 0x00, 200 }, .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_SET_ADV_ENABLE), }, { .opcode = ble_hs_hci_util_opcode_join( BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_ADD_RESOLV_LIST), }, { .opcode = ble_hs_hci_util_opcode_join( BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_PRIVACY_MODE), }, { 0 } }; void ble_hs_test_util_hci_ack_set_seq(const struct ble_hs_test_util_hci_ack *acks) { int i; for (i = 0; acks[i].opcode != 0; i++) { ble_hs_test_util_hci_acks[i] = acks[i]; } ble_hs_test_util_hci_num_acks = i; ble_hs_hci_set_phony_ack_cb(ble_hs_test_util_hci_ack_cb); } int ble_hs_test_util_hci_startup_seq_cnt(void) { /* last element is terminator, don't count it*/ return sizeof(hci_startup_seq)/sizeof(hci_startup_seq[0]) - 1; } void ble_hs_test_util_hci_ack_set_startup(void) { /* Receive acknowledgements for the startup sequence. We sent the * corresponding requests when the host task was started. */ ble_hs_test_util_hci_ack_set_seq(hci_startup_seq); } void ble_hs_test_util_hci_ack_set_disc(uint8_t own_addr_type, int fail_idx, uint8_t fail_status) { static bool privacy_enabled; /* * SET_RPA_TMO should be expected only when test uses RPA and privacy has * not yet been enabled. The Bluetooth stack remembers that privacy is * enabled and does not send SET_RPA_TMO every time. For test purpose * let's track privacy state in here. */ if ((own_addr_type == BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT || own_addr_type == BLE_OWN_ADDR_RPA_RANDOM_DEFAULT) && !privacy_enabled) { privacy_enabled = true; ble_hs_test_util_hci_ack_set_seq(((struct ble_hs_test_util_hci_ack[]) { { BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_RPA_TMO), ble_hs_test_util_hci_misc_exp_status(0, fail_idx, fail_status), }, { BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_SCAN_PARAMS), ble_hs_test_util_hci_misc_exp_status(1, fail_idx, fail_status), }, { BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_SCAN_ENABLE), ble_hs_test_util_hci_misc_exp_status(2, fail_idx, fail_status), }, { 0 } })); } else { ble_hs_test_util_hci_ack_set_seq(((struct ble_hs_test_util_hci_ack[]) { { BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_SCAN_PARAMS), ble_hs_test_util_hci_misc_exp_status(0, fail_idx, fail_status), }, { BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_SCAN_ENABLE), ble_hs_test_util_hci_misc_exp_status(1, fail_idx, fail_status), }, { 0 } })); } } void ble_hs_test_util_hci_ack_set_disconnect(uint8_t hci_status) { ble_hs_test_util_hci_ack_set( ble_hs_hci_util_opcode_join(BLE_HCI_OGF_LINK_CTRL, BLE_HCI_OCF_DISCONNECT_CMD), hci_status); } /***************************************************************************** * $verify tx * *****************************************************************************/ void ble_hs_test_util_hci_verify_tx_add_irk(uint8_t addr_type, const uint8_t *addr, const uint8_t *peer_irk, const uint8_t *local_irk) { uint8_t param_len; uint8_t *param; param = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_ADD_RESOLV_LIST, ¶m_len); TEST_ASSERT(param_len == BLE_HCI_ADD_TO_RESOLV_LIST_LEN); TEST_ASSERT(param[0] == addr_type); TEST_ASSERT(memcmp(param + 1, addr, 6) == 0); TEST_ASSERT(memcmp(param + 7, peer_irk, 16) == 0); TEST_ASSERT(memcmp(param + 23, local_irk, 16) == 0); } void ble_hs_test_util_hci_verify_tx_set_priv_mode(uint8_t addr_type, const uint8_t *addr, uint8_t priv_mode) { uint8_t param_len; uint8_t *param; param = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_PRIVACY_MODE, ¶m_len); TEST_ASSERT(param_len == BLE_HCI_LE_SET_PRIVACY_MODE_LEN); TEST_ASSERT(param[0] == addr_type); TEST_ASSERT(memcmp(param + 1, addr, 6) == 0); TEST_ASSERT(param[7] == priv_mode); } void ble_hs_test_util_hci_verify_tx_disconnect(uint16_t handle, uint8_t reason) { uint8_t param_len; uint8_t *param; param = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LINK_CTRL, BLE_HCI_OCF_DISCONNECT_CMD, ¶m_len); TEST_ASSERT(param_len == BLE_HCI_DISCONNECT_CMD_LEN); TEST_ASSERT(get_le16(param + 0) == handle); TEST_ASSERT(param[2] == reason); } void ble_hs_test_util_hci_verify_tx_create_conn(const struct hci_create_conn *exp) { uint8_t param_len; uint8_t *param; param = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_CREATE_CONN, ¶m_len); TEST_ASSERT(param_len == BLE_HCI_CREATE_CONN_LEN); TEST_ASSERT(get_le16(param + 0) == exp->scan_itvl); TEST_ASSERT(get_le16(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(get_le16(param + 13) == exp->conn_itvl_min); TEST_ASSERT(get_le16(param + 15) == exp->conn_itvl_max); TEST_ASSERT(get_le16(param + 17) == exp->conn_latency); TEST_ASSERT(get_le16(param + 19) == exp->supervision_timeout); TEST_ASSERT(get_le16(param + 21) == exp->min_ce_len); TEST_ASSERT(get_le16(param + 23) == exp->max_ce_len); } uint8_t * ble_hs_test_util_hci_verify_tx(uint8_t ogf, uint16_t ocf, uint8_t *out_param_len) { uint16_t opcode; uint8_t *cmd; cmd = ble_hs_test_util_hci_out_first(); TEST_ASSERT_FATAL(cmd != NULL); opcode = get_le16(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; } /***************************************************************************** * $rx * *****************************************************************************/ static void ble_hs_test_util_hci_rx_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); evbuf = ble_transport_alloc_evt(1); TEST_ASSERT_FATAL(evbuf != NULL); memcpy(evbuf, evt, totlen); if (os_started()) { rc = ble_transport_to_hs_evt(evbuf); } else { rc = ble_hs_hci_evt_process((void *)evbuf); } TEST_ASSERT_FATAL(rc == 0); } void ble_hs_test_util_hci_rx_num_completed_pkts_event( struct ble_hs_test_util_hci_num_completed_pkts_entry *entries) { struct ble_hs_test_util_hci_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++) { put_le16(buf + off, entries[i].handle_id); off += 2; put_le16(buf + off, entries[i].num_pkts); off += 2; } buf[1] = off - 2; ble_hs_test_util_hci_rx_evt(buf); } void ble_hs_test_util_hci_rx_disconn_complete_event(uint16_t conn_handle, uint8_t status, uint8_t reason) { 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] = status; put_le16(buf + 3, conn_handle); buf[5] = reason; ble_hs_test_util_hci_rx_evt(buf); } void ble_hs_test_util_hci_rx_conn_cancel_evt(void) { struct ble_gap_conn_complete evt; int rc; memset(&evt, 0, sizeof evt); evt.status = BLE_ERR_UNK_CONN_ID; /* test if host correctly ignores other fields if status is error */ evt.connection_handle = 0x0fff; rc = ble_gap_rx_conn_complete(&evt, 0); TEST_ASSERT_FATAL(rc == 0); } /***************************************************************************** * $misc * *****************************************************************************/ int ble_hs_test_util_hci_misc_exp_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; } }