diff --git a/subsys/mgmt/mcumgr/transport/CMakeLists.txt b/subsys/mgmt/mcumgr/transport/CMakeLists.txt index bbcea05eda..b2f854c35a 100644 --- a/subsys/mgmt/mcumgr/transport/CMakeLists.txt +++ b/subsys/mgmt/mcumgr/transport/CMakeLists.txt @@ -17,7 +17,7 @@ zephyr_library_sources_ifdef(CONFIG_MCUMGR_TRANSPORT_REASSEMBLY zephyr_library_sources_ifdef(CONFIG_MCUMGR_TRANSPORT_BM_UART src/smp_uart.c src/bm_uart_mcumgr.c - ${ZEPHYR_BASE}/subsys/mgmt/mcumgr/transport/src/serial_util.c + src/serial_util.c ) zephyr_include_directories(${ZEPHYR_BASE}/subsys/mgmt/mcumgr/transport/include include) diff --git a/subsys/mgmt/mcumgr/transport/include/serial.h b/subsys/mgmt/mcumgr/transport/include/serial.h new file mode 100644 index 0000000000..e85501712b --- /dev/null +++ b/subsys/mgmt/mcumgr/transport/include/serial.h @@ -0,0 +1,105 @@ +/* + * Copyright Runtime.io 2018. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_MGMT_SERIAL_H_ +#define ZEPHYR_INCLUDE_MGMT_SERIAL_H_ + +/** + * @brief This allows to use the MCUmgr SMP protocol over serial. + * @defgroup mcumgr_transport_serial Serial transport + * @ingroup mcumgr_transport + * @{ + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Serial packet header */ +#define MCUMGR_SERIAL_HDR_PKT 0x0609 +/** Serial fragment header */ +#define MCUMGR_SERIAL_HDR_FRAG 0x0414 +/** Maximum frame size */ +#define MCUMGR_SERIAL_MAX_FRAME 127 + +/** First byte of packet header */ +#define MCUMGR_SERIAL_HDR_PKT_1 (MCUMGR_SERIAL_HDR_PKT >> 8) +/** Second byte of packet header */ +#define MCUMGR_SERIAL_HDR_PKT_2 (MCUMGR_SERIAL_HDR_PKT & 0xff) +/** First byte of fragment header */ +#define MCUMGR_SERIAL_HDR_FRAG_1 (MCUMGR_SERIAL_HDR_FRAG >> 8) +/** Second byte of fragment header */ +#define MCUMGR_SERIAL_HDR_FRAG_2 (MCUMGR_SERIAL_HDR_FRAG & 0xff) + +/** + * @brief Maintains state for an incoming mcumgr request packet. + */ +struct mcumgr_serial_rx_ctxt { + /** Contains the partially- or fully-received mcumgr request. Data + * stored in this buffer has already been base64-decoded. + */ + struct net_buf *nb; + + /** Length of full packet, as read from header. */ + uint16_t pkt_len; + +}; + +/** @typedef mcumgr_serial_tx_cb + * @brief Transmits a chunk of raw response data. + * + * @param data The data to transmit. + * @param len The number of bytes to transmit. + * + * @return 0 on success; negative error code on failure. + */ +typedef int (*mcumgr_serial_tx_cb)(const void *data, int len); + +/** + * @brief Processes an mcumgr request fragment received over a serial + * transport. + * + * Processes an mcumgr request fragment received over a serial transport. If + * the fragment is the end of a valid mcumgr request, this function returns a + * net_buf containing the decoded request. It is the caller's responsibility + * to free the net_buf after it has been processed. + * + * @param rx_ctxt The receive context associated with the serial + * transport being used. + * @param frag The incoming fragment to process. + * @param frag_len The length of the fragment, in bytes. + * + * @return A net_buf containing the decoded request if a + * complete and valid request has been + * received. + * NULL if the packet is incomplete or invalid. + */ +struct net_buf *mcumgr_serial_process_frag( + struct mcumgr_serial_rx_ctxt *rx_ctxt, + const uint8_t *frag, int frag_len); + +/** + * @brief Encodes and transmits an mcumgr packet over serial. + * + * @param data The mcumgr packet data to send. + * @param len The length of the unencoded mcumgr packet. + * @param cb A callback used to transmit raw bytes. + * + * @return 0 on success; negative error code on failure. + */ +int mcumgr_serial_tx_pkt(const uint8_t *data, int len, mcumgr_serial_tx_cb cb); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif diff --git a/subsys/mgmt/mcumgr/transport/src/serial_util.c b/subsys/mgmt/mcumgr/transport/src/serial_util.c new file mode 100644 index 0000000000..7fb2a65ac8 --- /dev/null +++ b/subsys/mgmt/mcumgr/transport/src/serial_util.c @@ -0,0 +1,316 @@ +/* + * Copyright Runtime.io 2018. All rights reserved. + * Copyright (c) 2021-2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void mcumgr_serial_free_rx_ctxt(struct mcumgr_serial_rx_ctxt *rx_ctxt) +{ + if (rx_ctxt->nb != NULL) { + smp_packet_free(rx_ctxt->nb); + rx_ctxt->nb = NULL; + } +} + +static uint16_t mcumgr_serial_calc_crc(const uint8_t *data, int len) +{ + return crc16_itu_t(0x0000, data, len); +} + +static int mcumgr_serial_extract_len(struct mcumgr_serial_rx_ctxt *rx_ctxt) +{ + if (rx_ctxt->nb->len < 2) { + return -EINVAL; + } + + rx_ctxt->pkt_len = net_buf_pull_be16(rx_ctxt->nb); + return 0; +} + +static int mcumgr_serial_decode_frag(struct mcumgr_serial_rx_ctxt *rx_ctxt, + const uint8_t *frag, int frag_len) +{ + size_t dec_len; + int rc; + + rc = base64_decode(rx_ctxt->nb->data + rx_ctxt->nb->len, + net_buf_tailroom(rx_ctxt->nb), &dec_len, + frag, frag_len); + if (rc != 0) { + return -EINVAL; + } + + rx_ctxt->nb->len += dec_len; + + return 0; +} + + +/** + * Processes a received mcumgr frame. + * + * @return true if a complete packet was received; + * false if the frame is invalid or if additional + * fragments are expected. + */ +struct net_buf *mcumgr_serial_process_frag(struct mcumgr_serial_rx_ctxt *rx_ctxt, + const uint8_t *frag, int frag_len) +{ + struct net_buf *nb; + + int rc; + uint16_t crc; + uint16_t op; + + if (rx_ctxt->nb == NULL) { + rx_ctxt->nb = smp_packet_alloc(); + net_buf_reset(rx_ctxt->nb); + if (rx_ctxt->nb == NULL) { + return NULL; + } + } + + if (frag_len < sizeof(op)) { + return NULL; + } + + op = sys_be16_to_cpu(*(uint16_t *)frag); + switch (op) { + case MCUMGR_SERIAL_HDR_PKT: + net_buf_reset(rx_ctxt->nb); + break; + + case MCUMGR_SERIAL_HDR_FRAG: + if (rx_ctxt->nb->len == 0U) { + mcumgr_serial_free_rx_ctxt(rx_ctxt); + return NULL; + } + break; + + default: + return NULL; + } + + rc = mcumgr_serial_decode_frag(rx_ctxt, + frag + sizeof(op), + frag_len - sizeof(op)); + if (rc != 0) { + mcumgr_serial_free_rx_ctxt(rx_ctxt); + return NULL; + } + + if (op == MCUMGR_SERIAL_HDR_PKT) { + rc = mcumgr_serial_extract_len(rx_ctxt); + if (rc < 0) { + mcumgr_serial_free_rx_ctxt(rx_ctxt); + return NULL; + } + } + + if (rx_ctxt->nb->len < rx_ctxt->pkt_len) { + /* More fragments expected. */ + return NULL; + } else if (rx_ctxt->nb->len > rx_ctxt->pkt_len) { + /* Payload longer than indicated in header. */ + mcumgr_serial_free_rx_ctxt(rx_ctxt); + return NULL; + } + + crc = mcumgr_serial_calc_crc(rx_ctxt->nb->data, rx_ctxt->nb->len); + if (crc != 0U) { + mcumgr_serial_free_rx_ctxt(rx_ctxt); + return NULL; + } + + /* Packet is complete; strip the CRC. */ + rx_ctxt->nb->len -= 2U; + + nb = rx_ctxt->nb; + rx_ctxt->nb = NULL; + return nb; +} + +/** + * Base64-encodes a small chunk of data and transmits it. The data must be no larger than three + * bytes. + */ +static int mcumgr_serial_tx_small(const void *data, int len, mcumgr_serial_tx_cb cb) +{ + uint8_t b64[4 + 1]; /* +1 required for null terminator. */ + size_t dst_len; + int rc; + + rc = base64_encode(b64, sizeof(b64), &dst_len, data, len); + __ASSERT_NO_MSG(rc == 0); + __ASSERT_NO_MSG(dst_len == 4); + + return cb(b64, 4); +} + +/** + * @brief Transmits a single mcumgr packet over serial, splits into multiple frames as needed. + * + * @param data The packet payload to transmit. This does not include a header or + * CRC. + * @param len The size of the packet payload. + * @param cb A callback used for transmitting raw data. + * + * @return 0 on success; negative error code on failure. + */ +int mcumgr_serial_tx_pkt(const uint8_t *data, int len, mcumgr_serial_tx_cb cb) +{ + bool first = true; + bool last = false; + uint8_t raw[3]; + uint16_t u16; + uint16_t crc; + int src_off = 0; + int rc = 0; + int to_process; + int reminder; + + /* + * This is max input bytes that can be taken to the frame before encoding with Base64; + * Base64 has three-to-four ratio, which means that for each three input bytes there are + * going to be four bytes of output used. The frame starts with two byte marker and ends + * with newline character, which are not encoded (this is the "-3" in calculation). + */ + int max_input = (((MCUMGR_SERIAL_MAX_FRAME - 3) >> 2) * 3); + + /* Calculate the CRC16 checksum of the whole packet, prior to splitting it into frames */ + crc = mcumgr_serial_calc_crc(data, len); + + /* First frame marker */ + u16 = sys_cpu_to_be16(MCUMGR_SERIAL_HDR_PKT); + + while (src_off < len) { + /* Send first frame or continuation frame marker */ + rc = cb(&u16, sizeof(u16)); + if (rc != 0) { + return rc; + } + + /* + * Only the first fragment contains the packet length; the packet length, which is + * two byte long is paired with first byte of input buffer to form triplet for + * Base64 encoding. + */ + if (first) { + /* The size of the CRC16 should be added to packet length */ + u16 = sys_cpu_to_be16(len + 2); + memcpy(raw, &u16, sizeof(u16)); + raw[2] = data[0]; + + rc = mcumgr_serial_tx_small(raw, 3, cb); + if (rc != 0) { + return rc; + } + + ++src_off; + /* One triple of allowed input already used */ + max_input -= 3; + } + + /* + * Only as much as fits into the frame can be processed, but we also need to fit + * in the two byte CRC. The frame can not be stretched and current logic does not + * allow to send CRC separately so if CRC would not fit as a whole, shrink + * to_process by one byte forcing one byte to the next frame, with the CRC. + */ + to_process = MIN(max_input, len - src_off); + reminder = max_input - (len - src_off); + + if (reminder == 0 || reminder == 1) { + to_process -= 1; + + /* This is the last frame but the checksum cannot be added, remove the + * last packet flag until the function runs again with the final piece of + * data and place the checksum there + */ + last = false; + } else if (reminder >= 2) { + last = true; + } + + /* + * Try to process input buffer by chunks of three bytes that will be output as + * four byte chunks, due to Base64 encoding; the number of chunks that can be + * processed is calculated from number of three byte, complete, chunks in input + * buffer, but can not be greater than the number of four byte, complete, chunks + * that the frame can accept. + */ + while (to_process >= 3) { + memcpy(raw, data + src_off, 3); + rc = mcumgr_serial_tx_small(raw, 3, cb); + if (rc != 0) { + return rc; + } + src_off += 3; + to_process -= 3; + } + + if (last) { + /* + * Process the reminder bytes of the input buffer, after sending it in + * three byte chunks, and CRC. + */ + switch (len - src_off) { + case 0: + raw[0] = (crc & 0xff00) >> 8; + raw[1] = crc & 0x00ff; + rc = mcumgr_serial_tx_small(raw, 2, cb); + break; + + case 1: + raw[0] = data[src_off++]; + + raw[1] = (crc & 0xff00) >> 8; + raw[2] = crc & 0x00ff; + rc = mcumgr_serial_tx_small(raw, 3, cb); + break; + + case 2: + raw[0] = data[src_off++]; + raw[1] = data[src_off++]; + + raw[2] = (crc & 0xff00) >> 8; + rc = mcumgr_serial_tx_small(raw, 3, cb); + if (rc != 0) { + return rc; + } + + raw[0] = crc & 0x00ff; + rc = mcumgr_serial_tx_small(raw, 1, cb); + break; + } + + if (rc != 0) { + return rc; + } + } + + rc = cb("\n", 1); + if (rc != 0) { + return rc; + } + + /* Use a continuation frame marker for the next packet */ + u16 = sys_cpu_to_be16(MCUMGR_SERIAL_HDR_FRAG); + first = false; + } + + return 0; +}