blob: 45550483ffb034461d9c34c813717c1437f97d93 [file] [log] [blame]
/*
* Copyright Runtime.io 2018. All rights reserved.
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/__assert.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/sys/crc.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/net/buf.h>
#include <zephyr/sys/base64.h>
#include <zephyr/mgmt/mcumgr/buf.h>
#include <zephyr/mgmt/mcumgr/serial.h>
static void mcumgr_serial_free_rx_ctxt(struct mcumgr_serial_rx_ctxt *rx_ctxt)
{
if (rx_ctxt->nb != NULL) {
mcumgr_buf_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;
uint16_t crc;
uint16_t op;
int rc;
if (rx_ctxt->nb == NULL) {
rx_ctxt->nb = mcumgr_buf_alloc();
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;
}
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;
}