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