1 /*
2 * Copyright (c) 2024 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /* Implementation for states of Subnet Bridge feature in Bluetooth Mesh Protocol v1.1
8 * specification
9 */
10 #include <errno.h>
11 #include <zephyr/bluetooth/mesh.h>
12
13 #include "mesh.h"
14 #include "net.h"
15 #include "settings.h"
16 #include "brg_cfg.h"
17 #include "foundation.h"
18
19 #define LOG_LEVEL CONFIG_BT_MESH_BRG_LOG_LEVEL
20 #include <zephyr/logging/log.h>
21 LOG_MODULE_REGISTER(bt_mesh_brg_cfg);
22
23 /* Bridging table state and counter */
24 static struct bt_mesh_brg_cfg_row brg_tbl[CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX];
25 static uint32_t bt_mesh_brg_cfg_row_cnt;
26 /* Bridging enabled state */
27 static bool brg_enabled;
28
29 enum {
30 STATE_UPDATED,
31 TABLE_UPDATED,
32 BRG_CFG_FLAGS_COUNT,
33 };
34 static ATOMIC_DEFINE(brg_cfg_flags, BRG_CFG_FLAGS_COUNT);
35
36 /* Compact the bridge table for all removed entries, input is first removed entry */
brg_tbl_compact(int j)37 static void brg_tbl_compact(int j)
38 {
39 for (int k = j; k < bt_mesh_brg_cfg_row_cnt; k++) {
40 if (brg_tbl[k].direction != 0) {
41 brg_tbl[j] = brg_tbl[k];
42 j++;
43 }
44 }
45
46 memset(&brg_tbl[j], 0, sizeof(brg_tbl[j]) * (bt_mesh_brg_cfg_row_cnt - j));
47 bt_mesh_brg_cfg_row_cnt = j;
48 }
49
50 /* Set function for initializing bridging enable state from value stored in settings. */
brg_en_set(const char * name,size_t len_rd,settings_read_cb read_cb,void * cb_arg)51 static int brg_en_set(const char *name, size_t len_rd, settings_read_cb read_cb, void *cb_arg)
52 {
53 int err;
54
55 if (len_rd == 0) {
56 brg_enabled = 0;
57 LOG_DBG("Cleared bridge enable state");
58 return 0;
59 }
60
61 err = bt_mesh_settings_set(read_cb, cb_arg, &brg_enabled, sizeof(brg_enabled));
62 if (err) {
63 LOG_ERR("Failed to set bridge enable state");
64 return err;
65 }
66
67 LOG_DBG("Restored bridge enable state");
68
69 return 0;
70 }
71
72 /* Define a setting for storing enable state */
73 BT_MESH_SETTINGS_DEFINE(brg_en, "brg_en", brg_en_set);
74
75 /* Set function for initializing bridging table rows from values stored in settings. */
brg_tbl_set(const char * name,size_t len_rd,settings_read_cb read_cb,void * cb_arg)76 static int brg_tbl_set(const char *name, size_t len_rd, settings_read_cb read_cb, void *cb_arg)
77 {
78 ssize_t len;
79
80 if (len_rd == 0) {
81 memset(brg_tbl, 0, sizeof(brg_tbl));
82 bt_mesh_brg_cfg_row_cnt = 0;
83 LOG_DBG("Cleared bridging table entries");
84 return 0;
85 }
86
87 if (len_rd % sizeof(brg_tbl[0])) {
88 LOG_ERR("Invalid data size");
89 return -EINVAL;
90 }
91
92 if (len_rd > sizeof(brg_tbl)) {
93 LOG_ERR("Too many entries to fit in bridging table");
94 return -ENOMEM;
95 }
96
97 len = read_cb(cb_arg, brg_tbl, sizeof(brg_tbl));
98 if (len < 0 || len % sizeof(brg_tbl[0])) {
99 LOG_ERR("Failed to read bridging table entries (err %zd)", len);
100 return len < 0 ? len : -EINVAL;
101 }
102
103 bt_mesh_brg_cfg_row_cnt = len / sizeof(brg_tbl[0]);
104 LOG_DBG("Restored %d entries in bridging table", bt_mesh_brg_cfg_row_cnt);
105
106 return 0;
107 }
108
109 /* Define a setting for storing briging table rows */
110 BT_MESH_SETTINGS_DEFINE(brg_tbl, "brg_tbl", brg_tbl_set);
111
bt_mesh_brg_cfg_enable_get(void)112 bool bt_mesh_brg_cfg_enable_get(void)
113 {
114 return brg_enabled;
115 }
116
bt_mesh_brg_cfg_enable_set(bool enable)117 int bt_mesh_brg_cfg_enable_set(bool enable)
118 {
119 if (brg_enabled == enable) {
120 return 0;
121 }
122
123 brg_enabled = enable;
124
125 if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
126 atomic_set_bit(brg_cfg_flags, STATE_UPDATED);
127 bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING);
128 }
129
130 return 0;
131 }
132
bt_mesh_brg_cfg_pending_store(void)133 void bt_mesh_brg_cfg_pending_store(void)
134 {
135 char *path_en = "bt/mesh/brg_en";
136 char *path_tbl = "bt/mesh/brg_tbl";
137 int err;
138
139 if (atomic_test_and_clear_bit(brg_cfg_flags, STATE_UPDATED)) {
140 if (brg_enabled) {
141 err = settings_save_one(path_en, &brg_enabled, sizeof(brg_enabled));
142 } else {
143 err = settings_delete(path_en);
144 }
145
146 if (err) {
147 LOG_ERR("Failed to store %s value", path_en);
148 }
149 }
150
151 if (atomic_test_and_clear_bit(brg_cfg_flags, TABLE_UPDATED)) {
152 if (bt_mesh_brg_cfg_row_cnt) {
153 err = settings_save_one(path_tbl, &brg_tbl,
154 bt_mesh_brg_cfg_row_cnt * sizeof(brg_tbl[0]));
155 } else {
156 err = settings_delete(path_tbl);
157 }
158
159 if (err) {
160 LOG_ERR("Failed to store %s value", path_tbl);
161 }
162 }
163 }
164
165 /* Remove the entry from the bridging table that corresponds with the NetKey Index of the removed
166 * subnet.
167 */
brg_tbl_netkey_removed_evt(struct bt_mesh_subnet * sub,enum bt_mesh_key_evt evt)168 static void brg_tbl_netkey_removed_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt)
169 {
170 int first_removed = -1;
171
172 if (evt != BT_MESH_KEY_DELETED) {
173 return;
174 }
175
176 for (int i = 0; i < bt_mesh_brg_cfg_row_cnt; i++) {
177 if (brg_tbl[i].net_idx1 == sub->net_idx || brg_tbl[i].net_idx2 == sub->net_idx) {
178 /* Setting direction to 0, entry will be cleared in brg_tbl_compact. */
179 brg_tbl[i].direction = 0;
180 if (first_removed == -1) {
181 first_removed = i;
182 }
183 }
184 }
185
186 if (first_removed != -1) {
187 /* Compact when all rows have been deleted. */
188 brg_tbl_compact(first_removed);
189
190 if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
191 atomic_set_bit(brg_cfg_flags, TABLE_UPDATED);
192 bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING);
193 }
194 }
195 }
196
197 /* Add event hook for key deletion event */
198 BT_MESH_SUBNET_CB_DEFINE(sbr) = {
199 .evt_handler = brg_tbl_netkey_removed_evt,
200 };
201
bt_mesh_brg_cfg_tbl_reset(void)202 int bt_mesh_brg_cfg_tbl_reset(void)
203 {
204 int err = 0;
205
206 brg_enabled = false;
207 bt_mesh_brg_cfg_row_cnt = 0;
208 memset(brg_tbl, 0, sizeof(brg_tbl));
209 atomic_clear(brg_cfg_flags);
210
211 if (!IS_ENABLED(CONFIG_BT_SETTINGS)) {
212 return 0;
213 }
214
215 err = settings_delete("bt/mesh/brg_en");
216
217 if (err) {
218 return err;
219 }
220
221 err = settings_delete("bt/mesh/brg_tbl");
222 return err;
223 }
224
bt_mesh_brg_cfg_tbl_get(const struct bt_mesh_brg_cfg_row ** rows)225 int bt_mesh_brg_cfg_tbl_get(const struct bt_mesh_brg_cfg_row **rows)
226 {
227 *rows = brg_tbl;
228 return bt_mesh_brg_cfg_row_cnt;
229 }
230
netkey_check(uint16_t net_idx1,uint16_t net_idx2)231 static bool netkey_check(uint16_t net_idx1, uint16_t net_idx2)
232 {
233 return bt_mesh_subnet_get(net_idx1) && bt_mesh_subnet_get(net_idx2);
234 }
235
bt_mesh_brg_cfg_tbl_add(uint8_t direction,uint16_t net_idx1,uint16_t net_idx2,uint16_t addr1,uint16_t addr2,uint8_t * status)236 int bt_mesh_brg_cfg_tbl_add(uint8_t direction, uint16_t net_idx1, uint16_t net_idx2, uint16_t addr1,
237 uint16_t addr2, uint8_t *status)
238 {
239 /* Sanity checks */
240 if (!BT_MESH_ADDR_IS_UNICAST(addr1) || net_idx1 == net_idx2 || addr1 == addr2 ||
241 net_idx1 > BT_MESH_BRG_CFG_KEY_INDEX_MAX || net_idx2 > BT_MESH_BRG_CFG_KEY_INDEX_MAX) {
242 return -EINVAL;
243 }
244
245 if (direction != BT_MESH_BRG_CFG_DIR_ONEWAY && direction != BT_MESH_BRG_CFG_DIR_TWOWAY) {
246 return -EINVAL;
247 }
248
249 if ((direction == BT_MESH_BRG_CFG_DIR_ONEWAY &&
250 (addr2 == BT_MESH_ADDR_UNASSIGNED || addr2 == BT_MESH_ADDR_ALL_NODES)) ||
251 (direction == BT_MESH_BRG_CFG_DIR_TWOWAY && !BT_MESH_ADDR_IS_UNICAST(addr2))) {
252 return -EINVAL;
253 }
254
255 if (!netkey_check(net_idx1, net_idx2)) {
256 *status = STATUS_INVALID_NETKEY;
257 return 0;
258 }
259
260 /* Check if entry already exists, if yes, then, update the direction field and it is a
261 * success.
262 * "If a Bridging Table state entry corresponding to the received message exists, the
263 * element shall set the Directions field in the entry to the value of the Directions field
264 * in the received message."
265 */
266 for (int i = 0; i < bt_mesh_brg_cfg_row_cnt; i++) {
267 if (brg_tbl[i].net_idx1 == net_idx1 && brg_tbl[i].net_idx2 == net_idx2 &&
268 brg_tbl[i].addr1 == addr1 && brg_tbl[i].addr2 == addr2) {
269 brg_tbl[i].direction = direction;
270 goto store;
271 }
272 }
273
274 /* Empty element, is the current table row counter */
275 if (bt_mesh_brg_cfg_row_cnt >= CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX) {
276 *status = STATUS_INSUFF_RESOURCES;
277 return 0;
278 }
279
280 /* Update the row */
281 brg_tbl[bt_mesh_brg_cfg_row_cnt].direction = direction;
282 brg_tbl[bt_mesh_brg_cfg_row_cnt].net_idx1 = net_idx1;
283 brg_tbl[bt_mesh_brg_cfg_row_cnt].net_idx2 = net_idx2;
284 brg_tbl[bt_mesh_brg_cfg_row_cnt].addr1 = addr1;
285 brg_tbl[bt_mesh_brg_cfg_row_cnt].addr2 = addr2;
286 bt_mesh_brg_cfg_row_cnt++;
287
288 store:
289 if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
290 atomic_set_bit(brg_cfg_flags, TABLE_UPDATED);
291 bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING);
292 }
293
294 *status = STATUS_SUCCESS;
295 return 0;
296 }
297
bt_mesh_brg_cfg_tbl_foreach_subnet(uint16_t src,uint16_t dst,uint16_t net_idx,bt_mesh_brg_cfg_cb_t cb,void * user_data)298 void bt_mesh_brg_cfg_tbl_foreach_subnet(uint16_t src, uint16_t dst, uint16_t net_idx,
299 bt_mesh_brg_cfg_cb_t cb, void *user_data)
300 {
301 for (int i = 0; i < bt_mesh_brg_cfg_row_cnt; i++) {
302 if ((brg_tbl[i].direction == BT_MESH_BRG_CFG_DIR_ONEWAY ||
303 brg_tbl[i].direction == BT_MESH_BRG_CFG_DIR_TWOWAY) &&
304 brg_tbl[i].net_idx1 == net_idx && brg_tbl[i].addr1 == src &&
305 brg_tbl[i].addr2 == dst) {
306 cb(brg_tbl[i].net_idx2, user_data);
307 } else if ((brg_tbl[i].direction == BT_MESH_BRG_CFG_DIR_TWOWAY &&
308 brg_tbl[i].net_idx2 == net_idx && brg_tbl[i].addr2 == src &&
309 brg_tbl[i].addr1 == dst)) {
310 cb(brg_tbl[i].net_idx1, user_data);
311 }
312 }
313 }
314
bt_mesh_brg_cfg_tbl_remove(uint16_t net_idx1,uint16_t net_idx2,uint16_t addr1,uint16_t addr2,uint8_t * status)315 int bt_mesh_brg_cfg_tbl_remove(uint16_t net_idx1, uint16_t net_idx2, uint16_t addr1, uint16_t addr2,
316 uint8_t *status)
317 {
318 int first_removed = -1;
319
320 /* Sanity checks */
321 if ((!BT_MESH_ADDR_IS_UNICAST(addr1) && addr1 != BT_MESH_ADDR_UNASSIGNED) ||
322 (BT_MESH_ADDR_IS_UNICAST(addr1) && addr1 == addr2) || addr2 == BT_MESH_ADDR_ALL_NODES) {
323 return -EINVAL;
324 }
325
326 if (net_idx1 == net_idx2 || net_idx1 > BT_MESH_BRG_CFG_KEY_INDEX_MAX ||
327 net_idx2 > BT_MESH_BRG_CFG_KEY_INDEX_MAX) {
328 return -EINVAL;
329 }
330
331 if (!netkey_check(net_idx1, net_idx2)) {
332 *status = STATUS_INVALID_NETKEY;
333 return 0;
334 }
335
336 /* Iterate over items and set matching row to 0, if nothing exist, or nothing matches, then
337 * it is success (similar to add)
338 */
339 if (bt_mesh_brg_cfg_row_cnt == 0) {
340 *status = STATUS_SUCCESS;
341 return 0;
342 }
343
344 for (int i = 0; i < bt_mesh_brg_cfg_row_cnt; i++) {
345 /* Match according to remove behavior in Section 4.4.9.2.2 of MshPRT_v1.1 */
346 if (!(brg_tbl[i].net_idx1 == net_idx1 && brg_tbl[i].net_idx2 == net_idx2)) {
347 continue;
348 }
349
350 if ((brg_tbl[i].addr1 == addr1 && brg_tbl[i].addr2 == addr2) ||
351 (addr2 == BT_MESH_ADDR_UNASSIGNED && brg_tbl[i].addr1 == addr1) ||
352 (addr1 == BT_MESH_ADDR_UNASSIGNED && brg_tbl[i].addr2 == addr2)) {
353 /* Setting direction to 0, entry will be cleared in brg_tbl_compact. */
354 brg_tbl[i].direction = 0;
355 if (first_removed == -1) {
356 first_removed = i;
357 }
358 }
359 }
360
361 if (first_removed != -1) {
362 /* Compact when all rows have been deleted. */
363 brg_tbl_compact(first_removed);
364
365 if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
366 atomic_set_bit(brg_cfg_flags, TABLE_UPDATED);
367 bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING);
368 }
369 }
370
371 *status = STATUS_SUCCESS;
372 return 0;
373 }
374