diff options
Diffstat (limited to 'src/io.c')
-rw-r--r-- | src/io.c | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/src/io.c b/src/io.c new file mode 100644 index 0000000..aa88720 --- /dev/null +++ b/src/io.c | |||
@@ -0,0 +1,256 @@ | |||
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 | |||
14 | PACKED_TYPE(frame_t, | ||
15 | struct 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_RPT_SIZE - 7]; | ||
24 | } init; | ||
25 | struct { | ||
26 | uint8_t seq; | ||
27 | uint8_t data[CTAP_RPT_SIZE - 5]; | ||
28 | } cont; | ||
29 | } body; | ||
30 | }) | ||
31 | |||
32 | #ifndef MIN | ||
33 | #define MIN(x, y) ((x) > (y) ? (y) : (x)) | ||
34 | #endif | ||
35 | |||
36 | static size_t | ||
37 | tx_preamble(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count) | ||
38 | { | ||
39 | struct frame *fp; | ||
40 | unsigned char pkt[sizeof(*fp) + 1]; | ||
41 | int n; | ||
42 | |||
43 | if (d->io.write == NULL || (cmd & 0x80) == 0) | ||
44 | return (0); | ||
45 | |||
46 | memset(&pkt, 0, sizeof(pkt)); | ||
47 | fp = (struct frame *)(pkt + 1); | ||
48 | fp->cid = d->cid; | ||
49 | fp->body.init.cmd = 0x80 | cmd; | ||
50 | fp->body.init.bcnth = (count >> 8) & 0xff; | ||
51 | fp->body.init.bcntl = count & 0xff; | ||
52 | count = MIN(count, sizeof(fp->body.init.data)); | ||
53 | if (count) | ||
54 | memcpy(&fp->body.init.data, buf, count); | ||
55 | |||
56 | n = d->io.write(d->io_handle, pkt, sizeof(pkt)); | ||
57 | if (n < 0 || (size_t)n != sizeof(pkt)) | ||
58 | return (0); | ||
59 | |||
60 | return (count); | ||
61 | } | ||
62 | |||
63 | static size_t | ||
64 | tx_frame(fido_dev_t *d, int seq, const void *buf, size_t count) | ||
65 | { | ||
66 | struct frame *fp; | ||
67 | unsigned char pkt[sizeof(*fp) + 1]; | ||
68 | int n; | ||
69 | |||
70 | if (d->io.write == NULL || seq < 0 || seq > UINT8_MAX) | ||
71 | return (0); | ||
72 | |||
73 | memset(&pkt, 0, sizeof(pkt)); | ||
74 | fp = (struct frame *)(pkt + 1); | ||
75 | fp->cid = d->cid; | ||
76 | fp->body.cont.seq = (uint8_t)seq; | ||
77 | count = MIN(count, sizeof(fp->body.cont.data)); | ||
78 | memcpy(&fp->body.cont.data, buf, count); | ||
79 | |||
80 | n = d->io.write(d->io_handle, pkt, sizeof(pkt)); | ||
81 | if (n < 0 || (size_t)n != sizeof(pkt)) | ||
82 | return (0); | ||
83 | |||
84 | return (count); | ||
85 | } | ||
86 | |||
87 | int | ||
88 | fido_tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count) | ||
89 | { | ||
90 | int seq = 0; | ||
91 | size_t sent; | ||
92 | |||
93 | fido_log_debug("%s: d=%p, cmd=0x%02x, buf=%p, count=%zu", __func__, | ||
94 | (void *)d, cmd, buf, count); | ||
95 | fido_log_xxd(buf, count); | ||
96 | |||
97 | if (d->io_handle == NULL || count > UINT16_MAX) { | ||
98 | fido_log_debug("%s: invalid argument (%p, %zu)", __func__, | ||
99 | d->io_handle, count); | ||
100 | return (-1); | ||
101 | } | ||
102 | |||
103 | if ((sent = tx_preamble(d, cmd, buf, count)) == 0) { | ||
104 | fido_log_debug("%s: tx_preamble", __func__); | ||
105 | return (-1); | ||
106 | } | ||
107 | |||
108 | while (sent < count) { | ||
109 | if (seq & 0x80) { | ||
110 | fido_log_debug("%s: seq & 0x80", __func__); | ||
111 | return (-1); | ||
112 | } | ||
113 | const uint8_t *p = (const uint8_t *)buf + sent; | ||
114 | size_t n = tx_frame(d, seq++, p, count - sent); | ||
115 | if (n == 0) { | ||
116 | fido_log_debug("%s: tx_frame", __func__); | ||
117 | return (-1); | ||
118 | } | ||
119 | sent += n; | ||
120 | } | ||
121 | |||
122 | return (0); | ||
123 | } | ||
124 | |||
125 | static int | ||
126 | rx_frame(fido_dev_t *d, struct frame *fp, int ms) | ||
127 | { | ||
128 | int n; | ||
129 | |||
130 | if (d->io.read == NULL) | ||
131 | return (-1); | ||
132 | |||
133 | n = d->io.read(d->io_handle, (unsigned char *)fp, sizeof(*fp), ms); | ||
134 | if (n < 0 || (size_t)n != sizeof(*fp)) | ||
135 | return (-1); | ||
136 | |||
137 | return (0); | ||
138 | } | ||
139 | |||
140 | static int | ||
141 | rx_preamble(fido_dev_t *d, struct frame *fp, int ms) | ||
142 | { | ||
143 | do { | ||
144 | if (rx_frame(d, fp, ms) < 0) | ||
145 | return (-1); | ||
146 | #ifdef FIDO_FUZZ | ||
147 | fp->cid = d->cid; | ||
148 | #endif | ||
149 | } while (fp->cid == d->cid && | ||
150 | fp->body.init.cmd == (CTAP_FRAME_INIT | CTAP_KEEPALIVE)); | ||
151 | |||
152 | return (0); | ||
153 | } | ||
154 | |||
155 | int | ||
156 | fido_rx(fido_dev_t *d, uint8_t cmd, void *buf, size_t count, int ms) | ||
157 | { | ||
158 | struct frame f; | ||
159 | uint16_t r; | ||
160 | uint16_t flen; | ||
161 | int seq; | ||
162 | |||
163 | if (d->io_handle == NULL || (cmd & 0x80) == 0) { | ||
164 | fido_log_debug("%s: invalid argument (%p, 0x%02x)", __func__, | ||
165 | d->io_handle, cmd); | ||
166 | return (-1); | ||
167 | } | ||
168 | |||
169 | if (rx_preamble(d, &f, ms) < 0) { | ||
170 | fido_log_debug("%s: rx_preamble", __func__); | ||
171 | return (-1); | ||
172 | } | ||
173 | |||
174 | fido_log_debug("%s: initiation frame at %p, len %zu", __func__, | ||
175 | (void *)&f, sizeof(f)); | ||
176 | fido_log_xxd(&f, sizeof(f)); | ||
177 | |||
178 | #ifdef FIDO_FUZZ | ||
179 | f.cid = d->cid; | ||
180 | f.body.init.cmd = cmd; | ||
181 | #endif | ||
182 | |||
183 | if (f.cid != d->cid || f.body.init.cmd != cmd) { | ||
184 | fido_log_debug("%s: cid (0x%x, 0x%x), cmd (0x%02x, 0x%02x)", | ||
185 | __func__, f.cid, d->cid, f.body.init.cmd, cmd); | ||
186 | return (-1); | ||
187 | } | ||
188 | |||
189 | flen = (f.body.init.bcnth << 8) | f.body.init.bcntl; | ||
190 | if (count < (size_t)flen) { | ||
191 | fido_log_debug("%s: count < flen (%zu, %zu)", __func__, count, | ||
192 | (size_t)flen); | ||
193 | return (-1); | ||
194 | } | ||
195 | if (flen < sizeof(f.body.init.data)) { | ||
196 | memcpy(buf, f.body.init.data, flen); | ||
197 | return (flen); | ||
198 | } | ||
199 | |||
200 | memcpy(buf, f.body.init.data, sizeof(f.body.init.data)); | ||
201 | r = sizeof(f.body.init.data); | ||
202 | seq = 0; | ||
203 | |||
204 | while ((size_t)r < flen) { | ||
205 | if (rx_frame(d, &f, ms) < 0) { | ||
206 | fido_log_debug("%s: rx_frame", __func__); | ||
207 | return (-1); | ||
208 | } | ||
209 | |||
210 | fido_log_debug("%s: continuation frame at %p, len %zu", | ||
211 | __func__, (void *)&f, sizeof(f)); | ||
212 | fido_log_xxd(&f, sizeof(f)); | ||
213 | |||
214 | #ifdef FIDO_FUZZ | ||
215 | f.cid = d->cid; | ||
216 | f.body.cont.seq = seq; | ||
217 | #endif | ||
218 | |||
219 | if (f.cid != d->cid || f.body.cont.seq != seq++) { | ||
220 | fido_log_debug("%s: cid (0x%x, 0x%x), seq (%d, %d)", | ||
221 | __func__, f.cid, d->cid, f.body.cont.seq, seq); | ||
222 | return (-1); | ||
223 | } | ||
224 | |||
225 | uint8_t *p = (uint8_t *)buf + r; | ||
226 | |||
227 | if ((size_t)(flen - r) > sizeof(f.body.cont.data)) { | ||
228 | memcpy(p, f.body.cont.data, sizeof(f.body.cont.data)); | ||
229 | r += sizeof(f.body.cont.data); | ||
230 | } else { | ||
231 | memcpy(p, f.body.cont.data, flen - r); | ||
232 | r += (flen - r); /* break */ | ||
233 | } | ||
234 | } | ||
235 | |||
236 | fido_log_debug("%s: payload at %p, len %zu", __func__, buf, (size_t)r); | ||
237 | fido_log_xxd(buf, r); | ||
238 | |||
239 | return (r); | ||
240 | } | ||
241 | |||
242 | int | ||
243 | fido_rx_cbor_status(fido_dev_t *d, int ms) | ||
244 | { | ||
245 | const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR; | ||
246 | unsigned char reply[2048]; | ||
247 | int reply_len; | ||
248 | |||
249 | if ((reply_len = fido_rx(d, cmd, &reply, sizeof(reply), ms)) < 0 || | ||
250 | (size_t)reply_len < 1) { | ||
251 | fido_log_debug("%s: fido_rx", __func__); | ||
252 | return (FIDO_ERR_RX); | ||
253 | } | ||
254 | |||
255 | return (reply[0]); | ||
256 | } | ||