1 /*
2  * Copyright (c) 2018-2021 mcumgr authors
3  * Copyright (c) 2022-2023 Nordic Semiconductor ASA
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 /** SMP - Simple Management Protocol. */
9 
10 #include <zephyr/sys/byteorder.h>
11 #include <zephyr/net/buf.h>
12 #include <zephyr/mgmt/mcumgr/mgmt/mgmt.h>
13 #include <zephyr/mgmt/mcumgr/smp/smp.h>
14 #include <zephyr/mgmt/mcumgr/smp/smp_client.h>
15 #include <zephyr/mgmt/mcumgr/transport/smp.h>
16 #include <assert.h>
17 #include <string.h>
18 
19 #include <zcbor_common.h>
20 #include <zcbor_decode.h>
21 #include <zcbor_encode.h>
22 
23 #include <mgmt/mcumgr/transport/smp_internal.h>
24 
25 #ifdef CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS
26 #include <zephyr/mgmt/mcumgr/mgmt/callbacks.h>
27 #endif
28 
29 #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
30 /*
31  * @brief	Translate SMP version 2 error code to legacy SMP version 1 MCUmgr error code.
32  *
33  * @param group	#mcumgr_group_t group ID
34  * @param err	Group-specific error code
35  *
36  * @return	#mcumgr_err_t error code
37  */
smp_translate_error_code(uint16_t group,uint16_t err)38 static int smp_translate_error_code(uint16_t group, uint16_t err)
39 {
40 	smp_translate_error_fn translate_error_function = NULL;
41 
42 	translate_error_function = mgmt_find_error_translation_function(group);
43 
44 	if (translate_error_function == NULL) {
45 		return MGMT_ERR_EUNKNOWN;
46 	}
47 
48 	return translate_error_function(err);
49 }
50 #endif
51 
cbor_nb_reader_init(struct cbor_nb_reader * cnr,struct net_buf * nb)52 static void cbor_nb_reader_init(struct cbor_nb_reader *cnr, struct net_buf *nb)
53 {
54 	cnr->nb = nb;
55 	zcbor_new_decode_state(cnr->zs, ARRAY_SIZE(cnr->zs), nb->data,
56 			       nb->len, 1, NULL, 0);
57 }
58 
cbor_nb_writer_init(struct cbor_nb_writer * cnw,struct net_buf * nb)59 static void cbor_nb_writer_init(struct cbor_nb_writer *cnw, struct net_buf *nb)
60 {
61 	net_buf_reset(nb);
62 	cnw->nb = nb;
63 	cnw->nb->len = sizeof(struct smp_hdr);
64 	zcbor_new_encode_state(cnw->zs, ARRAY_SIZE(cnw->zs), nb->data + sizeof(struct smp_hdr),
65 			       net_buf_tailroom(nb), 0);
66 }
67 
68 /**
69  * Converts a request opcode to its corresponding response opcode.
70  */
smp_rsp_op(uint8_t req_op)71 static uint8_t smp_rsp_op(uint8_t req_op)
72 {
73 	if (req_op == MGMT_OP_READ) {
74 		return MGMT_OP_READ_RSP;
75 	} else {
76 		return MGMT_OP_WRITE_RSP;
77 	}
78 }
79 
smp_make_rsp_hdr(const struct smp_hdr * req_hdr,struct smp_hdr * rsp_hdr,size_t len)80 static void smp_make_rsp_hdr(const struct smp_hdr *req_hdr, struct smp_hdr *rsp_hdr, size_t len)
81 {
82 	*rsp_hdr = (struct smp_hdr) {
83 		.nh_len = sys_cpu_to_be16(len),
84 		.nh_flags = 0,
85 		.nh_op = smp_rsp_op(req_hdr->nh_op),
86 		.nh_group = sys_cpu_to_be16(req_hdr->nh_group),
87 		.nh_seq = req_hdr->nh_seq,
88 		.nh_id = req_hdr->nh_id,
89 		.nh_version = (req_hdr->nh_version > SMP_MCUMGR_VERSION_2 ? SMP_MCUMGR_VERSION_2 :
90 			       req_hdr->nh_version),
91 	};
92 }
93 
smp_read_hdr(const struct net_buf * nb,struct smp_hdr * dst_hdr)94 static int smp_read_hdr(const struct net_buf *nb, struct smp_hdr *dst_hdr)
95 {
96 	if (nb->len < sizeof(*dst_hdr)) {
97 		return MGMT_ERR_EINVAL;
98 	}
99 
100 	memcpy(dst_hdr, nb->data, sizeof(*dst_hdr));
101 	dst_hdr->nh_len = sys_be16_to_cpu(dst_hdr->nh_len);
102 	dst_hdr->nh_group = sys_be16_to_cpu(dst_hdr->nh_group);
103 
104 	return 0;
105 }
106 
smp_write_hdr(struct smp_streamer * streamer,const struct smp_hdr * src_hdr)107 static inline int smp_write_hdr(struct smp_streamer *streamer, const struct smp_hdr *src_hdr)
108 {
109 	memcpy(streamer->writer->nb->data, src_hdr, sizeof(*src_hdr));
110 	return 0;
111 }
112 
smp_build_err_rsp(struct smp_streamer * streamer,const struct smp_hdr * req_hdr,int status,const char * rc_rsn)113 static int smp_build_err_rsp(struct smp_streamer *streamer, const struct smp_hdr *req_hdr,
114 			     int status, const char *rc_rsn)
115 {
116 	struct smp_hdr rsp_hdr;
117 	struct cbor_nb_writer *nbw = streamer->writer;
118 	zcbor_state_t *zsp = nbw->zs;
119 	bool ok;
120 
121 	ok = zcbor_map_start_encode(zsp, 2)		&&
122 	     zcbor_tstr_put_lit(zsp, "rc")		&&
123 	     zcbor_int32_put(zsp, status);
124 
125 #ifdef CONFIG_MCUMGR_SMP_VERBOSE_ERR_RESPONSE
126 	if (ok && rc_rsn != NULL) {
127 		ok = zcbor_tstr_put_lit(zsp, "rsn")			&&
128 		     zcbor_tstr_put_term(zsp, rc_rsn, CONFIG_ZCBOR_MAX_STR_LEN);
129 	}
130 #else
131 	ARG_UNUSED(rc_rsn);
132 #endif
133 	ok &= zcbor_map_end_encode(zsp, 2);
134 
135 	if (!ok) {
136 		return MGMT_ERR_EMSGSIZE;
137 	}
138 
139 	smp_make_rsp_hdr(req_hdr, &rsp_hdr,
140 			 zsp->payload_mut - nbw->nb->data - MGMT_HDR_SIZE);
141 	nbw->nb->len = zsp->payload_mut - nbw->nb->data;
142 	smp_write_hdr(streamer, &rsp_hdr);
143 
144 	return 0;
145 }
146 
147 /**
148  * Processes a single SMP request and generates a response payload (i.e.,
149  * everything after the management header).  On success, the response payload
150  * is written to the supplied cbuf but not transmitted.  On failure, no error
151  * response gets written; the caller is expected to build an error response
152  * from the return code.
153  *
154  * @param cbuf		A cbuf containing the request and response buffer.
155  * @param req_hdr	The management header belonging to the incoming request (host-byte order).
156  *
157  * @return A MGMT_ERR_[...] error code.
158  */
smp_handle_single_payload(struct smp_streamer * cbuf,const struct smp_hdr * req_hdr)159 static int smp_handle_single_payload(struct smp_streamer *cbuf, const struct smp_hdr *req_hdr)
160 {
161 	const struct mgmt_group *group;
162 	const struct mgmt_handler *handler;
163 	mgmt_handler_fn handler_fn;
164 	int rc;
165 #if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS)
166 	enum mgmt_cb_return status;
167 	struct mgmt_evt_op_cmd_arg cmd_recv;
168 	int32_t err_rc;
169 	uint16_t err_group;
170 #endif
171 
172 	group = mgmt_find_group(req_hdr->nh_group);
173 	if (group == NULL) {
174 		return MGMT_ERR_ENOTSUP;
175 	}
176 
177 	handler = mgmt_get_handler(group, req_hdr->nh_id);
178 	if (handler == NULL) {
179 		return MGMT_ERR_ENOTSUP;
180 	}
181 
182 	switch (req_hdr->nh_op) {
183 	case MGMT_OP_READ:
184 		handler_fn = handler->mh_read;
185 		break;
186 
187 	case MGMT_OP_WRITE:
188 		handler_fn = handler->mh_write;
189 		break;
190 
191 	default:
192 		return MGMT_ERR_EINVAL;
193 	}
194 
195 	if (handler_fn) {
196 		bool ok;
197 
198 #if defined(CONFIG_MCUMGR_MGMT_CUSTOM_PAYLOAD)
199 		if (!group->custom_payload) {
200 #endif
201 			ok = zcbor_map_start_encode(cbuf->writer->zs,
202 						    CONFIG_MCUMGR_SMP_CBOR_MAX_MAIN_MAP_ENTRIES);
203 
204 			MGMT_CTXT_SET_RC_RSN(cbuf, NULL);
205 
206 			if (!ok) {
207 				return MGMT_ERR_EMSGSIZE;
208 			}
209 #if defined(CONFIG_MCUMGR_MGMT_CUSTOM_PAYLOAD)
210 		}
211 #endif
212 
213 #if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS)
214 		cmd_recv.group = req_hdr->nh_group;
215 		cmd_recv.id = req_hdr->nh_id;
216 		cmd_recv.op = req_hdr->nh_op;
217 
218 		/* Send request to application to check if handler should run or not. */
219 		status = mgmt_callback_notify(MGMT_EVT_OP_CMD_RECV, &cmd_recv, sizeof(cmd_recv),
220 					      &err_rc, &err_group);
221 
222 		/* Skip running the command if a handler reported an error and return that
223 		 * instead.
224 		 */
225 		if (status != MGMT_CB_OK) {
226 			if (status == MGMT_CB_ERROR_RC) {
227 				rc = err_rc;
228 			} else {
229 				ok = smp_add_cmd_err(cbuf->writer->zs, err_group,
230 						     (uint16_t)err_rc);
231 
232 				rc = (ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE);
233 			}
234 
235 			goto end;
236 		}
237 #endif
238 
239 		rc = handler_fn(cbuf);
240 
241 #if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS)
242 end:
243 #endif
244 #if defined(CONFIG_MCUMGR_MGMT_CUSTOM_PAYLOAD)
245 		if (!group->custom_payload) {
246 #endif
247 			/* End response payload. */
248 			if (!zcbor_map_end_encode(cbuf->writer->zs,
249 						  CONFIG_MCUMGR_SMP_CBOR_MAX_MAIN_MAP_ENTRIES) &&
250 			    rc == 0) {
251 				rc = MGMT_ERR_EMSGSIZE;
252 			}
253 #if defined(CONFIG_MCUMGR_MGMT_CUSTOM_PAYLOAD)
254 		}
255 #endif
256 	} else {
257 		rc = MGMT_ERR_ENOTSUP;
258 	}
259 
260 	return rc;
261 }
262 
263 /**
264  * Processes a single SMP request and generates a complete response (i.e.,
265  * header and payload).  On success, the response is written using the supplied
266  * streamer but not transmitted.  On failure, no error response gets written;
267  * the caller is expected to build an error response from the return code.
268  *
269  * @param streamer	The SMP streamer to use for reading the request and writing the response.
270  * @param req_hdr	The management header belonging to the incoming request (host-byte order).
271  *
272  * @return A MGMT_ERR_[...] error code.
273  */
smp_handle_single_req(struct smp_streamer * streamer,const struct smp_hdr * req_hdr,const char ** rsn)274 static int smp_handle_single_req(struct smp_streamer *streamer, const struct smp_hdr *req_hdr,
275 				 const char **rsn)
276 {
277 	struct smp_hdr rsp_hdr;
278 	struct cbor_nb_writer *nbw = streamer->writer;
279 	zcbor_state_t *zsp = nbw->zs;
280 	int rc;
281 
282 #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
283 	nbw->error_group = 0;
284 	nbw->error_ret = 0;
285 #else
286 	if (req_hdr->nh_version == SMP_MCUMGR_VERSION_1) {
287 		/* Support for the original version is excluded in this build */
288 		return MGMT_ERR_UNSUPPORTED_TOO_OLD;
289 	}
290 #endif
291 
292 	/* We do not currently support future versions of the protocol */
293 	if (req_hdr->nh_version > SMP_MCUMGR_VERSION_2) {
294 		return MGMT_ERR_UNSUPPORTED_TOO_NEW;
295 	}
296 
297 	/* Process the request and write the response payload. */
298 	rc = smp_handle_single_payload(streamer, req_hdr);
299 	if (rc != 0) {
300 		*rsn = MGMT_CTXT_RC_RSN(streamer);
301 		return rc;
302 	}
303 
304 #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
305 	/* If using the legacy protocol, translate the error code to a return code */
306 	if (nbw->error_ret != 0 && req_hdr->nh_version == 0) {
307 		rc = smp_translate_error_code(nbw->error_group, nbw->error_ret);
308 		*rsn = MGMT_CTXT_RC_RSN(streamer);
309 		return rc;
310 	}
311 #endif
312 
313 	smp_make_rsp_hdr(req_hdr, &rsp_hdr,
314 			 zsp->payload_mut - nbw->nb->data - MGMT_HDR_SIZE);
315 	nbw->nb->len = zsp->payload_mut - nbw->nb->data;
316 	smp_write_hdr(streamer, &rsp_hdr);
317 
318 	return 0;
319 }
320 
321 /**
322  * Attempts to transmit an SMP error response.  This function consumes both
323  * supplied buffers.
324  *
325  * @param streamer	The SMP streamer for building and transmitting the response.
326  * @param req_hdr	The header of the request which elicited the error.
327  * @param req		The buffer holding the request.
328  * @param rsp		The buffer holding the response, or NULL if none was allocated.
329  * @param status	The status to indicate in the error response.
330  * @param rsn		The text explanation to @status encoded as "rsn" into CBOR
331  *			response.
332  */
smp_on_err(struct smp_streamer * streamer,const struct smp_hdr * req_hdr,void * req,void * rsp,int status,const char * rsn)333 static void smp_on_err(struct smp_streamer *streamer, const struct smp_hdr *req_hdr,
334 		       void *req, void *rsp, int status, const char *rsn)
335 {
336 	int rc;
337 
338 	/* Prefer the response buffer for holding the error response.  If no
339 	 * response buffer was allocated, use the request buffer instead.
340 	 */
341 	if (rsp == NULL) {
342 		rsp = req;
343 		req = NULL;
344 	}
345 
346 	/* Clear the partial response from the buffer, if any. */
347 	cbor_nb_writer_init(streamer->writer, rsp);
348 
349 	/* Build and transmit the error response. */
350 	rc = smp_build_err_rsp(streamer, req_hdr, status, rsn);
351 	if (rc == 0) {
352 		streamer->smpt->functions.output(rsp);
353 		rsp = NULL;
354 	}
355 
356 	/* Free any extra buffers. */
357 	smp_free_buf(req, streamer->smpt);
358 	smp_free_buf(rsp, streamer->smpt);
359 }
360 
361 /**
362  * Processes all SMP requests in an incoming packet.  Requests are processed
363  * sequentially from the start of the packet to the end.  Each response is sent
364  * individually in its own packet.  If a request elicits an error response,
365  * processing of the packet is aborted.  This function consumes the supplied
366  * request buffer regardless of the outcome.
367  * The function will return MGMT_ERR_EOK (0) when given an empty input stream,
368  * and will also release the buffer from the stream; it does not return
369  * MTMT_ERR_ECORRUPT, or any other MGMT error, because there was no error while
370  * processing of the input stream, it is callers fault that an empty stream has
371  * been passed to the function.
372  *
373  * @param streamer	The streamer to use for reading, writing, and transmitting.
374  * @param req		A buffer containing the request packet.
375  *
376  * @return 0 on success or when input stream is empty;
377  *         MGMT_ERR_ECORRUPT if buffer starts with non SMP data header or there
378  *         is not enough bytes to process header, or other MGMT_ERR_[...] code on
379  *         failure.
380  */
smp_process_request_packet(struct smp_streamer * streamer,void * vreq)381 int smp_process_request_packet(struct smp_streamer *streamer, void *vreq)
382 {
383 	struct smp_hdr req_hdr = { 0 };
384 	void *rsp;
385 	struct net_buf *req = vreq;
386 	bool valid_hdr = false;
387 	bool handler_found = false;
388 	int rc = 0;
389 	const char *rsn = NULL;
390 
391 #if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS)
392 	struct mgmt_evt_op_cmd_arg cmd_done_arg;
393 	int32_t err_rc;
394 	uint16_t err_group;
395 #endif
396 
397 	rsp = NULL;
398 
399 	while (req->len > 0) {
400 		handler_found = false;
401 		valid_hdr = false;
402 
403 		/* Read the management header and strip it from the request. */
404 		rc = smp_read_hdr(req, &req_hdr);
405 		if (rc != 0) {
406 			rc = MGMT_ERR_ECORRUPT;
407 			break;
408 		}
409 
410 		valid_hdr = true;
411 		/* Skip the smp_hdr */
412 		net_buf_pull(req, sizeof(struct smp_hdr));
413 		/* Does buffer contain whole message? */
414 		if (req->len < req_hdr.nh_len) {
415 			rc = MGMT_ERR_ECORRUPT;
416 			break;
417 		}
418 
419 		if (req_hdr.nh_op == MGMT_OP_READ || req_hdr.nh_op == MGMT_OP_WRITE) {
420 			rsp = smp_alloc_rsp(req, streamer->smpt);
421 			if (rsp == NULL) {
422 				rc = MGMT_ERR_ENOMEM;
423 				break;
424 			}
425 
426 			cbor_nb_reader_init(streamer->reader, req);
427 			cbor_nb_writer_init(streamer->writer, rsp);
428 
429 			/* Process the request payload and build the response. */
430 			rc = smp_handle_single_req(streamer, &req_hdr, &rsn);
431 			handler_found = (rc != MGMT_ERR_ENOTSUP);
432 			if (rc != 0) {
433 				break;
434 			}
435 
436 			/* Send the response. */
437 			rc = streamer->smpt->functions.output(rsp);
438 			rsp = NULL;
439 		} else if (IS_ENABLED(CONFIG_SMP_CLIENT) && (req_hdr.nh_op == MGMT_OP_READ_RSP ||
440 			   req_hdr.nh_op == MGMT_OP_WRITE_RSP)) {
441 			rc = smp_client_single_response(req, &req_hdr);
442 
443 			if (rc == MGMT_ERR_EOK) {
444 				handler_found = true;
445 			} else {
446 				/* Server shuold not send error response for response */
447 				valid_hdr = false;
448 			}
449 
450 		} else {
451 			rc = MGMT_ERR_ENOTSUP;
452 		}
453 
454 		if (rc != 0) {
455 			break;
456 		}
457 		/* Trim processed request to free up space for subsequent responses. */
458 		net_buf_pull(req, req_hdr.nh_len);
459 
460 #if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS)
461 		cmd_done_arg.group = req_hdr.nh_group;
462 		cmd_done_arg.id = req_hdr.nh_id;
463 		cmd_done_arg.err = MGMT_ERR_EOK;
464 
465 		(void)mgmt_callback_notify(MGMT_EVT_OP_CMD_DONE, &cmd_done_arg,
466 					   sizeof(cmd_done_arg), &err_rc, &err_group);
467 #endif
468 	}
469 
470 	if (rc != 0 && valid_hdr) {
471 		smp_on_err(streamer, &req_hdr, req, rsp, rc, rsn);
472 
473 		if (handler_found) {
474 #if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS)
475 			cmd_done_arg.group = req_hdr.nh_group;
476 			cmd_done_arg.id = req_hdr.nh_id;
477 			cmd_done_arg.err = rc;
478 
479 			(void)mgmt_callback_notify(MGMT_EVT_OP_CMD_DONE, &cmd_done_arg,
480 						   sizeof(cmd_done_arg), &err_rc, &err_group);
481 #endif
482 		}
483 
484 		return rc;
485 	}
486 
487 	smp_free_buf(req, streamer->smpt);
488 	smp_free_buf(rsp, streamer->smpt);
489 
490 	return rc;
491 }
492 
smp_add_cmd_err(zcbor_state_t * zse,uint16_t group,uint16_t ret)493 bool smp_add_cmd_err(zcbor_state_t *zse, uint16_t group, uint16_t ret)
494 {
495 	bool ok = true;
496 
497 	if (ret != 0) {
498 #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
499 		struct cbor_nb_writer *container = CONTAINER_OF(zse, struct cbor_nb_writer, zs[0]);
500 
501 		container->error_group = group;
502 		container->error_ret = ret;
503 #endif
504 
505 		ok = zcbor_tstr_put_lit(zse, "err")		&&
506 		     zcbor_map_start_encode(zse, 2)		&&
507 		     zcbor_tstr_put_lit(zse, "group")		&&
508 		     zcbor_uint32_put(zse, (uint32_t)group)	&&
509 		     zcbor_tstr_put_lit(zse, "rc")		&&
510 		     zcbor_uint32_put(zse, (uint32_t)ret)	&&
511 		     zcbor_map_end_encode(zse, 2);
512 	}
513 
514 	return ok;
515 }
516