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