1 /*
2  * Copyright Runtime.io 2018. All rights reserved.
3  * Copyright (c) 2021 Nordic Semiconductor ASA
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <zephyr/sys/__assert.h>
9 #include <string.h>
10 #include <stdbool.h>
11 #include <errno.h>
12 #include <zephyr/sys/crc.h>
13 #include <zephyr/sys/byteorder.h>
14 #include <zephyr/net_buf.h>
15 #include <zephyr/sys/base64.h>
16 #include <zephyr/mgmt/mcumgr/mgmt/mgmt.h>
17 #include <zephyr/mgmt/mcumgr/smp/smp.h>
18 #include <zephyr/mgmt/mcumgr/transport/serial.h>
19 
mcumgr_serial_free_rx_ctxt(struct mcumgr_serial_rx_ctxt * rx_ctxt)20 static void mcumgr_serial_free_rx_ctxt(struct mcumgr_serial_rx_ctxt *rx_ctxt)
21 {
22 	if (rx_ctxt->nb != NULL) {
23 		smp_packet_free(rx_ctxt->nb);
24 		rx_ctxt->nb = NULL;
25 	}
26 }
27 
mcumgr_serial_calc_crc(const uint8_t * data,int len)28 static uint16_t mcumgr_serial_calc_crc(const uint8_t *data, int len)
29 {
30 	return crc16_itu_t(0x0000, data, len);
31 }
32 
mcumgr_serial_extract_len(struct mcumgr_serial_rx_ctxt * rx_ctxt)33 static int mcumgr_serial_extract_len(struct mcumgr_serial_rx_ctxt *rx_ctxt)
34 {
35 	if (rx_ctxt->nb->len < 2) {
36 		return -EINVAL;
37 	}
38 
39 	rx_ctxt->pkt_len = net_buf_pull_be16(rx_ctxt->nb);
40 	return 0;
41 }
42 
mcumgr_serial_decode_frag(struct mcumgr_serial_rx_ctxt * rx_ctxt,const uint8_t * frag,int frag_len)43 static int mcumgr_serial_decode_frag(struct mcumgr_serial_rx_ctxt *rx_ctxt,
44 				     const uint8_t *frag, int frag_len)
45 {
46 	size_t dec_len;
47 	int rc;
48 
49 	rc = base64_decode(rx_ctxt->nb->data + rx_ctxt->nb->len,
50 				   net_buf_tailroom(rx_ctxt->nb), &dec_len,
51 				   frag, frag_len);
52 	if (rc != 0) {
53 		return -EINVAL;
54 	}
55 
56 	rx_ctxt->nb->len += dec_len;
57 
58 	return 0;
59 }
60 
61 /**
62  * Processes a received mcumgr frame.
63  *
64  * @return                      true if a complete packet was received;
65  *                              false if the frame is invalid or if additional
66  *                                  fragments are expected.
67  */
mcumgr_serial_process_frag(struct mcumgr_serial_rx_ctxt * rx_ctxt,const uint8_t * frag,int frag_len)68 struct net_buf *mcumgr_serial_process_frag(
69 	struct mcumgr_serial_rx_ctxt *rx_ctxt,
70 	const uint8_t *frag, int frag_len)
71 {
72 	struct net_buf *nb;
73 	uint16_t crc;
74 	uint16_t op;
75 	int rc;
76 
77 	if (rx_ctxt->nb == NULL) {
78 		rx_ctxt->nb = smp_packet_alloc();
79 		if (rx_ctxt->nb == NULL) {
80 			return NULL;
81 		}
82 	}
83 
84 	if (frag_len < sizeof(op)) {
85 		return NULL;
86 	}
87 
88 	op = sys_be16_to_cpu(*(uint16_t *)frag);
89 	switch (op) {
90 	case MCUMGR_SERIAL_HDR_PKT:
91 		net_buf_reset(rx_ctxt->nb);
92 		break;
93 
94 	case MCUMGR_SERIAL_HDR_FRAG:
95 		if (rx_ctxt->nb->len == 0U) {
96 			mcumgr_serial_free_rx_ctxt(rx_ctxt);
97 			return NULL;
98 		}
99 		break;
100 
101 	default:
102 		return NULL;
103 	}
104 
105 	rc = mcumgr_serial_decode_frag(rx_ctxt,
106 				       frag + sizeof(op),
107 				       frag_len - sizeof(op));
108 	if (rc != 0) {
109 		mcumgr_serial_free_rx_ctxt(rx_ctxt);
110 		return NULL;
111 	}
112 
113 	if (op == MCUMGR_SERIAL_HDR_PKT) {
114 		rc = mcumgr_serial_extract_len(rx_ctxt);
115 		if (rc < 0) {
116 			mcumgr_serial_free_rx_ctxt(rx_ctxt);
117 			return NULL;
118 		}
119 	}
120 
121 	if (rx_ctxt->nb->len < rx_ctxt->pkt_len) {
122 		/* More fragments expected. */
123 		return NULL;
124 	}
125 
126 	if (rx_ctxt->nb->len > rx_ctxt->pkt_len) {
127 		/* Payload longer than indicated in header. */
128 		mcumgr_serial_free_rx_ctxt(rx_ctxt);
129 		return NULL;
130 	}
131 
132 	crc = mcumgr_serial_calc_crc(rx_ctxt->nb->data, rx_ctxt->nb->len);
133 	if (crc != 0U) {
134 		mcumgr_serial_free_rx_ctxt(rx_ctxt);
135 		return NULL;
136 	}
137 
138 	/* Packet is complete; strip the CRC. */
139 	rx_ctxt->nb->len -= 2U;
140 
141 	nb = rx_ctxt->nb;
142 	rx_ctxt->nb = NULL;
143 	return nb;
144 }
145 
146 /**
147  * Base64-encodes a small chunk of data and transmits it. The data must be no larger than three
148  * bytes.
149  */
mcumgr_serial_tx_small(const void * data,int len,mcumgr_serial_tx_cb cb)150 static int mcumgr_serial_tx_small(const void *data, int len, mcumgr_serial_tx_cb cb)
151 {
152 	uint8_t b64[4 + 1]; /* +1 required for null terminator. */
153 	size_t dst_len;
154 	int rc;
155 
156 	rc = base64_encode(b64, sizeof(b64), &dst_len, data, len);
157 	__ASSERT_NO_MSG(rc == 0);
158 	__ASSERT_NO_MSG(dst_len == 4);
159 
160 	return cb(b64, 4);
161 }
162 
163 /**
164  * @brief Transmits a single mcumgr packet over serial, splits into multiple frames as needed.
165  *
166  * @param data                  The packet payload to transmit. This does not include a header or
167  *                                  CRC.
168  * @param len                   The size of the packet payload.
169  * @param cb                    A callback used for transmitting raw data.
170  *
171  * @return                      0 on success; negative error code on failure.
172  */
mcumgr_serial_tx_pkt(const uint8_t * data,int len,mcumgr_serial_tx_cb cb)173 int mcumgr_serial_tx_pkt(const uint8_t *data, int len, mcumgr_serial_tx_cb cb)
174 {
175 	bool first = true;
176 	bool last = false;
177 	uint8_t raw[3];
178 	uint16_t u16;
179 	uint16_t crc;
180 	int src_off = 0;
181 	int rc = 0;
182 	int to_process;
183 	int reminder;
184 
185 	/*
186 	 * This is max input bytes that can be taken to the frame before encoding with Base64;
187 	 * Base64 has three-to-four ratio, which means that for each three input bytes there are
188 	 * going to be four bytes of output used. The frame starts with two byte marker and ends
189 	 * with newline character, which are not encoded (this is the "-3" in calculation).
190 	 */
191 	int max_input = (((MCUMGR_SERIAL_MAX_FRAME - 3) >> 2) * 3);
192 
193 	/* Calculate the CRC16 checksum of the whole packet, prior to splitting it into frames */
194 	crc = mcumgr_serial_calc_crc(data, len);
195 
196 	/* First frame marker */
197 	u16 = sys_cpu_to_be16(MCUMGR_SERIAL_HDR_PKT);
198 
199 	while (src_off < len) {
200 		/* Send first frame or continuation frame marker */
201 		rc = cb(&u16, sizeof(u16));
202 		if (rc != 0) {
203 			return rc;
204 		}
205 
206 		/*
207 		 * Only the first fragment contains the packet length; the packet length, which is
208 		 * two byte long is paired with first byte of input buffer to form triplet for
209 		 * Base64 encoding.
210 		 */
211 		if (first) {
212 			/* The size of the CRC16 should be added to packet length */
213 			u16 = sys_cpu_to_be16(len + 2);
214 			memcpy(raw, &u16, sizeof(u16));
215 			raw[2] = data[0];
216 
217 			rc = mcumgr_serial_tx_small(raw, 3, cb);
218 			if (rc != 0) {
219 				return rc;
220 			}
221 
222 			++src_off;
223 			/* One triple of allowed input already used */
224 			max_input -= 3;
225 		}
226 
227 		/*
228 		 * Only as much as fits into the frame can be processed, but we also need to fit
229 		 * in the two byte CRC. The frame can not be stretched and current logic does not
230 		 * allow to send CRC separately so if CRC would not fit as a whole, shrink
231 		 * to_process by one byte forcing one byte to the next frame, with the CRC.
232 		 */
233 		to_process = MIN(max_input, len - src_off);
234 		reminder = max_input - (len - src_off);
235 
236 		if (reminder == 0 || reminder == 1) {
237 			to_process -= 1;
238 
239 			/* This is the last frame but the checksum cannot be added, remove the
240 			 * last packet flag until the function runs again with the final piece of
241 			 * data and place the checksum there
242 			 */
243 			last = false;
244 		} else if (reminder >= 2) {
245 			last = true;
246 		}
247 
248 		/*
249 		 * Try to process input buffer by chunks of three bytes that will be output as
250 		 * four byte chunks, due to Base64 encoding; the number of chunks that can be
251 		 * processed is calculated from number of three byte, complete, chunks in input
252 		 * buffer, but can not be greater than the number of four byte, complete, chunks
253 		 * that the frame can accept.
254 		 */
255 		while (to_process >= 3) {
256 			memcpy(raw, data + src_off, 3);
257 			rc = mcumgr_serial_tx_small(raw, 3, cb);
258 			if (rc != 0) {
259 				return rc;
260 			}
261 			src_off += 3;
262 			to_process -= 3;
263 		}
264 
265 		if (last) {
266 			/*
267 			 * Process the reminder bytes of the input buffer, after sending it in
268 			 * three byte chunks, and CRC.
269 			 */
270 			switch (len - src_off) {
271 			case 0:
272 				raw[0] = (crc & 0xff00) >> 8;
273 				raw[1] = crc & 0x00ff;
274 				rc = mcumgr_serial_tx_small(raw, 2, cb);
275 				break;
276 
277 			case 1:
278 				raw[0] = data[src_off++];
279 
280 				raw[1] = (crc & 0xff00) >> 8;
281 				raw[2] = crc & 0x00ff;
282 				rc = mcumgr_serial_tx_small(raw, 3, cb);
283 				break;
284 
285 			case 2:
286 				raw[0] = data[src_off++];
287 				raw[1] = data[src_off++];
288 
289 				raw[2] = (crc & 0xff00) >> 8;
290 				rc = mcumgr_serial_tx_small(raw, 3, cb);
291 				if (rc != 0) {
292 					return rc;
293 				}
294 
295 				raw[0] = crc & 0x00ff;
296 				rc = mcumgr_serial_tx_small(raw, 1, cb);
297 				break;
298 			}
299 
300 			if (rc != 0) {
301 				return rc;
302 			}
303 		}
304 
305 		rc = cb("\n", 1);
306 		if (rc != 0) {
307 			return rc;
308 		}
309 
310 		/* Use a continuation frame marker for the next packet */
311 		u16 = sys_cpu_to_be16(MCUMGR_SERIAL_HDR_FRAG);
312 		first = false;
313 	}
314 
315 	return 0;
316 }
317