1 /*
2 * Copyright (c) 2020 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6 #include <zephyr/bluetooth/mesh.h>
7 #include <zephyr/sys/iterable_sections.h>
8
9 #include "net.h"
10 #include "rpl.h"
11 #include "access.h"
12 #include "lpn.h"
13 #include "settings.h"
14 #include "mesh.h"
15 #include "transport.h"
16 #include "heartbeat.h"
17 #include "foundation.h"
18
19 #define LOG_LEVEL CONFIG_BT_MESH_TRANS_LOG_LEVEL
20 #include <zephyr/logging/log.h>
21 LOG_MODULE_REGISTER(bt_mesh_hb);
22
23 /* Heartbeat Publication information for persistent storage. */
24 struct hb_pub_val {
25 uint16_t dst;
26 uint8_t period;
27 uint8_t ttl;
28 uint16_t feat;
29 uint16_t net_idx:12,
30 indefinite:1;
31 };
32
33 static struct bt_mesh_hb_pub pub;
34 static struct bt_mesh_hb_sub sub;
35 static struct k_work_delayable sub_timer;
36 static struct k_work_delayable pub_timer;
37
notify_pub_sent(void)38 static void notify_pub_sent(void)
39 {
40 STRUCT_SECTION_FOREACH(bt_mesh_hb_cb, cb) {
41 if (cb->pub_sent) {
42 cb->pub_sent(&pub);
43 }
44 }
45 }
46
sub_remaining(void)47 static int64_t sub_remaining(void)
48 {
49 if (sub.dst == BT_MESH_ADDR_UNASSIGNED) {
50 return 0U;
51 }
52
53 uint32_t rem_ms = k_ticks_to_ms_floor32(
54 k_work_delayable_remaining_get(&sub_timer));
55
56 return rem_ms / MSEC_PER_SEC;
57 }
58
hb_publish_end_cb(int err,void * cb_data)59 static void hb_publish_end_cb(int err, void *cb_data)
60 {
61 if (pub.period && pub.count > 1) {
62 k_work_reschedule(&pub_timer, K_SECONDS(pub.period));
63 }
64
65 if (pub.count != 0xffff) {
66 pub.count--;
67 }
68
69 if (!err) {
70 notify_pub_sent();
71 }
72 }
73
notify_recv(uint8_t hops,uint16_t feat)74 static void notify_recv(uint8_t hops, uint16_t feat)
75 {
76 sub.remaining = sub_remaining();
77
78 STRUCT_SECTION_FOREACH(bt_mesh_hb_cb, cb) {
79 if (cb->recv) {
80 cb->recv(&sub, hops, feat);
81 }
82 }
83 }
84
notify_sub_end(void)85 static void notify_sub_end(void)
86 {
87 sub.remaining = 0;
88
89 STRUCT_SECTION_FOREACH(bt_mesh_hb_cb, cb) {
90 if (cb->sub_end) {
91 cb->sub_end(&sub);
92 }
93 }
94 }
95
sub_end(struct k_work * work)96 static void sub_end(struct k_work *work)
97 {
98 notify_sub_end();
99 }
100
heartbeat_send(const struct bt_mesh_send_cb * cb,void * cb_data)101 static int heartbeat_send(const struct bt_mesh_send_cb *cb, void *cb_data)
102 {
103 uint16_t feat = 0U;
104 struct __packed {
105 uint8_t init_ttl;
106 uint16_t feat;
107 } hb;
108 struct bt_mesh_msg_ctx ctx = {
109 .net_idx = pub.net_idx,
110 .app_idx = BT_MESH_KEY_UNUSED,
111 .addr = pub.dst,
112 .send_ttl = pub.ttl,
113 };
114 struct bt_mesh_net_tx tx = {
115 .sub = bt_mesh_subnet_get(pub.net_idx),
116 .ctx = &ctx,
117 .src = bt_mesh_primary_addr(),
118 .xmit = bt_mesh_net_transmit_get(),
119 };
120
121 /* Do nothing if heartbeat publication is not enabled or the subnet is
122 * removed.
123 */
124 if (!tx.sub || pub.dst == BT_MESH_ADDR_UNASSIGNED) {
125 return 0;
126 }
127
128 hb.init_ttl = pub.ttl;
129
130 if (bt_mesh_relay_get() == BT_MESH_RELAY_ENABLED) {
131 feat |= BT_MESH_FEAT_RELAY;
132 }
133
134 if (bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED) {
135 feat |= BT_MESH_FEAT_PROXY;
136 }
137
138 if (bt_mesh_friend_get() == BT_MESH_FRIEND_ENABLED) {
139 feat |= BT_MESH_FEAT_FRIEND;
140 }
141
142 if (bt_mesh_lpn_established()) {
143 feat |= BT_MESH_FEAT_LOW_POWER;
144 }
145
146 hb.feat = sys_cpu_to_be16(feat);
147
148 LOG_DBG("InitTTL %u feat 0x%04x", pub.ttl, feat);
149
150 return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_HEARTBEAT, &hb, sizeof(hb),
151 cb, cb_data);
152 }
153
hb_publish_start_cb(uint16_t duration,int err,void * cb_data)154 static void hb_publish_start_cb(uint16_t duration, int err, void *cb_data)
155 {
156 if (err) {
157 hb_publish_end_cb(err, cb_data);
158 }
159 }
160
hb_publish(struct k_work * work)161 static void hb_publish(struct k_work *work)
162 {
163 static const struct bt_mesh_send_cb publish_cb = {
164 .start = hb_publish_start_cb,
165 .end = hb_publish_end_cb,
166 };
167 struct bt_mesh_subnet *subnet;
168 int err;
169
170 LOG_DBG("hb_pub.count: %u", pub.count);
171
172 /* Fast exit if disabled or expired */
173 if (pub.period == 0U || pub.count == 0U) {
174 return;
175 }
176
177 subnet = bt_mesh_subnet_get(pub.net_idx);
178 if (!subnet) {
179 LOG_ERR("No matching subnet for idx 0x%02x", pub.net_idx);
180 pub.dst = BT_MESH_ADDR_UNASSIGNED;
181 return;
182 }
183
184 err = heartbeat_send(&publish_cb, NULL);
185 if (err) {
186 hb_publish_end_cb(err, NULL);
187 }
188 }
189
bt_mesh_hb_recv(struct bt_mesh_net_rx * rx,struct net_buf_simple * buf)190 int bt_mesh_hb_recv(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf)
191 {
192 uint8_t init_ttl, hops;
193 uint16_t feat;
194
195 if (buf->len < 3) {
196 LOG_ERR("Too short heartbeat message");
197 return -EINVAL;
198 }
199
200 init_ttl = (net_buf_simple_pull_u8(buf) & 0x7f);
201 feat = net_buf_simple_pull_be16(buf);
202
203 hops = (init_ttl - rx->ctx.recv_ttl + 1);
204
205 if (rx->ctx.addr != sub.src || rx->ctx.recv_dst != sub.dst) {
206 LOG_DBG("No subscription for received heartbeat");
207 return 0;
208 }
209
210 if (!k_work_delayable_is_pending(&sub_timer)) {
211 LOG_DBG("Heartbeat subscription inactive");
212 return 0;
213 }
214
215 sub.min_hops = MIN(sub.min_hops, hops);
216 sub.max_hops = MAX(sub.max_hops, hops);
217
218 if (sub.count < 0xffff) {
219 sub.count++;
220 }
221
222 LOG_DBG("src 0x%04x TTL %u InitTTL %u (%u hop%s) feat 0x%04x", rx->ctx.addr,
223 rx->ctx.recv_ttl, init_ttl, hops, (hops == 1U) ? "" : "s", feat);
224
225 notify_recv(hops, feat);
226
227 return 0;
228 }
229
pub_disable(void)230 static void pub_disable(void)
231 {
232 LOG_DBG("");
233
234 pub.dst = BT_MESH_ADDR_UNASSIGNED;
235 pub.count = 0U;
236 pub.period = 0U;
237 pub.ttl = 0U;
238 pub.feat = 0U;
239 pub.net_idx = 0U;
240
241 /* Try to cancel, but it's OK if this still runs (or is
242 * running) as the handler will be a no-op if it hasn't
243 * already checked period for being non-zero.
244 */
245 (void)k_work_cancel_delayable(&pub_timer);
246 }
247
bt_mesh_hb_pub_set(struct bt_mesh_hb_pub * new_pub)248 uint8_t bt_mesh_hb_pub_set(struct bt_mesh_hb_pub *new_pub)
249 {
250 if (!new_pub || new_pub->dst == BT_MESH_ADDR_UNASSIGNED) {
251 pub_disable();
252
253 if (IS_ENABLED(CONFIG_BT_SETTINGS) &&
254 bt_mesh_is_provisioned()) {
255 bt_mesh_settings_store_schedule(
256 BT_MESH_SETTINGS_HB_PUB_PENDING);
257 }
258
259 return STATUS_SUCCESS;
260 }
261
262 if (!bt_mesh_subnet_get(new_pub->net_idx)) {
263 LOG_ERR("Unknown NetKey 0x%04x", new_pub->net_idx);
264 return STATUS_INVALID_NETKEY;
265 }
266
267 new_pub->feat &= BT_MESH_FEAT_SUPPORTED;
268 pub = *new_pub;
269
270 if (!bt_mesh_is_provisioned()) {
271 return STATUS_SUCCESS;
272 }
273
274 /* The first Heartbeat message shall be published as soon as possible
275 * after the Heartbeat Publication Period state has been configured for
276 * periodic publishing.
277 *
278 * If the new configuration disables publishing this flushes
279 * the work item.
280 */
281 k_work_reschedule(&pub_timer, K_NO_WAIT);
282
283 if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
284 bt_mesh_settings_store_schedule(
285 BT_MESH_SETTINGS_HB_PUB_PENDING);
286 }
287
288 return STATUS_SUCCESS;
289 }
290
bt_mesh_hb_pub_get(struct bt_mesh_hb_pub * get)291 void bt_mesh_hb_pub_get(struct bt_mesh_hb_pub *get)
292 {
293 *get = pub;
294 }
295
bt_mesh_hb_sub_set(uint16_t src,uint16_t dst,uint32_t period)296 uint8_t bt_mesh_hb_sub_set(uint16_t src, uint16_t dst, uint32_t period)
297 {
298 if (src != BT_MESH_ADDR_UNASSIGNED && !BT_MESH_ADDR_IS_UNICAST(src)) {
299 LOG_WRN("Prohibited source address");
300 return STATUS_INVALID_ADDRESS;
301 }
302
303 if (BT_MESH_ADDR_IS_VIRTUAL(dst) || BT_MESH_ADDR_IS_RFU(dst) ||
304 (BT_MESH_ADDR_IS_UNICAST(dst) && dst != bt_mesh_primary_addr())) {
305 LOG_WRN("Prohibited destination address");
306 return STATUS_INVALID_ADDRESS;
307 }
308
309 if (period > (1U << 16)) {
310 LOG_WRN("Prohibited subscription period %u s", period);
311 return STATUS_CANNOT_SET;
312 }
313
314 /* Only an explicit address change to unassigned should trigger clearing
315 * of the values according to MESH/NODE/CFG/HBS/BV-02-C.
316 */
317 if (src == BT_MESH_ADDR_UNASSIGNED || dst == BT_MESH_ADDR_UNASSIGNED) {
318 sub.src = BT_MESH_ADDR_UNASSIGNED;
319 sub.dst = BT_MESH_ADDR_UNASSIGNED;
320 sub.min_hops = 0U;
321 sub.max_hops = 0U;
322 sub.count = 0U;
323 sub.period = 0U;
324 } else if (period) {
325 sub.src = src;
326 sub.dst = dst;
327 sub.min_hops = BT_MESH_TTL_MAX;
328 sub.max_hops = 0U;
329 sub.count = 0U;
330 sub.period = period;
331 } else {
332 /* Clearing the period should stop heartbeat subscription
333 * without clearing the parameters, so we can still read them.
334 */
335 sub.period = 0U;
336 }
337
338 /* Start the timer, which notifies immediately if the new
339 * configuration disables the subscription.
340 */
341 k_work_reschedule(&sub_timer, K_SECONDS(sub.period));
342
343 return STATUS_SUCCESS;
344 }
345
bt_mesh_hb_sub_reset_count(void)346 void bt_mesh_hb_sub_reset_count(void)
347 {
348 sub.count = 0;
349 }
350
bt_mesh_hb_sub_get(struct bt_mesh_hb_sub * get)351 void bt_mesh_hb_sub_get(struct bt_mesh_hb_sub *get)
352 {
353 *get = sub;
354 get->remaining = sub_remaining();
355 }
356
hb_unsolicited_pub_end_cb(int err,void * cb_data)357 static void hb_unsolicited_pub_end_cb(int err, void *cb_data)
358 {
359 if (!err) {
360 notify_pub_sent();
361 }
362 }
363
bt_mesh_hb_feature_changed(uint16_t features)364 void bt_mesh_hb_feature_changed(uint16_t features)
365 {
366 static const struct bt_mesh_send_cb pub_cb = {
367 .end = hb_unsolicited_pub_end_cb,
368 };
369
370 if (pub.dst == BT_MESH_ADDR_UNASSIGNED) {
371 return;
372 }
373
374 if (!(pub.feat & features)) {
375 return;
376 }
377
378 heartbeat_send(&pub_cb, NULL);
379 }
380
bt_mesh_hb_init(void)381 void bt_mesh_hb_init(void)
382 {
383 pub.net_idx = BT_MESH_KEY_UNUSED;
384 k_work_init_delayable(&pub_timer, hb_publish);
385 k_work_init_delayable(&sub_timer, sub_end);
386 }
387
bt_mesh_hb_start(void)388 void bt_mesh_hb_start(void)
389 {
390 if (pub.count && pub.period) {
391 LOG_DBG("Starting heartbeat publication");
392 k_work_reschedule(&pub_timer, K_NO_WAIT);
393 }
394 }
395
bt_mesh_hb_suspend(void)396 void bt_mesh_hb_suspend(void)
397 {
398 /* Best-effort suspend. This cannot guarantee that an
399 * in-progress publish will not complete.
400 */
401 (void)k_work_cancel_delayable(&pub_timer);
402 }
403
bt_mesh_hb_resume(void)404 void bt_mesh_hb_resume(void)
405 {
406 if (pub.period && pub.count) {
407 LOG_DBG("Starting heartbeat publication");
408 k_work_reschedule(&pub_timer, K_NO_WAIT);
409 }
410 }
411
hb_pub_set(const char * name,size_t len_rd,settings_read_cb read_cb,void * cb_arg)412 static int hb_pub_set(const char *name, size_t len_rd,
413 settings_read_cb read_cb, void *cb_arg)
414 {
415 struct bt_mesh_hb_pub hb_pub;
416 struct hb_pub_val hb_val;
417 int err;
418
419 err = bt_mesh_settings_set(read_cb, cb_arg, &hb_val, sizeof(hb_val));
420 if (err) {
421 LOG_ERR("Failed to set \'hb_val\'");
422 return err;
423 }
424
425 hb_pub.dst = hb_val.dst;
426 hb_pub.period = bt_mesh_hb_pwr2(hb_val.period);
427 hb_pub.ttl = hb_val.ttl;
428 hb_pub.feat = hb_val.feat;
429 hb_pub.net_idx = hb_val.net_idx;
430
431 if (hb_val.indefinite) {
432 hb_pub.count = 0xffff;
433 } else {
434 hb_pub.count = 0U;
435 }
436
437 (void)bt_mesh_hb_pub_set(&hb_pub);
438
439 LOG_DBG("Restored heartbeat publication");
440
441 return 0;
442 }
443
444 BT_MESH_SETTINGS_DEFINE(pub, "HBPub", hb_pub_set);
445
bt_mesh_hb_pub_pending_store(void)446 void bt_mesh_hb_pub_pending_store(void)
447 {
448 struct bt_mesh_hb_pub hb_pub;
449 struct hb_pub_val val;
450 int err;
451
452 bt_mesh_hb_pub_get(&hb_pub);
453 if (hb_pub.dst == BT_MESH_ADDR_UNASSIGNED) {
454 err = settings_delete("bt/mesh/HBPub");
455 } else {
456 val.indefinite = (hb_pub.count == 0xffff);
457 val.dst = hb_pub.dst;
458 val.period = bt_mesh_hb_log(hb_pub.period);
459 val.ttl = hb_pub.ttl;
460 val.feat = hb_pub.feat;
461 val.net_idx = hb_pub.net_idx;
462
463 err = settings_save_one("bt/mesh/HBPub", &val, sizeof(val));
464 }
465
466 if (err) {
467 LOG_ERR("Failed to store Heartbeat Publication");
468 } else {
469 LOG_DBG("Stored Heartbeat Publication");
470 }
471 }
472