1 /**
2  * Copyright (c) 2024 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdlib.h>
8 
9 #include <zephyr/kernel.h>
10 #include <zephyr/sys/util.h>
11 #include <zephyr/mgmt/mcumgr/smp/smp.h>
12 #include <zephyr/mgmt/mcumgr/mgmt/mgmt.h>
13 #include <zephyr/mgmt/mcumgr/mgmt/handlers.h>
14 #include <zephyr/mgmt/mcumgr/grp/enum_mgmt/enum_mgmt.h>
15 #include <zephyr/logging/log.h>
16 
17 #include <mgmt/mcumgr/util/zcbor_bulk.h>
18 
19 #include <zcbor_common.h>
20 #include <zcbor_encode.h>
21 #include <zcbor_decode.h>
22 
23 #if defined(CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS)
24 #include <zephyr/mgmt/mcumgr/mgmt/callbacks.h>
25 #include <mgmt/mcumgr/transport/smp_internal.h>
26 #endif
27 
28 LOG_MODULE_REGISTER(mcumgr_enum_grp, CONFIG_MCUMGR_GRP_ENUM_LOG_LEVEL);
29 
30 #define MAX_MCUMGR_GROUPS 65535
31 
32 struct enum_mgmt_single_arg {
33 	uint32_t index;
34 	uint32_t current_index;
35 	uint32_t group;
36 	bool found;
37 	bool last;
38 };
39 
40 struct enum_mgmt_list_arg {
41 	bool *ok;
42 	zcbor_state_t *zse;
43 };
44 
45 struct enum_mgmt_details_arg {
46 	bool *ok;
47 	zcbor_state_t *zse;
48 	uint16_t *allowed_group_ids;
49 	uint16_t allowed_group_id_size;
50 #if defined(CONFIG_MCUMGR_GRP_ENUM_DETAILS_HOOK)
51 	enum mgmt_cb_return status;
52 	int32_t err_rc;
53 	uint16_t err_group;
54 #endif
55 };
56 
enum_mgmt_cb_count(const struct mgmt_group * group,void * user_data)57 static bool enum_mgmt_cb_count(const struct mgmt_group *group, void *user_data)
58 {
59 	uint16_t *count = (uint16_t *)user_data;
60 
61 	++*count;
62 
63 	return true;
64 }
65 
enum_mgmt_cb_single(const struct mgmt_group * group,void * user_data)66 static bool enum_mgmt_cb_single(const struct mgmt_group *group, void *user_data)
67 {
68 	struct enum_mgmt_single_arg *data = (struct enum_mgmt_single_arg *)user_data;
69 
70 	if (data->index == data->current_index) {
71 		data->found = true;
72 		data->group = group->mg_group_id;
73 		++data->current_index;
74 		data->last = true;
75 	} else {
76 		if (data->found == true && data->current_index == (data->index + 1)) {
77 			data->last = false;
78 			return false;
79 		}
80 
81 		++data->current_index;
82 	}
83 
84 	return true;
85 }
86 
enum_mgmt_cb_list(const struct mgmt_group * group,void * user_data)87 static bool enum_mgmt_cb_list(const struct mgmt_group *group, void *user_data)
88 {
89 	struct enum_mgmt_list_arg *data = (struct enum_mgmt_list_arg *)user_data;
90 
91 	*data->ok = zcbor_uint32_put(data->zse, group->mg_group_id);
92 
93 	return *data->ok;
94 }
95 
96 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS
enum_mgmt_cb_details(const struct mgmt_group * group,void * user_data)97 static bool enum_mgmt_cb_details(const struct mgmt_group *group, void *user_data)
98 {
99 	struct enum_mgmt_details_arg *data = (struct enum_mgmt_details_arg *)user_data;
100 
101 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS_NAME
102 	const char *group_name = group->mg_group_name;
103 	int group_name_size = 0;
104 #endif
105 
106 #if defined(CONFIG_MCUMGR_GRP_ENUM_DETAILS_HOOK)
107 	struct enum_mgmt_detail_output group_detail_data = {
108 		.group = group,
109 		.zse = data->zse,
110 	};
111 #endif
112 
113 	if (data->allowed_group_ids != NULL && data->allowed_group_id_size > 0) {
114 		/* Check if this entry is in the allow list */
115 		uint16_t i = 0;
116 
117 		while (i < data->allowed_group_id_size) {
118 			if (data->allowed_group_ids[i] == group->mg_group_id) {
119 				break;
120 			}
121 
122 			++i;
123 		}
124 
125 		if (i == data->allowed_group_id_size) {
126 			*data->ok = true;
127 			goto finished;
128 		}
129 	}
130 
131 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS_NAME
132 	if (group_name != NULL) {
133 		group_name_size = strlen(group_name);
134 	}
135 #endif
136 
137 	*data->ok = zcbor_map_start_encode(data->zse, CONFIG_MCUMGR_GRP_ENUM_DETAILS_STATES) &&
138 		    zcbor_tstr_put_lit(data->zse, "group") &&
139 		    zcbor_uint32_put(data->zse, group->mg_group_id) &&
140 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS_NAME
141 		    zcbor_tstr_put_lit(data->zse, "name") &&
142 		    zcbor_tstr_encode_ptr(data->zse, group_name, group_name_size) &&
143 #endif
144 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS_HANDLERS
145 		    zcbor_tstr_put_lit(data->zse, "handlers") &&
146 		    zcbor_uint32_put(data->zse, group->mg_handlers_count) &&
147 #endif
148 		    1;
149 
150 #if defined(CONFIG_MCUMGR_GRP_ENUM_DETAILS_HOOK)
151 	/* Send notification to application to optionally append more fields */
152 	data->status = mgmt_callback_notify(MGMT_EVT_OP_ENUM_MGMT_DETAILS, &group_detail_data,
153 				      sizeof(group_detail_data), &data->err_rc, &data->err_group);
154 
155 	if (data->status != MGMT_CB_OK) {
156 		*data->ok = false;
157 
158 		return false;
159 	}
160 #endif
161 
162 	if (*data->ok) {
163 		*data->ok = zcbor_map_end_encode(data->zse, CONFIG_MCUMGR_GRP_ENUM_DETAILS_STATES);
164 	}
165 
166 finished:
167 	return *data->ok;
168 }
169 #endif
170 
enum_mgmt_count(struct smp_streamer * ctxt)171 static int enum_mgmt_count(struct smp_streamer *ctxt)
172 {
173 	zcbor_state_t *zse = ctxt->writer->zs;
174 	uint32_t count = 0;
175 	bool ok;
176 
177 	mgmt_groups_foreach(enum_mgmt_cb_count, (void *)&count);
178 
179 	ok = zcbor_tstr_put_lit(zse, "count") &&
180 	     zcbor_uint32_put(zse, count);
181 
182 	return ok ? MGMT_ERR_EOK : MGMT_ERR_ECORRUPT;
183 }
184 
enum_mgmt_list(struct smp_streamer * ctxt)185 static int enum_mgmt_list(struct smp_streamer *ctxt)
186 {
187 	zcbor_state_t *zse = ctxt->writer->zs;
188 	uint32_t count = 0;
189 	bool ok;
190 	struct enum_mgmt_list_arg arguments = {
191 		.ok = &ok,
192 		.zse = zse
193 	};
194 
195 	mgmt_groups_foreach(enum_mgmt_cb_count, (void *)&count);
196 
197 	ok = zcbor_tstr_put_lit(zse, "groups") &&
198 	     zcbor_list_start_encode(zse, count);
199 
200 	if (!ok) {
201 		goto failure;
202 	}
203 
204 	mgmt_groups_foreach(enum_mgmt_cb_list, (void *)&arguments);
205 
206 	if (!ok) {
207 		goto failure;
208 	}
209 
210 	ok = zcbor_list_end_encode(zse, count);
211 
212 failure:
213 	return ok ? MGMT_ERR_EOK : MGMT_ERR_ECORRUPT;
214 }
215 
enum_mgmt_single(struct smp_streamer * ctxt)216 static int enum_mgmt_single(struct smp_streamer *ctxt)
217 {
218 	zcbor_state_t *zse = ctxt->writer->zs;
219 	zcbor_state_t *zsd = ctxt->reader->zs;
220 	size_t decoded = 0;
221 	bool ok;
222 	struct enum_mgmt_single_arg arguments = {
223 		.index = 0,
224 		.current_index = 0,
225 		.group = 0,
226 		.found = false,
227 		.last = false
228 	};
229 
230 	struct zcbor_map_decode_key_val enum_single_decode[] = {
231 		ZCBOR_MAP_DECODE_KEY_DECODER("index", zcbor_uint32_decode, &arguments.index),
232 	};
233 
234 	ok = zcbor_map_decode_bulk(zsd, enum_single_decode, ARRAY_SIZE(enum_single_decode),
235 				   &decoded) == 0;
236 
237 	if (!ok || arguments.index > MAX_MCUMGR_GROUPS) {
238 		return MGMT_ERR_EINVAL;
239 	}
240 
241 	mgmt_groups_foreach(enum_mgmt_cb_single, &arguments);
242 
243 	if (!arguments.found) {
244 		/* Out of range index supplied */
245 		ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_ENUM, ENUM_MGMT_ERR_INDEX_TOO_LARGE);
246 	} else {
247 		ok = zcbor_tstr_put_lit(zse, "group") &&
248 		     zcbor_uint32_put(zse, arguments.group);
249 
250 		if (arguments.last) {
251 			ok &= zcbor_tstr_put_lit(zse, "end") &&
252 			      zcbor_bool_put(zse, true);
253 		}
254 	}
255 
256 	return ok ? MGMT_ERR_EOK : MGMT_ERR_ECORRUPT;
257 }
258 
259 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS
enum_mgmt_cb_count_entries(zcbor_state_t * state,void * user_data)260 static bool enum_mgmt_cb_count_entries(zcbor_state_t *state, void *user_data)
261 {
262 	size_t *entries = (size_t *)user_data;
263 
264 	if (!zcbor_list_start_decode(state)) {
265 		return true;
266 	}
267 
268 	while (!zcbor_array_at_end(state)) {
269 		++*entries;
270 		zcbor_any_skip(state, NULL);
271 	}
272 
273 	(void)zcbor_list_end_decode(state);
274 
275 	return true;
276 }
277 
enum_mgmt_cb_list_entries(zcbor_state_t * state,void * user_data)278 static bool enum_mgmt_cb_list_entries(zcbor_state_t *state, void *user_data)
279 {
280 	uint32_t temp = 0;
281 	uint16_t i = 0;
282 	uint16_t *list = (uint16_t *)user_data;
283 
284 	if (!zcbor_list_start_decode(state)) {
285 		return true;
286 	}
287 
288 	while (!zcbor_array_at_end(state)) {
289 		if (!zcbor_uint32_decode(state, &temp)) {
290 			return false;
291 		}
292 
293 		list[i] = (uint16_t)temp;
294 		++i;
295 	}
296 
297 	(void)zcbor_list_end_decode(state);
298 
299 	return true;
300 }
301 
enum_mgmt_details(struct smp_streamer * ctxt)302 static int enum_mgmt_details(struct smp_streamer *ctxt)
303 {
304 	zcbor_state_t *zse = ctxt->writer->zs;
305 	zcbor_state_t *zsd = ctxt->reader->zs;
306 	uint32_t count = 0;
307 	bool ok;
308 	size_t backup_element_count_reader = zsd->elem_count;
309 	size_t entries = 0;
310 	size_t decoded = 0;
311 	struct enum_mgmt_details_arg arguments = {
312 		.ok = &ok,
313 		.zse = zse,
314 		.allowed_group_ids = NULL,
315 		.allowed_group_id_size = 0,
316 #if defined(CONFIG_MCUMGR_GRP_ENUM_DETAILS_HOOK)
317 		.status = MGMT_CB_OK,
318 #endif
319 	};
320 
321 	if (!zcbor_new_backup(zsd, backup_element_count_reader)) {
322 		LOG_ERR("Failed to create zcbor backup");
323 		return MGMT_ERR_ENOMEM;
324 	}
325 
326 	struct zcbor_map_decode_key_val enum_group_decode[] = {
327 		ZCBOR_MAP_DECODE_KEY_DECODER("groups", enum_mgmt_cb_count_entries, &entries),
328 	};
329 
330 	ok = zcbor_map_decode_bulk(zsd, enum_group_decode, ARRAY_SIZE(enum_group_decode),
331 				   &decoded) == 0;
332 
333 	if (!ok) {
334 		return MGMT_ERR_EINVAL;
335 	}
336 
337 	if (!zcbor_process_backup(zsd, (ZCBOR_FLAG_RESTORE | ZCBOR_FLAG_CONSUME),
338 				  backup_element_count_reader)) {
339 		LOG_ERR("Failed to restore zcbor reader backup");
340 		return MGMT_ERR_ENOMEM;
341 	}
342 
343 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS_BUFFER_TYPE_STACK
344 	if (entries > CONFIG_MCUMGR_GRP_ENUM_DETAILS_BUFFER_TYPE_STACK_ENTRIES) {
345 		ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_ENUM,
346 				     ENUM_MGMT_ERR_TOO_MANY_GROUP_ENTRIES);
347 		goto failure;
348 	}
349 #endif
350 
351 	if (entries > 0) {
352 		/* Return details on selected groups only */
353 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS_BUFFER_TYPE_HEAP
354 		uint16_t *entry_list = malloc(sizeof(uint16_t) * entries);
355 #else
356 		uint16_t entry_list[CONFIG_MCUMGR_GRP_ENUM_DETAILS_BUFFER_TYPE_STACK_ENTRIES];
357 #endif
358 
359 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS_BUFFER_TYPE_HEAP
360 		if (entry_list == NULL) {
361 			ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_ENUM,
362 					     ENUM_MGMT_ERR_INSUFFICIENT_HEAP_FOR_ENTRIES);
363 			goto failure;
364 		}
365 #endif
366 
367 		enum_group_decode[0].decoder = enum_mgmt_cb_list_entries;
368 		enum_group_decode[0].value_ptr = entry_list;
369 		enum_group_decode[0].found = false;
370 		arguments.allowed_group_ids = entry_list;
371 		arguments.allowed_group_id_size = (uint16_t)entries;
372 
373 		ok = zcbor_map_decode_bulk(zsd, enum_group_decode, ARRAY_SIZE(enum_group_decode),
374 					   &decoded) == 0;
375 
376 		if (!ok) {
377 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS_BUFFER_TYPE_HEAP
378 			free(entry_list);
379 #endif
380 
381 			return MGMT_ERR_EINVAL;
382 		}
383 
384 		ok = zcbor_tstr_put_lit(zse, "groups") &&
385 		     zcbor_list_start_encode(zse, count);
386 
387 		if (!ok) {
388 			goto cleanup;
389 		}
390 
391 		mgmt_groups_foreach(enum_mgmt_cb_details, (void *)&arguments);
392 
393 cleanup:
394 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS_BUFFER_TYPE_HEAP
395 		free(entry_list);
396 #endif
397 
398 		if (!ok) {
399 			goto failure;
400 		}
401 	} else {
402 		/* Return details on all groups */
403 		mgmt_groups_foreach(enum_mgmt_cb_count, (void *)&count);
404 
405 		ok = zcbor_tstr_put_lit(zse, "groups") &&
406 		     zcbor_list_start_encode(zse, count);
407 
408 		if (!ok) {
409 			goto failure;
410 		}
411 
412 		mgmt_groups_foreach(enum_mgmt_cb_details, (void *)&arguments);
413 
414 		if (!ok) {
415 			goto failure;
416 		}
417 	}
418 
419 #if defined(CONFIG_MCUMGR_GRP_ENUM_DETAILS_HOOK)
420 	if (arguments.status == MGMT_CB_ERROR_RC) {
421 		return arguments.err_rc;
422 	} else if (arguments.status == MGMT_CB_ERROR_ERR) {
423 		/* Because there is already data in the buffer, it must be cleared first */
424 		net_buf_reset(ctxt->writer->nb);
425 		ctxt->writer->nb->len = sizeof(struct smp_hdr);
426 		zcbor_new_encode_state(zse, ARRAY_SIZE(ctxt->writer->zs),
427 				       ctxt->writer->nb->data + sizeof(struct smp_hdr),
428 				       net_buf_tailroom(ctxt->writer->nb), 0);
429 		ok = zcbor_map_start_encode(zse, CONFIG_MCUMGR_SMP_CBOR_MAX_MAIN_MAP_ENTRIES) &&
430 		     smp_add_cmd_err(zse, arguments.err_group, (uint16_t)arguments.err_rc);
431 
432 		goto failure;
433 	}
434 #endif
435 
436 	ok = zcbor_list_end_encode(zse, count);
437 
438 failure:
439 	return ok ? MGMT_ERR_EOK : MGMT_ERR_ECORRUPT;
440 }
441 #endif
442 
443 #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
444 /*
445  * @brief       Translate enum mgmt group error code into MCUmgr error code
446  *
447  * @param ret   #enum_mgmt_err_code_t error code
448  *
449  * @return      #mcumgr_err_t error code
450  */
enum_mgmt_translate_error_code(uint16_t err)451 static int enum_mgmt_translate_error_code(uint16_t err)
452 {
453 	int rc;
454 
455 	switch (err) {
456 	case ENUM_MGMT_ERR_TOO_MANY_GROUP_ENTRIES:
457 		rc = MGMT_ERR_EINVAL;
458 		break;
459 
460 	case ENUM_MGMT_ERR_UNKNOWN:
461 	default:
462 		rc = MGMT_ERR_EUNKNOWN;
463 	}
464 
465 	return rc;
466 }
467 #endif
468 
469 static const struct mgmt_handler enum_mgmt_group_handlers[] = {
470 	[ENUM_MGMT_ID_COUNT] = { enum_mgmt_count, NULL },
471 	[ENUM_MGMT_ID_LIST] = { enum_mgmt_list, NULL },
472 	[ENUM_MGMT_ID_SINGLE] = { enum_mgmt_single, NULL },
473 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS
474 	[ENUM_MGMT_ID_DETAILS] = { enum_mgmt_details, NULL },
475 #endif
476 };
477 
478 static struct mgmt_group enum_mgmt_group = {
479 	.mg_handlers = enum_mgmt_group_handlers,
480 	.mg_handlers_count = ARRAY_SIZE(enum_mgmt_group_handlers),
481 	.mg_group_id = MGMT_GROUP_ID_ENUM,
482 #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
483 	.mg_translate_error = enum_mgmt_translate_error_code,
484 #endif
485 #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS_NAME
486 	.mg_group_name = "enum mgmt",
487 #endif
488 };
489 
enum_mgmt_register_group(void)490 static void enum_mgmt_register_group(void)
491 {
492 	mgmt_register_group(&enum_mgmt_group);
493 }
494 
495 MCUMGR_HANDLER_DEFINE(enum_mgmt, enum_mgmt_register_group);
496