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