Skip to content

Commit a04f248

Browse files
nimble/host: Add unit tests for maintaining Client Supported Features
Introduce a comprehensive unit test suite to validate the persistance, restoration, and specification compliance of the Client Supported Features characteristic values across peer bonding lifecycles.
1 parent d1af0f3 commit a04f248

3 files changed

Lines changed: 337 additions & 0 deletions

File tree

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include <string.h>
21+
#include <errno.h>
22+
#include "testutil/testutil.h"
23+
#include "nimble/ble.h"
24+
#include "host/ble_uuid.h"
25+
#include "ble_hs_test.h"
26+
#include "ble_hs_test_util.h"
27+
#include "../src/ble_gatt_priv.h"
28+
29+
#define BLE_GATTS_TEST_CL_SUP_FEAT_CHR_UUID 0x1111
30+
#define BLE_GATT_TEST_CL_SUP_FEAT_CHR_SZ 1
31+
32+
/* Client supported features bit positions*/
33+
#define BLE_GATT_TEST_CLI_SUP_FEAT_ROBUST_CACHING_BIT 0x00
34+
#define BLE_GATT_TEST_CLI_SUP_FEAT_EATT_BIT 0x01
35+
#define BLE_GATT_TEST_CLI_SUP_FEAT_MULT_NTF_BIT 0x02
36+
37+
/* --- Client Supported Features masks --- */
38+
#define BLE_GATT_TEST_CLI_SUP_FEAT_NONE_MASK 0x00
39+
#define BLE_GATT_TEST_CLI_SUP_FEAT_ROBUST_CACHING_MASK 0x01
40+
#define BLE_GATT_TEST_CLI_SUP_FEAT_EATT_MASK 0x02
41+
#define BLE_GATT_TEST_CLI_SUP_FEAT_MULT_NTF_MASK 0x04
42+
#define BLE_GATT_TEST_CLI_SUP_FEAT_ROBUST_EATT_MASK 0x03
43+
#define BLE_GATT_TEST_CLI_SUP_FEAT_ROBUST_MULT_NTF_MASK 0x05
44+
#define BLE_GATT_TEST_CLI_SUP_FEAT_EATT_MULT_NTF_MASK 0x06
45+
#define BLE_GATT_TEST_CLI_SUP_FEAT_ALL_MASK 0x07
46+
47+
static uint8_t ble_gatts_cl_sup_feat_test_peer_addr[6] = { 1, 2, 3, 4, 5, 6 };
48+
49+
static int
50+
ble_gatts_cl_sup_feat_test_misc_access(uint16_t conn_handle, uint16_t attr_handle,
51+
struct ble_gatt_access_ctxt *ctxt, void *arg)
52+
{
53+
uint8_t supported_feat;
54+
int rc;
55+
56+
TEST_ASSERT(conn_handle != 0xffff);
57+
58+
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
59+
rc = ble_gatts_peer_cl_sup_feat_get(conn_handle, &supported_feat, 1);
60+
if (rc == 0) {
61+
rc = os_mbuf_append(ctxt->om, &supported_feat, 1);
62+
}
63+
return rc;
64+
}
65+
66+
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
67+
rc = ble_gatts_peer_cl_sup_feat_update(conn_handle, ctxt->om);
68+
return rc;
69+
}
70+
71+
return BLE_ATT_ERR_UNLIKELY;
72+
}
73+
74+
static const struct ble_gatt_svc_def ble_gatts_cl_sup_feat_test_svcs[] = {
75+
{
76+
.type = BLE_GATT_SVC_TYPE_PRIMARY,
77+
.uuid = BLE_UUID16_DECLARE(0x1234),
78+
.characteristics =
79+
(struct ble_gatt_chr_def[]){
80+
{
81+
.uuid = BLE_UUID16_DECLARE(BLE_GATTS_TEST_CL_SUP_FEAT_CHR_UUID),
82+
.access_cb = ble_gatts_cl_sup_feat_test_misc_access,
83+
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE,
84+
},
85+
{ 0 } },
86+
},
87+
{ 0 }
88+
};
89+
90+
static uint16_t ble_gatts_cl_sup_feat_test_chr_def_handle;
91+
static uint16_t ble_gatts_cl_sup_feat_test_chr_val_handle;
92+
static uint8_t ble_gatts_cl_sup_feat_test_chr_val[BLE_GATT_TEST_CL_SUP_FEAT_CHR_SZ] = { 0 };
93+
94+
static void
95+
ble_gatts_notify_test_misc_reg_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
96+
{
97+
uint16_t uuid16;
98+
99+
if (ctxt->op == BLE_GATT_REGISTER_OP_CHR) {
100+
uuid16 = ble_uuid_u16(ctxt->chr.chr_def->uuid);
101+
switch (uuid16) {
102+
case BLE_GATTS_TEST_CL_SUP_FEAT_CHR_UUID:
103+
ble_gatts_cl_sup_feat_test_chr_def_handle = ctxt->chr.def_handle;
104+
ble_gatts_cl_sup_feat_test_chr_val_handle = ctxt->chr.val_handle;
105+
break;
106+
107+
default:
108+
TEST_ASSERT_FATAL(0);
109+
break;
110+
}
111+
}
112+
}
113+
114+
static void
115+
ble_gatts_cl_sup_feat_test_misc_init(uint16_t *out_conn_handle, int bonding)
116+
{
117+
ble_hs_test_util_init();
118+
119+
ble_hs_test_util_reg_svcs(ble_gatts_cl_sup_feat_test_svcs,
120+
ble_gatts_notify_test_misc_reg_cb, NULL);
121+
TEST_ASSERT_FATAL(ble_gatts_cl_sup_feat_test_chr_def_handle != 0);
122+
123+
ble_hs_test_util_create_conn(1, ble_gatts_cl_sup_feat_test_peer_addr, NULL, NULL);
124+
*out_conn_handle = 1;
125+
}
126+
127+
static void
128+
ble_gatts_cl_sup_feat_test_perform_bonding(uint16_t conn_handle)
129+
{
130+
struct ble_hs_conn *conn;
131+
132+
ble_hs_lock();
133+
conn = ble_hs_conn_find(conn_handle);
134+
TEST_ASSERT_FATAL(conn != NULL);
135+
conn->bhc_sec_state.encrypted = 1;
136+
conn->bhc_sec_state.authenticated = 1;
137+
conn->bhc_sec_state.bonded = 1;
138+
ble_hs_unlock();
139+
}
140+
141+
TEST_CASE_SELF(ble_gatts_cl_sup_feat_test_bond_then_write)
142+
{
143+
uint16_t conn_handle = 1;
144+
uint16_t conn_handle_2 = 2;
145+
uint8_t out_feat = 0;
146+
int rc;
147+
148+
ble_gatts_cl_sup_feat_test_misc_init(&conn_handle, 0);
149+
150+
ble_gatts_cl_sup_feat_test_perform_bonding(conn_handle);
151+
ble_gatts_bonding_established(conn_handle);
152+
153+
/* Peer with trusted relationship enables EATT in Client Supported
154+
* Features Characteristic */
155+
ble_gatts_cl_sup_feat_test_chr_val[0] = BLE_GATT_TEST_CLI_SUP_FEAT_EATT_MASK;
156+
ble_hs_test_util_rx_att_write_req(
157+
conn_handle, ble_gatts_cl_sup_feat_test_chr_val_handle,
158+
ble_gatts_cl_sup_feat_test_chr_val, BLE_GATT_TEST_CL_SUP_FEAT_CHR_SZ);
159+
160+
ble_hs_test_util_conn_disconnect(conn_handle);
161+
ble_hs_test_util_create_conn(conn_handle_2,
162+
ble_gatts_cl_sup_feat_test_peer_addr, NULL, NULL);
163+
164+
ble_gatts_cl_sup_feat_test_perform_bonding(conn_handle_2);
165+
ble_gatts_bonding_restored(conn_handle_2);
166+
167+
/* Verify Client Supported Features value is persisted for this peer after
168+
* reconnection.*/
169+
rc = ble_gatts_peer_cl_sup_feat_get(conn_handle_2, &out_feat, 1);
170+
171+
TEST_ASSERT(rc == 0);
172+
TEST_ASSERT(out_feat == BLE_GATT_TEST_CLI_SUP_FEAT_EATT_MASK);
173+
}
174+
175+
TEST_CASE_SELF(ble_gatts_cl_sup_feat_test_write_then_bond)
176+
{
177+
uint16_t conn_handle = 1;
178+
uint16_t new_conn_handle = 2;
179+
uint8_t out_feat = 0;
180+
int rc;
181+
182+
ble_gatts_cl_sup_feat_test_misc_init(&conn_handle, 0);
183+
184+
/* Peer enables Robust Caching in Client Supported Features Characteristic
185+
* prior to bonding*/
186+
ble_gatts_cl_sup_feat_test_chr_val[0] =
187+
BLE_GATT_TEST_CLI_SUP_FEAT_ROBUST_CACHING_MASK;
188+
ble_hs_test_util_rx_att_write_req(
189+
conn_handle, ble_gatts_cl_sup_feat_test_chr_val_handle,
190+
ble_gatts_cl_sup_feat_test_chr_val, BLE_GATT_TEST_CL_SUP_FEAT_CHR_SZ);
191+
192+
/* Bonding should result in maintaining last known Client Supported
193+
* Features value for this peer */
194+
ble_gatts_cl_sup_feat_test_perform_bonding(conn_handle);
195+
ble_gatts_bonding_established(conn_handle);
196+
197+
ble_hs_test_util_conn_disconnect(conn_handle);
198+
ble_hs_test_util_create_conn(new_conn_handle,
199+
ble_gatts_cl_sup_feat_test_peer_addr, NULL, NULL);
200+
201+
ble_gatts_cl_sup_feat_test_perform_bonding(new_conn_handle);
202+
ble_gatts_bonding_restored(new_conn_handle);
203+
204+
/* Verify Client Supported Features value is persisted for this peer after
205+
* reconnection.*/
206+
rc = ble_gatts_peer_cl_sup_feat_get(new_conn_handle, &out_feat, 1);
207+
208+
TEST_ASSERT(rc == 0);
209+
TEST_ASSERT(out_feat == BLE_GATT_TEST_CLI_SUP_FEAT_ROBUST_CACHING_MASK);
210+
}
211+
212+
TEST_CASE_SELF(ble_gatts_cl_sup_feat_test_unbonded_no_persist)
213+
{
214+
uint16_t conn_handle = 1;
215+
uint16_t new_conn_handle = 2;
216+
uint8_t out_feat = 0xFF; /* Initialize to dummy value */
217+
int rc;
218+
219+
ble_gatts_cl_sup_feat_test_misc_init(&conn_handle, 0);
220+
221+
/* Peer enables EATT in Client Supported Features Characteristic */
222+
ble_gatts_cl_sup_feat_test_chr_val[0] = BLE_GATT_TEST_CLI_SUP_FEAT_EATT_MASK;
223+
ble_hs_test_util_rx_att_write_req(
224+
conn_handle, ble_gatts_cl_sup_feat_test_chr_val_handle,
225+
ble_gatts_cl_sup_feat_test_chr_val, BLE_GATT_TEST_CL_SUP_FEAT_CHR_SZ);
226+
227+
/* Disconnect (there was no bonding) */
228+
ble_hs_test_util_conn_disconnect(conn_handle);
229+
230+
ble_hs_test_util_create_conn(new_conn_handle,
231+
ble_gatts_cl_sup_feat_test_peer_addr, NULL, NULL);
232+
233+
/* Verify Client Supported Features value wasn't persisted */
234+
rc = ble_gatts_peer_cl_sup_feat_get(new_conn_handle, &out_feat, 1);
235+
236+
TEST_ASSERT(rc == 0);
237+
TEST_ASSERT(out_feat == 0x00);
238+
}
239+
240+
TEST_CASE_SELF(ble_gatts_cl_sup_feat_test_enable_new_feature_across_sessions)
241+
{
242+
uint16_t conn_handle = 1;
243+
uint16_t conn_handle_2 = 2;
244+
uint16_t conn_handle_3 = 3;
245+
uint8_t out_feat = 0;
246+
int rc;
247+
248+
ble_gatts_cl_sup_feat_test_misc_init(&conn_handle, 0);
249+
250+
ble_gatts_cl_sup_feat_test_perform_bonding(conn_handle);
251+
ble_gatts_bonding_established(conn_handle);
252+
253+
/* Peer with trusted relationship enables EATT and MULT NTF in Client
254+
* Supported Features Characteristic */
255+
ble_gatts_cl_sup_feat_test_chr_val[0] =
256+
BLE_GATT_TEST_CLI_SUP_FEAT_EATT_MULT_NTF_MASK;
257+
ble_hs_test_util_rx_att_write_req(
258+
conn_handle, ble_gatts_cl_sup_feat_test_chr_val_handle,
259+
ble_gatts_cl_sup_feat_test_chr_val, BLE_GATT_TEST_CL_SUP_FEAT_CHR_SZ);
260+
261+
ble_hs_test_util_conn_disconnect(conn_handle);
262+
ble_hs_test_util_create_conn(conn_handle_2,
263+
ble_gatts_cl_sup_feat_test_peer_addr, NULL, NULL);
264+
265+
ble_gatts_cl_sup_feat_test_perform_bonding(conn_handle_2);
266+
ble_gatts_bonding_restored(conn_handle_2);
267+
268+
/* After rebonding, peer enables all features in Client Supported Features
269+
* Characteristic */
270+
ble_gatts_cl_sup_feat_test_chr_val[0] = BLE_GATT_TEST_CLI_SUP_FEAT_ALL_MASK;
271+
ble_hs_test_util_rx_att_write_req(
272+
conn_handle_2, ble_gatts_cl_sup_feat_test_chr_val_handle,
273+
ble_gatts_cl_sup_feat_test_chr_val, BLE_GATT_TEST_CL_SUP_FEAT_CHR_SZ);
274+
275+
ble_hs_test_util_conn_disconnect(conn_handle_2);
276+
ble_hs_test_util_create_conn(conn_handle_3,
277+
ble_gatts_cl_sup_feat_test_peer_addr, NULL, NULL);
278+
279+
ble_gatts_cl_sup_feat_test_perform_bonding(conn_handle_3);
280+
ble_gatts_bonding_restored(conn_handle_3);
281+
282+
/* Verify Client Supported Features value is persisted for this peer after
283+
* reconnection i.e. all features should be enabled. */
284+
rc = ble_gatts_peer_cl_sup_feat_get(conn_handle_3, &out_feat, 1);
285+
TEST_ASSERT(rc == 0);
286+
TEST_ASSERT(out_feat == BLE_GATT_TEST_CLI_SUP_FEAT_ALL_MASK);
287+
}
288+
289+
TEST_CASE_SELF(ble_gatts_cl_sup_feat_test_reject_disabling_already_enabled)
290+
{
291+
uint16_t conn_handle = 1;
292+
uint16_t new_conn_handle = 2;
293+
uint8_t out_feat = 0;
294+
int rc;
295+
296+
ble_gatts_cl_sup_feat_test_misc_init(&conn_handle, 0);
297+
298+
ble_gatts_cl_sup_feat_test_perform_bonding(conn_handle);
299+
ble_gatts_bonding_established(conn_handle);
300+
301+
/* Peer with trusted relationship enables EATT and MULT NTF in Client
302+
* Supported Features Characteristic */
303+
ble_gatts_cl_sup_feat_test_chr_val[0] =
304+
BLE_GATT_TEST_CLI_SUP_FEAT_EATT_MULT_NTF_MASK;
305+
ble_hs_test_util_rx_att_write_req(
306+
conn_handle, ble_gatts_cl_sup_feat_test_chr_val_handle,
307+
ble_gatts_cl_sup_feat_test_chr_val, BLE_GATT_TEST_CL_SUP_FEAT_CHR_SZ);
308+
309+
ble_hs_test_util_conn_disconnect(conn_handle);
310+
ble_hs_test_util_create_conn(new_conn_handle,
311+
ble_gatts_cl_sup_feat_test_peer_addr, NULL, NULL);
312+
313+
ble_gatts_cl_sup_feat_test_perform_bonding(new_conn_handle);
314+
ble_gatts_bonding_restored(new_conn_handle);
315+
316+
/* Attempt to disable EATT feature */
317+
ble_gatts_cl_sup_feat_test_chr_val[0] = BLE_GATT_TEST_CLI_SUP_FEAT_MULT_NTF_BIT;
318+
ble_hs_test_util_rx_att_write_req(
319+
new_conn_handle, ble_gatts_cl_sup_feat_test_chr_val_handle,
320+
ble_gatts_cl_sup_feat_test_chr_val, BLE_GATT_TEST_CL_SUP_FEAT_CHR_SZ);
321+
322+
/* Verify original features from storage weren't overwritten */
323+
rc = ble_gatts_peer_cl_sup_feat_get(new_conn_handle, &out_feat, 1);
324+
TEST_ASSERT(rc == 0);
325+
TEST_ASSERT(out_feat == BLE_GATT_TEST_CLI_SUP_FEAT_EATT_MULT_NTF_MASK);
326+
}
327+
328+
TEST_SUITE(ble_gatts_cl_sup_feat_suite)
329+
{
330+
ble_gatts_cl_sup_feat_test_bond_then_write();
331+
ble_gatts_cl_sup_feat_test_write_then_bond();
332+
ble_gatts_cl_sup_feat_test_unbonded_no_persist();
333+
ble_gatts_cl_sup_feat_test_enable_new_feature_across_sessions();
334+
ble_gatts_cl_sup_feat_test_reject_disabling_already_enabled();
335+
}

nimble/host/test/src/ble_hs_test.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ main(int argc, char **argv)
7676
ble_sm_sc_test_suite();
7777
ble_store_suite();
7878
ble_uuid_test_suite();
79+
ble_gatts_cl_sup_feat_suite();
7980

8081
return tu_any_failed;
8182
}

nimble/host/test/src/ble_hs_test.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,6 @@ TEST_SUITE_DECL(ble_sm_lgcy_test_suite);
5858
TEST_SUITE_DECL(ble_sm_sc_test_suite);
5959
TEST_SUITE_DECL(ble_store_suite);
6060
TEST_SUITE_DECL(ble_uuid_test_suite);
61+
TEST_SUITE_DECL(ble_gatts_cl_sup_feat_suite);
6162

6263
#endif

0 commit comments

Comments
 (0)