/* * 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 #include #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" #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, .uuid = BLE_UUID16_DECLARE(0x1234), .characteristics = (struct ble_gatt_chr_def[]) { { .uuid = BLE_UUID16_DECLARE(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, }, { .uuid = BLE_UUID16_DECLARE(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, const union ble_store_value *val); typedef int ble_store_delete_fn(int obj_type, const 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); 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 = get_le16(om->om_data + 1); return flags; } static void 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); put_le16(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); 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; ble_hs_test_util_init(); ble_gatts_notify_test_num_events = 0; ble_hs_test_util_reg_svcs(ble_gatts_notify_test_svcs, ble_gatts_notify_test_misc_reg_cb, NULL); 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_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_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_u16(ctxt->chr.chr_def->uuid); 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, const uint8_t *attr_data, int attr_len) { struct ble_att_notify_req req; struct os_mbuf *om; int i; 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, const uint8_t *attr_data, int attr_len) { struct ble_att_indicate_req req; struct os_mbuf *om; int i; 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) { static const uint8_t fourbytes[] = { 1, 2, 3, 4 }; struct os_mbuf *om; uint16_t conn_handle; uint16_t flags; int rc; 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); /* Verify custom notification data. */ om = ble_hs_mbuf_from_flat(fourbytes, sizeof fourbytes); TEST_ASSERT_FATAL(om != NULL); rc = ble_gattc_notify_custom(conn_handle, ble_gatts_notify_test_chr_1_def_handle + 1, om); TEST_ASSERT_FATAL(rc == 0); ble_gatts_notify_test_misc_verify_tx_n( conn_handle, ble_gatts_notify_test_chr_1_def_handle + 1, fourbytes, sizeof fourbytes); /* 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) { static const uint8_t fourbytes[] = { 1, 2, 3, 4 }; struct os_mbuf *om; uint16_t conn_handle; uint16_t flags; int rc; ble_gatts_notify_test_misc_init(&conn_handle, 0, BLE_GATTS_CLT_CFG_F_INDICATE, BLE_GATTS_CLT_CFG_F_INDICATE); /* Verify custom indication data. */ om = ble_hs_mbuf_from_flat(fourbytes, sizeof fourbytes); TEST_ASSERT_FATAL(om != NULL); rc = ble_gattc_indicate_custom(conn_handle, ble_gatts_notify_test_chr_1_def_handle + 1, om); TEST_ASSERT_FATAL(rc == 0); ble_gatts_notify_test_misc_verify_tx_i( conn_handle, ble_gatts_notify_test_chr_1_def_handle + 1, fourbytes, sizeof fourbytes); /* 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); /* 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. */ 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_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_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_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_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. */ 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_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_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_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 = *BLE_ADDR_ANY; 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_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_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; const struct ble_gatt_svc_def svcs[] = { { .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = BLE_UUID16_DECLARE(0x1234), .characteristics = (struct ble_gatt_chr_def[]) { { .uuid = BLE_UUID16_DECLARE(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, }, { .uuid = BLE_UUID16_DECLARE(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, }, { .uuid = BLE_UUID16_DECLARE(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(); ble_hs_test_util_reg_svcs(svcs, NULL, NULL); TEST_ASSERT_FATAL(chr1_val_handle != 0); TEST_ASSERT_FATAL(chr2_val_handle != 0); TEST_ASSERT_FATAL(chr3_val_handle != 0); 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; }