1/*
2 * Copyright (c) 2018-2022 Yubico AB. All rights reserved.
3 * Use of this source code is governed by a BSD-style
4 * license that can be found in the LICENSE file.
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include "fido.h"
9#include "packed.h"
10
11PACKED_TYPE(frame_t,
12struct frame {
13	uint32_t cid; /* channel id */
14	union {
15		uint8_t type;
16		struct {
17			uint8_t cmd;
18			uint8_t bcnth;
19			uint8_t bcntl;
20			uint8_t data[CTAP_MAX_REPORT_LEN - CTAP_INIT_HEADER_LEN];
21		} init;
22		struct {
23			uint8_t seq;
24			uint8_t data[CTAP_MAX_REPORT_LEN - CTAP_CONT_HEADER_LEN];
25		} cont;
26	} body;
27})
28
29#ifndef MIN
30#define MIN(x, y) ((x) > (y) ? (y) : (x))
31#endif
32
33static int
34tx_pkt(fido_dev_t *d, const void *pkt, size_t len, int *ms)
35{
36	struct timespec ts;
37	int n;
38
39	if (fido_time_now(&ts) != 0)
40		return (-1);
41
42	n = d->io.write(d->io_handle, pkt, len);
43
44	if (fido_time_delta(&ts, ms) != 0)
45		return (-1);
46
47	return (n);
48}
49
50static int
51tx_empty(fido_dev_t *d, uint8_t cmd, int *ms)
52{
53	struct frame	*fp;
54	unsigned char	 pkt[sizeof(*fp) + 1];
55	const size_t	 len = d->tx_len + 1;
56	int		 n;
57
58	memset(&pkt, 0, sizeof(pkt));
59	fp = (struct frame *)(pkt + 1);
60	fp->cid = d->cid;
61	fp->body.init.cmd = CTAP_FRAME_INIT | cmd;
62
63	if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 ||
64	    (size_t)n != len)
65		return (-1);
66
67	return (0);
68}
69
70static size_t
71tx_preamble(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms)
72{
73	struct frame	*fp;
74	unsigned char	 pkt[sizeof(*fp) + 1];
75	const size_t	 len = d->tx_len + 1;
76	int		 n;
77
78	if (d->tx_len - CTAP_INIT_HEADER_LEN > sizeof(fp->body.init.data))
79		return (0);
80
81	memset(&pkt, 0, sizeof(pkt));
82	fp = (struct frame *)(pkt + 1);
83	fp->cid = d->cid;
84	fp->body.init.cmd = CTAP_FRAME_INIT | cmd;
85	fp->body.init.bcnth = (count >> 8) & 0xff;
86	fp->body.init.bcntl = count & 0xff;
87	count = MIN(count, d->tx_len - CTAP_INIT_HEADER_LEN);
88	memcpy(&fp->body.init.data, buf, count);
89
90	if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 ||
91	    (size_t)n != len)
92		return (0);
93
94	return (count);
95}
96
97static size_t
98tx_frame(fido_dev_t *d, uint8_t seq, const void *buf, size_t count, int *ms)
99{
100	struct frame	*fp;
101	unsigned char	 pkt[sizeof(*fp) + 1];
102	const size_t	 len = d->tx_len + 1;
103	int		 n;
104
105	if (d->tx_len - CTAP_CONT_HEADER_LEN > sizeof(fp->body.cont.data))
106		return (0);
107
108	memset(&pkt, 0, sizeof(pkt));
109	fp = (struct frame *)(pkt + 1);
110	fp->cid = d->cid;
111	fp->body.cont.seq = seq;
112	count = MIN(count, d->tx_len - CTAP_CONT_HEADER_LEN);
113	memcpy(&fp->body.cont.data, buf, count);
114
115	if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 ||
116	    (size_t)n != len)
117		return (0);
118
119	return (count);
120}
121
122static int
123tx(fido_dev_t *d, uint8_t cmd, const unsigned char *buf, size_t count, int *ms)
124{
125	size_t n, sent;
126
127	if ((sent = tx_preamble(d, cmd, buf, count, ms)) == 0) {
128		fido_log_debug("%s: tx_preamble", __func__);
129		return (-1);
130	}
131
132	for (uint8_t seq = 0; sent < count; sent += n) {
133		if (seq & 0x80) {
134			fido_log_debug("%s: seq & 0x80", __func__);
135			return (-1);
136		}
137		if ((n = tx_frame(d, seq++, buf + sent, count - sent,
138		    ms)) == 0) {
139			fido_log_debug("%s: tx_frame", __func__);
140			return (-1);
141		}
142	}
143
144	return (0);
145}
146
147static int
148transport_tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms)
149{
150	struct timespec ts;
151	int n;
152
153	if (fido_time_now(&ts) != 0)
154		return (-1);
155
156	n = d->transport.tx(d, cmd, buf, count);
157
158	if (fido_time_delta(&ts, ms) != 0)
159		return (-1);
160
161	return (n);
162}
163
164int
165fido_tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms)
166{
167	fido_log_debug("%s: dev=%p, cmd=0x%02x", __func__, (void *)d, cmd);
168	fido_log_xxd(buf, count, "%s", __func__);
169
170	if (d->transport.tx != NULL)
171		return (transport_tx(d, cmd, buf, count, ms));
172	if (d->io_handle == NULL || d->io.write == NULL || count > UINT16_MAX) {
173		fido_log_debug("%s: invalid argument", __func__);
174		return (-1);
175	}
176
177	return (count == 0 ? tx_empty(d, cmd, ms) : tx(d, cmd, buf, count, ms));
178}
179
180static int
181rx_frame(fido_dev_t *d, struct frame *fp, int *ms)
182{
183	struct timespec ts;
184	int n;
185
186	memset(fp, 0, sizeof(*fp));
187
188	if (fido_time_now(&ts) != 0)
189		return (-1);
190
191	if (d->rx_len > sizeof(*fp) || (n = d->io.read(d->io_handle,
192	    (unsigned char *)fp, d->rx_len, *ms)) < 0 || (size_t)n != d->rx_len)
193		return (-1);
194
195	return (fido_time_delta(&ts, ms));
196}
197
198static int
199rx_preamble(fido_dev_t *d, uint8_t cmd, struct frame *fp, int *ms)
200{
201	do {
202		if (rx_frame(d, fp, ms) < 0)
203			return (-1);
204#ifdef FIDO_FUZZ
205		fp->cid = d->cid;
206#endif
207	} while (fp->cid != d->cid || (fp->cid == d->cid &&
208	    fp->body.init.cmd == (CTAP_FRAME_INIT | CTAP_KEEPALIVE)));
209
210	if (d->rx_len > sizeof(*fp))
211		return (-1);
212
213	fido_log_xxd(fp, d->rx_len, "%s", __func__);
214#ifdef FIDO_FUZZ
215	fp->body.init.cmd = (CTAP_FRAME_INIT | cmd);
216#endif
217
218	if (fp->cid != d->cid || fp->body.init.cmd != (CTAP_FRAME_INIT | cmd)) {
219		fido_log_debug("%s: cid (0x%x, 0x%x), cmd (0x%02x, 0x%02x)",
220		    __func__, fp->cid, d->cid, fp->body.init.cmd, cmd);
221		return (-1);
222	}
223
224	return (0);
225}
226
227static int
228rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int *ms)
229{
230	struct frame f;
231	size_t r, payload_len, init_data_len, cont_data_len;
232
233	if (d->rx_len <= CTAP_INIT_HEADER_LEN ||
234	    d->rx_len <= CTAP_CONT_HEADER_LEN)
235		return (-1);
236
237	init_data_len = d->rx_len - CTAP_INIT_HEADER_LEN;
238	cont_data_len = d->rx_len - CTAP_CONT_HEADER_LEN;
239
240	if (init_data_len > sizeof(f.body.init.data) ||
241	    cont_data_len > sizeof(f.body.cont.data))
242		return (-1);
243
244	if (rx_preamble(d, cmd, &f, ms) < 0) {
245		fido_log_debug("%s: rx_preamble", __func__);
246		return (-1);
247	}
248
249	payload_len = (size_t)((f.body.init.bcnth << 8) | f.body.init.bcntl);
250	fido_log_debug("%s: payload_len=%zu", __func__, payload_len);
251
252	if (count < payload_len) {
253		fido_log_debug("%s: count < payload_len", __func__);
254		return (-1);
255	}
256
257	if (payload_len < init_data_len) {
258		memcpy(buf, f.body.init.data, payload_len);
259		return ((int)payload_len);
260	}
261
262	memcpy(buf, f.body.init.data, init_data_len);
263	r = init_data_len;
264
265	for (int seq = 0; r < payload_len; seq++) {
266		if (rx_frame(d, &f, ms) < 0) {
267			fido_log_debug("%s: rx_frame", __func__);
268			return (-1);
269		}
270
271		fido_log_xxd(&f, d->rx_len, "%s", __func__);
272#ifdef FIDO_FUZZ
273		f.cid = d->cid;
274		f.body.cont.seq = (uint8_t)seq;
275#endif
276
277		if (f.cid != d->cid || f.body.cont.seq != seq) {
278			fido_log_debug("%s: cid (0x%x, 0x%x), seq (%d, %d)",
279			    __func__, f.cid, d->cid, f.body.cont.seq, seq);
280			return (-1);
281		}
282
283		if (payload_len - r > cont_data_len) {
284			memcpy(buf + r, f.body.cont.data, cont_data_len);
285			r += cont_data_len;
286		} else {
287			memcpy(buf + r, f.body.cont.data, payload_len - r);
288			r += payload_len - r; /* break */
289		}
290	}
291
292	return ((int)r);
293}
294
295static int
296transport_rx(fido_dev_t *d, uint8_t cmd, void *buf, size_t count, int *ms)
297{
298	struct timespec ts;
299	int n;
300
301	if (fido_time_now(&ts) != 0)
302		return (-1);
303
304	n = d->transport.rx(d, cmd, buf, count, *ms);
305
306	if (fido_time_delta(&ts, ms) != 0)
307		return (-1);
308
309	return (n);
310}
311
312int
313fido_rx(fido_dev_t *d, uint8_t cmd, void *buf, size_t count, int *ms)
314{
315	int n;
316
317	fido_log_debug("%s: dev=%p, cmd=0x%02x, ms=%d", __func__, (void *)d,
318	    cmd, *ms);
319
320	if (d->transport.rx != NULL)
321		return (transport_rx(d, cmd, buf, count, ms));
322	if (d->io_handle == NULL || d->io.read == NULL || count > UINT16_MAX) {
323		fido_log_debug("%s: invalid argument", __func__);
324		return (-1);
325	}
326	if ((n = rx(d, cmd, buf, count, ms)) >= 0)
327		fido_log_xxd(buf, (size_t)n, "%s", __func__);
328
329	return (n);
330}
331
332int
333fido_rx_cbor_status(fido_dev_t *d, int *ms)
334{
335	unsigned char	*msg;
336	int		 msglen;
337	int		 r;
338
339	if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
340		r = FIDO_ERR_INTERNAL;
341		goto out;
342	}
343
344	if ((msglen = fido_rx(d, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0 ||
345	    (size_t)msglen < 1) {
346		fido_log_debug("%s: fido_rx", __func__);
347		r = FIDO_ERR_RX;
348		goto out;
349	}
350
351	r = msg[0];
352out:
353	freezero(msg, FIDO_MAXMSG);
354
355	return (r);
356}
357