io.c revision 1.1.1.4
1/*
2 * Copyright (c) 2018 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 */
6
7#include "fido.h"
8#include "packed.h"
9
10PACKED_TYPE(frame_t,
11struct frame {
12	uint32_t cid; /* channel id */
13	union {
14		uint8_t type;
15		struct {
16			uint8_t cmd;
17			uint8_t bcnth;
18			uint8_t bcntl;
19			uint8_t data[CTAP_MAX_REPORT_LEN - CTAP_INIT_HEADER_LEN];
20		} init;
21		struct {
22			uint8_t seq;
23			uint8_t data[CTAP_MAX_REPORT_LEN - CTAP_CONT_HEADER_LEN];
24		} cont;
25	} body;
26})
27
28#ifndef MIN
29#define MIN(x, y) ((x) > (y) ? (y) : (x))
30#endif
31
32static int
33tx_empty(fido_dev_t *d, uint8_t cmd)
34{
35	struct frame	*fp;
36	unsigned char	 pkt[sizeof(*fp) + 1];
37	const size_t	 len = d->tx_len + 1;
38	int		 n;
39
40	memset(&pkt, 0, sizeof(pkt));
41	fp = (struct frame *)(pkt + 1);
42	fp->cid = d->cid;
43	fp->body.init.cmd = CTAP_FRAME_INIT | cmd;
44
45	if (len > sizeof(pkt) || (n = d->io.write(d->io_handle, pkt,
46	    len)) < 0 || (size_t)n != len)
47		return (-1);
48
49	return (0);
50}
51
52static size_t
53tx_preamble(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count)
54{
55	struct frame	*fp;
56	unsigned char	 pkt[sizeof(*fp) + 1];
57	const size_t	 len = d->tx_len + 1;
58	int		 n;
59
60	if (d->tx_len - CTAP_INIT_HEADER_LEN > sizeof(fp->body.init.data))
61		return (0);
62
63	memset(&pkt, 0, sizeof(pkt));
64	fp = (struct frame *)(pkt + 1);
65	fp->cid = d->cid;
66	fp->body.init.cmd = CTAP_FRAME_INIT | cmd;
67	fp->body.init.bcnth = (count >> 8) & 0xff;
68	fp->body.init.bcntl = count & 0xff;
69	count = MIN(count, d->tx_len - CTAP_INIT_HEADER_LEN);
70	memcpy(&fp->body.init.data, buf, count);
71
72	if (len > sizeof(pkt) || (n = d->io.write(d->io_handle, pkt,
73	    len)) < 0 || (size_t)n != len)
74		return (0);
75
76	return (count);
77}
78
79static size_t
80tx_frame(fido_dev_t *d, uint8_t seq, const void *buf, size_t count)
81{
82	struct frame	*fp;
83	unsigned char	 pkt[sizeof(*fp) + 1];
84	const size_t	 len = d->tx_len + 1;
85	int		 n;
86
87	if (d->tx_len - CTAP_CONT_HEADER_LEN > sizeof(fp->body.cont.data))
88		return (0);
89
90	memset(&pkt, 0, sizeof(pkt));
91	fp = (struct frame *)(pkt + 1);
92	fp->cid = d->cid;
93	fp->body.cont.seq = seq;
94	count = MIN(count, d->tx_len - CTAP_CONT_HEADER_LEN);
95	memcpy(&fp->body.cont.data, buf, count);
96
97	if (len > sizeof(pkt) || (n = d->io.write(d->io_handle, pkt,
98	    len)) < 0 || (size_t)n != len)
99		return (0);
100
101	return (count);
102}
103
104static int
105tx(fido_dev_t *d, uint8_t cmd, const unsigned char *buf, size_t count)
106{
107	size_t n, sent;
108
109	if ((sent = tx_preamble(d, cmd, buf, count)) == 0) {
110		fido_log_debug("%s: tx_preamble", __func__);
111		return (-1);
112	}
113
114	for (uint8_t seq = 0; sent < count; sent += n) {
115		if (seq & 0x80) {
116			fido_log_debug("%s: seq & 0x80", __func__);
117			return (-1);
118		}
119		if ((n = tx_frame(d, seq++, buf + sent, count - sent)) == 0) {
120			fido_log_debug("%s: tx_frame", __func__);
121			return (-1);
122		}
123	}
124
125	return (0);
126}
127
128int
129fido_tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count)
130{
131	fido_log_debug("%s: dev=%p, cmd=0x%02x", __func__, (void *)d, cmd);
132	fido_log_xxd(buf, count, "%s", __func__);
133
134	if (d->transport.tx != NULL)
135		return (d->transport.tx(d, cmd, buf, count));
136	if (d->io_handle == NULL || d->io.write == NULL || count > UINT16_MAX) {
137		fido_log_debug("%s: invalid argument", __func__);
138		return (-1);
139	}
140
141	return (count == 0 ? tx_empty(d, cmd) : tx(d, cmd, buf, count));
142}
143
144static int
145rx_frame(fido_dev_t *d, struct frame *fp, int ms)
146{
147	int n;
148
149	memset(fp, 0, sizeof(*fp));
150
151	if (d->rx_len > sizeof(*fp) || (n = d->io.read(d->io_handle,
152	    (unsigned char *)fp, d->rx_len, ms)) < 0 || (size_t)n != d->rx_len)
153		return (-1);
154
155	return (0);
156}
157
158static int
159rx_preamble(fido_dev_t *d, uint8_t cmd, struct frame *fp, int ms)
160{
161	do {
162		if (rx_frame(d, fp, ms) < 0)
163			return (-1);
164#ifdef FIDO_FUZZ
165		fp->cid = d->cid;
166#endif
167	} while (fp->cid != d->cid || (fp->cid == d->cid &&
168	    fp->body.init.cmd == (CTAP_FRAME_INIT | CTAP_KEEPALIVE)));
169
170	if (d->rx_len > sizeof(*fp))
171		return (-1);
172
173	fido_log_xxd(fp, d->rx_len, "%s", __func__);
174#ifdef FIDO_FUZZ
175	fp->body.init.cmd = (CTAP_FRAME_INIT | cmd);
176#endif
177
178	if (fp->cid != d->cid || fp->body.init.cmd != (CTAP_FRAME_INIT | cmd)) {
179		fido_log_debug("%s: cid (0x%x, 0x%x), cmd (0x%02x, 0x%02x)",
180		    __func__, fp->cid, d->cid, fp->body.init.cmd, cmd);
181		return (-1);
182	}
183
184	return (0);
185}
186
187static int
188rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int ms)
189{
190	struct frame f;
191	size_t r, payload_len, init_data_len, cont_data_len;
192
193	if (d->rx_len <= CTAP_INIT_HEADER_LEN ||
194	    d->rx_len <= CTAP_CONT_HEADER_LEN)
195		return (-1);
196
197	init_data_len = d->rx_len - CTAP_INIT_HEADER_LEN;
198	cont_data_len = d->rx_len - CTAP_CONT_HEADER_LEN;
199
200	if (init_data_len > sizeof(f.body.init.data) ||
201	    cont_data_len > sizeof(f.body.cont.data))
202		return (-1);
203
204	if (rx_preamble(d, cmd, &f, ms) < 0) {
205		fido_log_debug("%s: rx_preamble", __func__);
206		return (-1);
207	}
208
209	payload_len = (size_t)((f.body.init.bcnth << 8) | f.body.init.bcntl);
210	fido_log_debug("%s: payload_len=%zu", __func__, payload_len);
211
212	if (count < payload_len) {
213		fido_log_debug("%s: count < payload_len", __func__);
214		return (-1);
215	}
216
217	if (payload_len < init_data_len) {
218		memcpy(buf, f.body.init.data, payload_len);
219		return ((int)payload_len);
220	}
221
222	memcpy(buf, f.body.init.data, init_data_len);
223	r = init_data_len;
224
225	for (int seq = 0; r < payload_len; seq++) {
226		if (rx_frame(d, &f, ms) < 0) {
227			fido_log_debug("%s: rx_frame", __func__);
228			return (-1);
229		}
230
231		fido_log_xxd(&f, d->rx_len, "%s", __func__);
232#ifdef FIDO_FUZZ
233		f.cid = d->cid;
234		f.body.cont.seq = (uint8_t)seq;
235#endif
236
237		if (f.cid != d->cid || f.body.cont.seq != seq) {
238			fido_log_debug("%s: cid (0x%x, 0x%x), seq (%d, %d)",
239			    __func__, f.cid, d->cid, f.body.cont.seq, seq);
240			return (-1);
241		}
242
243		if (payload_len - r > cont_data_len) {
244			memcpy(buf + r, f.body.cont.data, cont_data_len);
245			r += cont_data_len;
246		} else {
247			memcpy(buf + r, f.body.cont.data, payload_len - r);
248			r += payload_len - r; /* break */
249		}
250	}
251
252	return ((int)r);
253}
254
255int
256fido_rx(fido_dev_t *d, uint8_t cmd, void *buf, size_t count, int ms)
257{
258	int n;
259
260	fido_log_debug("%s: dev=%p, cmd=0x%02x, ms=%d", __func__, (void *)d,
261	    cmd, ms);
262
263	if (d->transport.rx != NULL)
264		return (d->transport.rx(d, cmd, buf, count, ms));
265	if (d->io_handle == NULL || d->io.read == NULL || count > UINT16_MAX) {
266		fido_log_debug("%s: invalid argument", __func__);
267		return (-1);
268	}
269	if ((n = rx(d, cmd, buf, count, ms)) >= 0)
270		fido_log_xxd(buf, (size_t)n, "%s", __func__);
271
272	return (n);
273}
274
275int
276fido_rx_cbor_status(fido_dev_t *d, int ms)
277{
278	unsigned char	reply[FIDO_MAXMSG];
279	int		reply_len;
280
281	if ((reply_len = fido_rx(d, CTAP_CMD_CBOR, &reply, sizeof(reply),
282	    ms)) < 0 || (size_t)reply_len < 1) {
283		fido_log_debug("%s: fido_rx", __func__);
284		return (FIDO_ERR_RX);
285	}
286
287	return (reply[0]);
288}
289