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