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);
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);
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,bool * handler_found)159 static int smp_handle_single_payload(struct smp_streamer *cbuf, const struct smp_hdr *req_hdr,
160 				     bool *handler_found)
161 {
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 	handler = mgmt_find_handler(req_hdr->nh_group, req_hdr->nh_id);
173 	if (handler == NULL) {
174 		return MGMT_ERR_ENOTSUP;
175 	}
176 
177 	switch (req_hdr->nh_op) {
178 	case MGMT_OP_READ:
179 		handler_fn = handler->mh_read;
180 		break;
181 
182 	case MGMT_OP_WRITE:
183 		handler_fn = handler->mh_write;
184 		break;
185 
186 	default:
187 		return MGMT_ERR_EINVAL;
188 	}
189 
190 	if (handler_fn) {
191 		bool ok;
192 
193 		*handler_found = true;
194 		ok = zcbor_map_start_encode(cbuf->writer->zs,
195 					    CONFIG_MCUMGR_SMP_CBOR_MAX_MAIN_MAP_ENTRIES);
196 
197 		MGMT_CTXT_SET_RC_RSN(cbuf, NULL);
198 
199 		if (!ok) {
200 			return MGMT_ERR_EMSGSIZE;
201 		}
202 
203 #if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS)
204 		cmd_recv.group = req_hdr->nh_group;
205 		cmd_recv.id = req_hdr->nh_id;
206 		cmd_recv.err = MGMT_ERR_EOK;
207 
208 		/* Send request to application to check if handler should run or not. */
209 		status = mgmt_callback_notify(MGMT_EVT_OP_CMD_RECV, &cmd_recv, sizeof(cmd_recv),
210 					      &err_rc, &err_group);
211 
212 		/* Skip running the command if a handler reported an error and return that
213 		 * instead.
214 		 */
215 		if (status != MGMT_CB_OK) {
216 			if (status == MGMT_CB_ERROR_RC) {
217 				rc = err_rc;
218 			} else {
219 				ok = smp_add_cmd_err(cbuf->writer->zs, err_group,
220 						     (uint16_t)err_rc);
221 
222 				rc = (ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE);
223 			}
224 
225 			goto end;
226 		}
227 #endif
228 
229 		rc = handler_fn(cbuf);
230 
231 #if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS)
232 end:
233 #endif
234 		/* End response payload. */
235 		if (!zcbor_map_end_encode(cbuf->writer->zs,
236 					  CONFIG_MCUMGR_SMP_CBOR_MAX_MAIN_MAP_ENTRIES) &&
237 		    rc == 0) {
238 			rc = MGMT_ERR_EMSGSIZE;
239 		}
240 	} else {
241 		rc = MGMT_ERR_ENOTSUP;
242 	}
243 
244 	return rc;
245 }
246 
247 /**
248  * Processes a single SMP request and generates a complete response (i.e.,
249  * header and payload).  On success, the response is written using the supplied
250  * streamer but not transmitted.  On failure, no error response gets written;
251  * the caller is expected to build an error response from the return code.
252  *
253  * @param streamer	The SMP streamer to use for reading the request and writing the response.
254  * @param req_hdr	The management header belonging to the incoming request (host-byte order).
255  *
256  * @return A MGMT_ERR_[...] error code.
257  */
smp_handle_single_req(struct smp_streamer * streamer,const struct smp_hdr * req_hdr,bool * handler_found,const char ** rsn)258 static int smp_handle_single_req(struct smp_streamer *streamer, const struct smp_hdr *req_hdr,
259 				 bool *handler_found, const char **rsn)
260 {
261 	struct smp_hdr rsp_hdr;
262 	struct cbor_nb_writer *nbw = streamer->writer;
263 	zcbor_state_t *zsp = nbw->zs;
264 	int rc;
265 
266 #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
267 	nbw->error_group = 0;
268 	nbw->error_ret = 0;
269 #else
270 	if (req_hdr->nh_version == SMP_MCUMGR_VERSION_1) {
271 		/* Support for the original version is excluded in this build */
272 		return MGMT_ERR_UNSUPPORTED_TOO_OLD;
273 	}
274 #endif
275 
276 	/* We do not currently support future versions of the protocol */
277 	if (req_hdr->nh_version > SMP_MCUMGR_VERSION_2) {
278 		return MGMT_ERR_UNSUPPORTED_TOO_NEW;
279 	}
280 
281 	/* Process the request and write the response payload. */
282 	rc = smp_handle_single_payload(streamer, req_hdr, handler_found);
283 	if (rc != 0) {
284 		*rsn = MGMT_CTXT_RC_RSN(streamer);
285 		return rc;
286 	}
287 
288 #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
289 	/* If using the legacy protocol, translate the error code to a return code */
290 	if (nbw->error_ret != 0 && req_hdr->nh_version == 0) {
291 		rc = smp_translate_error_code(nbw->error_group, nbw->error_ret);
292 		*rsn = MGMT_CTXT_RC_RSN(streamer);
293 		return rc;
294 	}
295 #endif
296 
297 	smp_make_rsp_hdr(req_hdr, &rsp_hdr,
298 			 zsp->payload_mut - nbw->nb->data - MGMT_HDR_SIZE);
299 	nbw->nb->len = zsp->payload_mut - nbw->nb->data;
300 	smp_write_hdr(streamer, &rsp_hdr);
301 
302 	return 0;
303 }
304 
305 /**
306  * Attempts to transmit an SMP error response.  This function consumes both
307  * supplied buffers.
308  *
309  * @param streamer	The SMP streamer for building and transmitting the response.
310  * @param req_hdr	The header of the request which elicited the error.
311  * @param req		The buffer holding the request.
312  * @param rsp		The buffer holding the response, or NULL if none was allocated.
313  * @param status	The status to indicate in the error response.
314  * @param rsn		The text explanation to @status encoded as "rsn" into CBOR
315  *			response.
316  */
smp_on_err(struct smp_streamer * streamer,const struct smp_hdr * req_hdr,void * req,void * rsp,int status,const char * rsn)317 static void smp_on_err(struct smp_streamer *streamer, const struct smp_hdr *req_hdr,
318 		       void *req, void *rsp, int status, const char *rsn)
319 {
320 	int rc;
321 
322 	/* Prefer the response buffer for holding the error response.  If no
323 	 * response buffer was allocated, use the request buffer instead.
324 	 */
325 	if (rsp == NULL) {
326 		rsp = req;
327 		req = NULL;
328 	}
329 
330 	/* Clear the partial response from the buffer, if any. */
331 	cbor_nb_writer_init(streamer->writer, rsp);
332 
333 	/* Build and transmit the error response. */
334 	rc = smp_build_err_rsp(streamer, req_hdr, status, rsn);
335 	if (rc == 0) {
336 		streamer->smpt->functions.output(rsp);
337 		rsp = NULL;
338 	}
339 
340 	/* Free any extra buffers. */
341 	smp_free_buf(req, streamer->smpt);
342 	smp_free_buf(rsp, streamer->smpt);
343 }
344 
345 /**
346  * Processes all SMP requests in an incoming packet.  Requests are processed
347  * sequentially from the start of the packet to the end.  Each response is sent
348  * individually in its own packet.  If a request elicits an error response,
349  * processing of the packet is aborted.  This function consumes the supplied
350  * request buffer regardless of the outcome.
351  * The function will return MGMT_ERR_EOK (0) when given an empty input stream,
352  * and will also release the buffer from the stream; it does not return
353  * MTMT_ERR_ECORRUPT, or any other MGMT error, because there was no error while
354  * processing of the input stream, it is callers fault that an empty stream has
355  * been passed to the function.
356  *
357  * @param streamer	The streamer to use for reading, writing, and transmitting.
358  * @param req		A buffer containing the request packet.
359  *
360  * @return 0 on success or when input stream is empty;
361  *         MGMT_ERR_ECORRUPT if buffer starts with non SMP data header or there
362  *         is not enough bytes to process header, or other MGMT_ERR_[...] code on
363  *         failure.
364  */
smp_process_request_packet(struct smp_streamer * streamer,void * vreq)365 int smp_process_request_packet(struct smp_streamer *streamer, void *vreq)
366 {
367 	struct smp_hdr req_hdr;
368 	void *rsp;
369 	struct net_buf *req = vreq;
370 	bool valid_hdr = false;
371 	bool handler_found = false;
372 	int rc = 0;
373 	const char *rsn = NULL;
374 
375 #if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS)
376 	struct mgmt_evt_op_cmd_arg cmd_done_arg;
377 	int32_t err_rc;
378 	uint16_t err_group;
379 #endif
380 
381 	rsp = NULL;
382 
383 	while (req->len > 0) {
384 		handler_found = false;
385 		valid_hdr = false;
386 
387 		/* Read the management header and strip it from the request. */
388 		rc = smp_read_hdr(req, &req_hdr);
389 		if (rc != 0) {
390 			rc = MGMT_ERR_ECORRUPT;
391 			break;
392 		}
393 
394 		valid_hdr = true;
395 		/* Skip the smp_hdr */
396 		net_buf_pull(req, sizeof(struct smp_hdr));
397 		/* Does buffer contain whole message? */
398 		if (req->len < req_hdr.nh_len) {
399 			rc = MGMT_ERR_ECORRUPT;
400 			break;
401 		}
402 
403 		if (req_hdr.nh_op == MGMT_OP_READ || req_hdr.nh_op == MGMT_OP_WRITE) {
404 			rsp = smp_alloc_rsp(req, streamer->smpt);
405 			if (rsp == NULL) {
406 				rc = MGMT_ERR_ENOMEM;
407 				break;
408 			}
409 
410 			cbor_nb_reader_init(streamer->reader, req);
411 			cbor_nb_writer_init(streamer->writer, rsp);
412 
413 			/* Process the request payload and build the response. */
414 			rc = smp_handle_single_req(streamer, &req_hdr, &handler_found, &rsn);
415 			if (rc != 0) {
416 				break;
417 			}
418 
419 			/* Send the response. */
420 			rc = streamer->smpt->functions.output(rsp);
421 			rsp = NULL;
422 		} else if (IS_ENABLED(CONFIG_SMP_CLIENT) && (req_hdr.nh_op == MGMT_OP_READ_RSP ||
423 			   req_hdr.nh_op == MGMT_OP_WRITE_RSP)) {
424 			rc = smp_client_single_response(req, &req_hdr);
425 
426 			if (rc == MGMT_ERR_EOK) {
427 				handler_found = true;
428 			} else {
429 				/* Server shuold not send error response for response */
430 				valid_hdr = false;
431 			}
432 
433 		} else {
434 			rc = MGMT_ERR_ENOTSUP;
435 		}
436 
437 		if (rc != 0) {
438 			break;
439 		}
440 		/* Trim processed request to free up space for subsequent responses. */
441 		net_buf_pull(req, req_hdr.nh_len);
442 
443 #if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS)
444 		cmd_done_arg.group = req_hdr.nh_group;
445 		cmd_done_arg.id = req_hdr.nh_id;
446 		cmd_done_arg.err = MGMT_ERR_EOK;
447 
448 		(void)mgmt_callback_notify(MGMT_EVT_OP_CMD_DONE, &cmd_done_arg,
449 					   sizeof(cmd_done_arg), &err_rc, &err_group);
450 #endif
451 	}
452 
453 	if (rc != 0 && valid_hdr) {
454 		smp_on_err(streamer, &req_hdr, req, rsp, rc, rsn);
455 
456 		if (handler_found) {
457 #if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS)
458 			cmd_done_arg.group = req_hdr.nh_group;
459 			cmd_done_arg.id = req_hdr.nh_id;
460 			cmd_done_arg.err = rc;
461 
462 			(void)mgmt_callback_notify(MGMT_EVT_OP_CMD_DONE, &cmd_done_arg,
463 						   sizeof(cmd_done_arg), &err_rc, &err_group);
464 #endif
465 		}
466 
467 		return rc;
468 	}
469 
470 	smp_free_buf(req, streamer->smpt);
471 	smp_free_buf(rsp, streamer->smpt);
472 
473 	return rc;
474 }
475 
smp_add_cmd_err(zcbor_state_t * zse,uint16_t group,uint16_t ret)476 bool smp_add_cmd_err(zcbor_state_t *zse, uint16_t group, uint16_t ret)
477 {
478 	bool ok = true;
479 
480 	if (ret != 0) {
481 #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
482 		struct cbor_nb_writer *container = CONTAINER_OF(zse, struct cbor_nb_writer, zs[0]);
483 
484 		container->error_group = group;
485 		container->error_ret = ret;
486 #endif
487 
488 		ok = zcbor_tstr_put_lit(zse, "err")		&&
489 		     zcbor_map_start_encode(zse, 2)		&&
490 		     zcbor_tstr_put_lit(zse, "group")		&&
491 		     zcbor_uint32_put(zse, (uint32_t)group)	&&
492 		     zcbor_tstr_put_lit(zse, "rc")		&&
493 		     zcbor_uint32_put(zse, (uint32_t)ret)	&&
494 		     zcbor_map_end_encode(zse, 2);
495 	}
496 
497 	return ok;
498 }
499