1 /*
2  * Copyright (c) 2021 Nordic Semiconductor
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 #include "mesh_test.h"
7 #include "argparse.h"
8 #include "mesh/net.h"
9 #include "mesh/heartbeat.h"
10 #include "mesh/lpn.h"
11 
12 #define LOG_MODULE_NAME test_heartbeat
13 
14 #include <zephyr/logging/log.h>
15 LOG_MODULE_REGISTER(LOG_MODULE_NAME);
16 
17 #define WAIT_TIME 60 /*seconds*/
18 #define SUBSCRIBER_ADDR 0x00fe
19 #define SUBSCRIBE_PERIOD_SEC 30
20 #define PUBLISHER_ADDR_START 0x0001
21 #define PUBLISH_PERIOD_SEC 1
22 #define PUBLISH_MSG_CNT 10
23 #define PUBLISH_TTL 0
24 #define EXPECTED_HB_HOPS 0x01
25 
26 static uint16_t pub_addr = BT_MESH_ADDR_UNASSIGNED;
27 
28 static const struct bt_mesh_test_cfg subscribe_cfg = {
29 	.addr = SUBSCRIBER_ADDR,
30 	.dev_key = { 0xff },
31 };
32 static struct bt_mesh_test_cfg pub_cfg;
33 static int pub_cnt;
34 struct k_sem sem;
35 
test_publish_init(void)36 static void test_publish_init(void)
37 {
38 	pub_cfg.addr = PUBLISHER_ADDR_START + get_device_nbr();
39 	pub_cfg.dev_key[0] = get_device_nbr();
40 	bt_mesh_test_cfg_set(&pub_cfg, WAIT_TIME);
41 }
42 
test_subscribe_init(void)43 static void test_subscribe_init(void)
44 {
45 	bt_mesh_test_cfg_set(&subscribe_cfg, WAIT_TIME);
46 }
47 
48 static struct sub_context {
49 	uint8_t count;
50 	uint8_t min_hops;
51 	uint8_t max_hops;
52 } sub_ctx = {
53 	.count = 0,
54 	.min_hops = 0xFF,
55 	.max_hops = 0,
56 };
57 
sub_hb_recv_cb(const struct bt_mesh_hb_sub * sub,uint8_t hops,uint16_t feat)58 static void sub_hb_recv_cb(const struct bt_mesh_hb_sub *sub, uint8_t hops, uint16_t feat)
59 {
60 	LOG_INF("Heartbeat received from addr: 0x%04x", sub->src);
61 
62 	ASSERT_EQUAL(PUBLISHER_ADDR_START, sub->src);
63 	ASSERT_EQUAL(pub_addr, sub->dst);
64 	ASSERT_EQUAL(SUBSCRIBE_PERIOD_SEC, sub->period);
65 	ASSERT_TRUE(sub->remaining <= SUBSCRIBE_PERIOD_SEC);
66 	ASSERT_EQUAL(sub_ctx.count + 1, sub->count);
67 	ASSERT_EQUAL(hops, EXPECTED_HB_HOPS);
68 
69 	uint16_t feature = 0;
70 
71 	if (bt_mesh_relay_get() == BT_MESH_RELAY_ENABLED) {
72 		feature |= BT_MESH_FEAT_RELAY;
73 	}
74 
75 	if (bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED) {
76 		feature |= BT_MESH_FEAT_PROXY;
77 	}
78 
79 	if (bt_mesh_friend_get() == BT_MESH_FRIEND_ENABLED) {
80 		feature |= BT_MESH_FEAT_FRIEND;
81 	}
82 
83 	if (bt_mesh_lpn_established()) {
84 		feature |= BT_MESH_FEAT_LOW_POWER;
85 	}
86 
87 	ASSERT_EQUAL(feature, feat);
88 
89 	sub_ctx.count++;
90 	sub_ctx.min_hops = MIN(sub_ctx.min_hops, sub->min_hops);
91 	sub_ctx.max_hops = MAX(sub_ctx.max_hops, sub->max_hops);
92 }
93 
sub_hb_end_cb(const struct bt_mesh_hb_sub * sub)94 static void sub_hb_end_cb(const struct bt_mesh_hb_sub *sub)
95 {
96 	LOG_INF("Heartbeat subscription has ended");
97 	ASSERT_EQUAL(PUBLISHER_ADDR_START, sub->src);
98 	ASSERT_EQUAL(pub_addr, sub->dst);
99 	ASSERT_EQUAL(SUBSCRIBE_PERIOD_SEC, sub->period);
100 	ASSERT_EQUAL(0, sub->remaining);
101 	ASSERT_EQUAL(PUBLISH_MSG_CNT, sub->count);
102 	ASSERT_EQUAL(sub_ctx.count, sub->count);
103 	ASSERT_EQUAL(sub_ctx.min_hops, sub->min_hops);
104 	ASSERT_EQUAL(sub_ctx.max_hops, sub->max_hops);
105 	PASS();
106 }
107 
pub_hb_sent_cb(const struct bt_mesh_hb_pub * pub)108 static void pub_hb_sent_cb(const struct bt_mesh_hb_pub *pub)
109 {
110 	LOG_INF("Heartbeat publication has ended");
111 
112 	pub_cnt--;
113 
114 	ASSERT_EQUAL(pub_addr, pub->dst);
115 	ASSERT_EQUAL(pub_cnt, pub->count);
116 	ASSERT_EQUAL(PUBLISH_PERIOD_SEC, pub->period);
117 	ASSERT_EQUAL(0, pub->net_idx);
118 	ASSERT_EQUAL(PUBLISH_TTL, pub->ttl);
119 	ASSERT_EQUAL(BT_MESH_FEAT_SUPPORTED, pub->feat);
120 
121 	if (pub_cnt == 0) {
122 		k_sem_give(&sem);
123 	}
124 
125 	if (pub_cnt < 0) {
126 		LOG_ERR("Published more times than expected");
127 		FAIL();
128 	}
129 }
130 
131 BT_MESH_HB_CB_DEFINE(hb_cb) = {
132 	.recv = sub_hb_recv_cb,
133 	.sub_end = sub_hb_end_cb,
134 	.pub_sent = pub_hb_sent_cb
135 };
136 
publish_common(void)137 static void publish_common(void)
138 {
139 	bt_mesh_test_setup();
140 	struct bt_mesh_hb_pub new_pub = {
141 		.dst = pub_addr,
142 		.count = PUBLISH_MSG_CNT,
143 		.period = PUBLISH_PERIOD_SEC,
144 		.net_idx = 0,
145 		.ttl = PUBLISH_TTL,
146 		.feat = BT_MESH_FEAT_SUPPORTED
147 	};
148 
149 	pub_cnt = PUBLISH_MSG_CNT;
150 	bt_mesh_hb_pub_set(&new_pub);
151 }
152 
publish_process(void)153 static void publish_process(void)
154 {
155 	k_sem_init(&sem, 0, 1);
156 	publish_common();
157 	/* +1 to avoid boundary time rally */
158 	if (k_sem_take(&sem, K_SECONDS(PUBLISH_PERIOD_SEC * (PUBLISH_MSG_CNT + 1)))) {
159 		LOG_ERR("Publishing timed out");
160 		FAIL();
161 	}
162 }
163 
test_publish_unicast(void)164 static void test_publish_unicast(void)
165 {
166 	pub_addr = SUBSCRIBER_ADDR;
167 	publish_process();
168 
169 	PASS();
170 }
171 
test_publish_all(void)172 static void test_publish_all(void)
173 {
174 	pub_addr = BT_MESH_ADDR_ALL_NODES;
175 	publish_process();
176 
177 	PASS();
178 }
179 
subscribe_commmon(void)180 static void subscribe_commmon(void)
181 {
182 	bt_mesh_test_setup();
183 	bt_mesh_hb_sub_set(PUBLISHER_ADDR_START, pub_addr, SUBSCRIBE_PERIOD_SEC);
184 }
185 
test_subscribe_unicast(void)186 static void test_subscribe_unicast(void)
187 {
188 	pub_addr = SUBSCRIBER_ADDR;
189 	subscribe_commmon();
190 }
191 
test_subscribe_all(void)192 static void test_subscribe_all(void)
193 {
194 	pub_addr = BT_MESH_ADDR_ALL_NODES;
195 	subscribe_commmon();
196 }
197 
198 #define TEST_CASE(role, name, description)                                     \
199 	{                                                                      \
200 		.test_id = "heartbeat_" #role "_" #name,                       \
201 		.test_descr = description,                                     \
202 		.test_post_init_f = test_##role##_init,                        \
203 		.test_tick_f = bt_mesh_test_timeout,                           \
204 		.test_main_f = test_##role##_##name,                           \
205 	}
206 
207 static const struct bst_test_instance test_connect[] = {
208 	TEST_CASE(publish, unicast,     "Heartbeat: Publish heartbeat to unicast"),
209 	TEST_CASE(subscribe, unicast,   "Heartbeat: Subscribe to heartbeat from unicast"),
210 	TEST_CASE(publish, all,         "Heartbeat: Publish heartbeat to all nodes"),
211 	TEST_CASE(subscribe, all,       "Heartbeat: Subscribe to heartbeat all nodes"),
212 	BSTEST_END_MARKER
213 };
214 
test_heartbeat_install(struct bst_test_list * tests)215 struct bst_test_list *test_heartbeat_install(struct bst_test_list *tests)
216 {
217 	tests = bst_add_tests(tests, test_connect);
218 	return tests;
219 }
220