diff options
author | tb <> | 2024-12-26 00:04:24 +0000 |
---|---|---|
committer | tb <> | 2024-12-26 00:04:24 +0000 |
commit | 31d1b04da9af806cdb66a2b49ed6490e67479eef (patch) | |
tree | f187d226245651988501e2fb8891081ff9eea9f2 /src/regress/lib/libcrypto/mlkem/parse_test_file.c | |
parent | fe8b80dbfd7a71d866da84cfdab5d2ce23feac28 (diff) | |
download | openbsd-31d1b04da9af806cdb66a2b49ed6490e67479eef.tar.gz openbsd-31d1b04da9af806cdb66a2b49ed6490e67479eef.tar.bz2 openbsd-31d1b04da9af806cdb66a2b49ed6490e67479eef.zip |
Overhaul ML-KEM regress once more
Implement a file parser that drives a state machine to extract the test data
from the .txt files and manages the parsed data. Comments and empty lines are
ignored. The code currently assumes that instruction lines are at the start
of the file (which isn't generally true) and only supports two line types for
now. This is good enough for all the ML-KEM tests but should be easy enough
to extend.
Once all data for a test case is parsed in the expected order, a test handler
is called which can retrieve the test data via a simple API and throw warnings
and errors with information on the test case line number, etc.
Merge the tests into three programs: one parsing the .txt files and running
the corresponding test cases, a unit test and the iteration tests. Deduplicate
the actual test code and let the caller pass in an object containing the
API functions, private keys and arrays that need to be different between the
768 version and the 1024 version. This way we don't have two sets of half a
dozen .c files differing only in 3 or 4 occurrences of 768 and 1024.
All this will also make it a lot easier to hook these tests into portable.
Diffstat (limited to 'src/regress/lib/libcrypto/mlkem/parse_test_file.c')
-rw-r--r-- | src/regress/lib/libcrypto/mlkem/parse_test_file.c | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/src/regress/lib/libcrypto/mlkem/parse_test_file.c b/src/regress/lib/libcrypto/mlkem/parse_test_file.c new file mode 100644 index 0000000000..d79fa3dfd5 --- /dev/null +++ b/src/regress/lib/libcrypto/mlkem/parse_test_file.c | |||
@@ -0,0 +1,752 @@ | |||
1 | /* $OpenBSD: parse_test_file.c,v 1.1 2024/12/26 00:04:24 tb Exp $ */ | ||
2 | |||
3 | /* | ||
4 | * Copyright (c) 2024 Theo Buehler <tb@openbsd.org> | ||
5 | * | ||
6 | * Permission to use, copy, modify, and distribute this software for any | ||
7 | * purpose with or without fee is hereby granted, provided that the above | ||
8 | * copyright notice and this permission notice appear in all copies. | ||
9 | * | ||
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
17 | */ | ||
18 | |||
19 | #include <sys/types.h> | ||
20 | |||
21 | #include <assert.h> | ||
22 | #include <err.h> | ||
23 | #include <stdarg.h> | ||
24 | #include <stdint.h> | ||
25 | #include <stdio.h> | ||
26 | #include <stdlib.h> | ||
27 | #include <string.h> | ||
28 | |||
29 | #include "bytestring.h" | ||
30 | |||
31 | #include "parse_test_file.h" | ||
32 | |||
33 | struct line_data { | ||
34 | uint8_t *data; | ||
35 | size_t data_len; | ||
36 | CBS cbs; | ||
37 | int val; | ||
38 | }; | ||
39 | |||
40 | static struct line_data * | ||
41 | line_data_new(void) | ||
42 | { | ||
43 | return calloc(1, sizeof(struct line_data)); | ||
44 | } | ||
45 | |||
46 | static void | ||
47 | line_data_clear(struct line_data *ld) | ||
48 | { | ||
49 | freezero(ld->data, ld->data_len); | ||
50 | explicit_bzero(ld, sizeof(*ld)); | ||
51 | } | ||
52 | |||
53 | static void | ||
54 | line_data_free(struct line_data *ld) | ||
55 | { | ||
56 | if (ld == NULL) | ||
57 | return; | ||
58 | line_data_clear(ld); | ||
59 | free(ld); | ||
60 | } | ||
61 | |||
62 | static void | ||
63 | line_data_get_int(struct line_data *ld, int *out) | ||
64 | { | ||
65 | *out = ld->val; | ||
66 | } | ||
67 | |||
68 | static void | ||
69 | line_data_get_cbs(struct line_data *ld, CBS *out) | ||
70 | { | ||
71 | CBS_dup(&ld->cbs, out); | ||
72 | } | ||
73 | |||
74 | static void | ||
75 | line_data_set_int(struct line_data *ld, int val) | ||
76 | { | ||
77 | ld->val = val; | ||
78 | } | ||
79 | |||
80 | static int | ||
81 | line_data_set_from_cbb(struct line_data *ld, CBB *cbb) | ||
82 | { | ||
83 | if (!CBB_finish(cbb, &ld->data, &ld->data_len)) | ||
84 | return 0; | ||
85 | |||
86 | CBS_init(&ld->cbs, ld->data, ld->data_len); | ||
87 | |||
88 | return 1; | ||
89 | } | ||
90 | |||
91 | struct parse_state { | ||
92 | size_t line; | ||
93 | size_t test; | ||
94 | |||
95 | size_t max; | ||
96 | size_t cur; | ||
97 | struct line_data **data; | ||
98 | |||
99 | size_t instruction_max; | ||
100 | size_t instruction_cur; | ||
101 | struct line_data **instruction_data; | ||
102 | |||
103 | int running_test_case; | ||
104 | }; | ||
105 | |||
106 | static void | ||
107 | parse_state_init(struct parse_state *ps, size_t max, size_t instruction_max) | ||
108 | { | ||
109 | size_t i; | ||
110 | |||
111 | assert(max > 0); | ||
112 | |||
113 | memset(ps, 0, sizeof(*ps)); | ||
114 | ps->test = 1; | ||
115 | |||
116 | ps->max = max; | ||
117 | if ((ps->data = calloc(max, sizeof(*ps->data))) == NULL) | ||
118 | err(1, NULL); | ||
119 | for (i = 0; i < max; i++) { | ||
120 | if ((ps->data[i] = line_data_new()) == NULL) | ||
121 | err(1, NULL); | ||
122 | } | ||
123 | |||
124 | if ((ps->instruction_max = instruction_max) > 0) { | ||
125 | if ((ps->instruction_data = calloc(instruction_max, | ||
126 | sizeof(*ps->instruction_data))) == NULL) | ||
127 | err(1, NULL); | ||
128 | for (i = 0; i < instruction_max; i++) | ||
129 | if ((ps->instruction_data[i] = line_data_new()) == NULL) | ||
130 | err(1, NULL); | ||
131 | } | ||
132 | } | ||
133 | |||
134 | static void | ||
135 | parse_state_finish(struct parse_state *ps) | ||
136 | { | ||
137 | size_t i; | ||
138 | |||
139 | for (i = 0; i < ps->max; i++) | ||
140 | line_data_free(ps->data[i]); | ||
141 | free(ps->data); | ||
142 | |||
143 | for (i = 0; i < ps->instruction_max; i++) | ||
144 | line_data_free(ps->instruction_data[i]); | ||
145 | free(ps->instruction_data); | ||
146 | } | ||
147 | |||
148 | static void | ||
149 | parse_state_new_line(struct parse_state *ps) | ||
150 | { | ||
151 | ps->line++; | ||
152 | } | ||
153 | |||
154 | static void | ||
155 | parse_instruction_advance(struct parse_state *ps) | ||
156 | { | ||
157 | assert(ps->instruction_cur < ps->instruction_max); | ||
158 | ps->instruction_cur++; | ||
159 | } | ||
160 | |||
161 | static void | ||
162 | parse_state_advance(struct parse_state *ps) | ||
163 | { | ||
164 | assert(ps->cur < ps->max); | ||
165 | |||
166 | ps->cur++; | ||
167 | if ((ps->cur %= ps->max) == 0) | ||
168 | ps->test++; | ||
169 | } | ||
170 | |||
171 | struct parse { | ||
172 | struct parse_state state; | ||
173 | CBS cbs; | ||
174 | char *buf; | ||
175 | size_t buf_max; | ||
176 | const struct test_parse *tctx; | ||
177 | void *ctx; | ||
178 | |||
179 | const char *fn; | ||
180 | FILE *fp; | ||
181 | }; | ||
182 | |||
183 | static int | ||
184 | parse_instructions_parsed(struct parse *p) | ||
185 | { | ||
186 | return p->state.instruction_max == p->state.instruction_cur; | ||
187 | } | ||
188 | |||
189 | static void | ||
190 | parse_advance(struct parse *p) | ||
191 | { | ||
192 | if (!parse_instructions_parsed(p)) { | ||
193 | parse_instruction_advance(&p->state); | ||
194 | return; | ||
195 | } | ||
196 | parse_state_advance(&p->state); | ||
197 | } | ||
198 | |||
199 | static size_t | ||
200 | parse_max(struct parse *p) | ||
201 | { | ||
202 | return p->state.max; | ||
203 | } | ||
204 | |||
205 | static size_t | ||
206 | parse_instruction_max(struct parse *p) | ||
207 | { | ||
208 | return p->state.instruction_max; | ||
209 | } | ||
210 | |||
211 | static size_t | ||
212 | parse_cur(struct parse *p) | ||
213 | { | ||
214 | if (!parse_instructions_parsed(p)) { | ||
215 | assert(p->state.instruction_cur < p->state.instruction_max); | ||
216 | return p->state.instruction_cur; | ||
217 | } | ||
218 | |||
219 | assert(p->state.cur < parse_max(p)); | ||
220 | return p->state.cur; | ||
221 | } | ||
222 | |||
223 | static size_t | ||
224 | parse_must_run_test_case(struct parse *p) | ||
225 | { | ||
226 | return parse_instructions_parsed(p) && parse_max(p) - parse_cur(p) == 1; | ||
227 | } | ||
228 | |||
229 | static const struct line_spec * | ||
230 | parse_states(struct parse *p) | ||
231 | { | ||
232 | if (!parse_instructions_parsed(p)) | ||
233 | return p->tctx->instructions; | ||
234 | return p->tctx->states; | ||
235 | } | ||
236 | |||
237 | static const struct line_spec * | ||
238 | parse_instruction_states(struct parse *p) | ||
239 | { | ||
240 | return p->tctx->instructions; | ||
241 | } | ||
242 | |||
243 | static const struct line_spec * | ||
244 | parse_state(struct parse *p) | ||
245 | { | ||
246 | return &parse_states(p)[parse_cur(p)]; | ||
247 | } | ||
248 | |||
249 | static size_t | ||
250 | line(struct parse *p) | ||
251 | { | ||
252 | return p->state.line; | ||
253 | } | ||
254 | |||
255 | static size_t | ||
256 | test(struct parse *p) | ||
257 | { | ||
258 | return p->state.test; | ||
259 | } | ||
260 | |||
261 | static const char * | ||
262 | name(struct parse *p) | ||
263 | { | ||
264 | if (p->state.running_test_case) | ||
265 | return "running test case"; | ||
266 | return parse_state(p)->name; | ||
267 | } | ||
268 | |||
269 | static const char * | ||
270 | label(struct parse *p) | ||
271 | { | ||
272 | return parse_state(p)->label; | ||
273 | } | ||
274 | |||
275 | static const char * | ||
276 | match(struct parse *p) | ||
277 | { | ||
278 | return parse_state(p)->match; | ||
279 | } | ||
280 | |||
281 | static enum line | ||
282 | parse_line_type(struct parse *p) | ||
283 | { | ||
284 | return parse_state(p)->type; | ||
285 | } | ||
286 | |||
287 | static void | ||
288 | parse_vinfo(struct parse *p, const char *fmt, va_list ap) | ||
289 | { | ||
290 | fprintf(stderr, "%s:%zu test #%zu (%s): ", | ||
291 | p->fn, line(p), test(p), name(p)); | ||
292 | vfprintf(stderr, fmt, ap); | ||
293 | fprintf(stderr, "\n"); | ||
294 | } | ||
295 | |||
296 | void | ||
297 | parse_info(struct parse *p, const char *fmt, ...) | ||
298 | { | ||
299 | va_list ap; | ||
300 | |||
301 | va_start(ap, fmt); | ||
302 | parse_vinfo(p, fmt, ap); | ||
303 | va_end(ap); | ||
304 | } | ||
305 | |||
306 | void | ||
307 | parse_errx(struct parse *p, const char *fmt, ...) | ||
308 | { | ||
309 | va_list ap; | ||
310 | |||
311 | va_start(ap, fmt); | ||
312 | parse_vinfo(p, fmt, ap); | ||
313 | va_end(ap); | ||
314 | |||
315 | exit(1); | ||
316 | } | ||
317 | |||
318 | int | ||
319 | parse_length_equal(struct parse *p, const char *descr, size_t want, size_t got) | ||
320 | { | ||
321 | if (want == got) | ||
322 | return 1; | ||
323 | |||
324 | parse_info(p, "%s length: want %zu, got %zu", descr, want, got); | ||
325 | return 0; | ||
326 | } | ||
327 | |||
328 | static void | ||
329 | hexdump(const uint8_t *buf, size_t len, const uint8_t *compare) | ||
330 | { | ||
331 | const char *mark = "", *newline; | ||
332 | size_t i; | ||
333 | |||
334 | for (i = 1; i <= len; i++) { | ||
335 | if (compare != NULL) | ||
336 | mark = (buf[i - 1] != compare[i - 1]) ? "*" : " "; | ||
337 | newline = i % 8 ? "" : "\n"; | ||
338 | fprintf(stderr, " %s0x%02x,%s", mark, buf[i - 1], newline); | ||
339 | } | ||
340 | if ((len % 8) != 0) | ||
341 | fprintf(stderr, "\n"); | ||
342 | } | ||
343 | |||
344 | int | ||
345 | parse_data_equal(struct parse *p, const char *descr, CBS *want, | ||
346 | const uint8_t *got, size_t got_len) | ||
347 | { | ||
348 | if (!parse_length_equal(p, descr, CBS_len(want), got_len)) | ||
349 | return 0; | ||
350 | if (CBS_mem_equal(want, got, got_len)) | ||
351 | return 1; | ||
352 | |||
353 | parse_info(p, "%s differs", descr); | ||
354 | fprintf(stderr, "want:\n"); | ||
355 | hexdump(CBS_data(want), CBS_len(want), got); | ||
356 | fprintf(stderr, "got:\n"); | ||
357 | hexdump(got, got_len, CBS_data(want)); | ||
358 | fprintf(stderr, "\n"); | ||
359 | |||
360 | return 0; | ||
361 | } | ||
362 | |||
363 | static void | ||
364 | parse_line_data_clear(struct parse *p) | ||
365 | { | ||
366 | size_t i; | ||
367 | |||
368 | for (i = 0; i < parse_max(p); i++) | ||
369 | line_data_clear(p->state.data[i]); | ||
370 | } | ||
371 | |||
372 | static struct line_data ** | ||
373 | parse_state_data(struct parse *p) | ||
374 | { | ||
375 | if (!parse_instructions_parsed(p)) | ||
376 | return p->state.instruction_data; | ||
377 | return p->state.data; | ||
378 | } | ||
379 | |||
380 | static void | ||
381 | parse_state_set_int(struct parse *p, int val) | ||
382 | { | ||
383 | if (parse_line_type(p) != LINE_STRING_MATCH) | ||
384 | parse_errx(p, "%s: want %d, got %d", __func__, | ||
385 | LINE_STRING_MATCH, parse_line_type(p)); | ||
386 | line_data_set_int(parse_state_data(p)[parse_cur(p)], val); | ||
387 | } | ||
388 | |||
389 | static void | ||
390 | parse_state_set_from_cbb(struct parse *p, CBB *cbb) | ||
391 | { | ||
392 | if (parse_line_type(p) != LINE_HEX) | ||
393 | parse_errx(p, "%s: want %d, got %d", __func__, | ||
394 | LINE_STRING_MATCH, parse_line_type(p)); | ||
395 | if (!line_data_set_from_cbb(parse_state_data(p)[parse_cur(p)], cbb)) | ||
396 | parse_errx(p, "line_data_set_from_cbb"); | ||
397 | } | ||
398 | |||
399 | int | ||
400 | parse_get_int(struct parse *p, size_t idx, int *out) | ||
401 | { | ||
402 | assert(parse_must_run_test_case(p)); | ||
403 | assert(idx < parse_max(p)); | ||
404 | assert(parse_states(p)[idx].type == LINE_STRING_MATCH); | ||
405 | |||
406 | line_data_get_int(p->state.data[idx], out); | ||
407 | |||
408 | return 1; | ||
409 | } | ||
410 | |||
411 | int | ||
412 | parse_get_cbs(struct parse *p, size_t idx, CBS *out) | ||
413 | { | ||
414 | assert(parse_must_run_test_case(p)); | ||
415 | assert(idx < parse_max(p)); | ||
416 | assert(parse_states(p)[idx].type == LINE_HEX); | ||
417 | |||
418 | line_data_get_cbs(p->state.data[idx], out); | ||
419 | |||
420 | return 1; | ||
421 | } | ||
422 | |||
423 | int | ||
424 | parse_instruction_get_int(struct parse *p, size_t idx, int *out) | ||
425 | { | ||
426 | assert(parse_must_run_test_case(p)); | ||
427 | assert(idx < parse_instruction_max(p)); | ||
428 | assert(parse_instruction_states(p)[idx].type == LINE_STRING_MATCH); | ||
429 | |||
430 | line_data_get_int(p->state.instruction_data[idx], out); | ||
431 | |||
432 | return 1; | ||
433 | } | ||
434 | |||
435 | int | ||
436 | parse_instruction_get_cbs(struct parse *p, size_t idx, CBS *out) | ||
437 | { | ||
438 | assert(parse_must_run_test_case(p)); | ||
439 | assert(idx < parse_instruction_max(p)); | ||
440 | assert(parse_instruction_states(p)[idx].type == LINE_HEX); | ||
441 | |||
442 | line_data_get_cbs(p->state.instruction_data[idx], out); | ||
443 | |||
444 | return 1; | ||
445 | } | ||
446 | |||
447 | static int | ||
448 | CBS_peek_bytes(CBS *cbs, CBS *out, size_t len) | ||
449 | { | ||
450 | CBS dup; | ||
451 | |||
452 | CBS_dup(cbs, &dup); | ||
453 | return CBS_get_bytes(&dup, out, len); | ||
454 | } | ||
455 | |||
456 | static int | ||
457 | parse_peek_string_cbs(struct parse *p, const char *str) | ||
458 | { | ||
459 | CBS cbs; | ||
460 | size_t len = strlen(str); | ||
461 | |||
462 | if (!CBS_peek_bytes(&p->cbs, &cbs, len)) | ||
463 | parse_errx(p, "CBS_peek_data"); | ||
464 | |||
465 | return CBS_mem_equal(&cbs, (const uint8_t *)str, len); | ||
466 | } | ||
467 | |||
468 | static int | ||
469 | parse_get_string_cbs(struct parse *p, const char *str) | ||
470 | { | ||
471 | CBS cbs; | ||
472 | size_t len = strlen(str); | ||
473 | |||
474 | if (!CBS_get_bytes(&p->cbs, &cbs, len)) | ||
475 | parse_errx(p, "CBS_get_bytes"); | ||
476 | |||
477 | return CBS_mem_equal(&cbs, (const uint8_t *)str, len); | ||
478 | } | ||
479 | |||
480 | static int | ||
481 | parse_get_string_end_cbs(struct parse *p, const char *str) | ||
482 | { | ||
483 | CBS cbs; | ||
484 | int equal = 1; | ||
485 | |||
486 | CBS_init(&cbs, (const uint8_t *)str, strlen(str)); | ||
487 | |||
488 | if (CBS_len(&p->cbs) < CBS_len(&cbs)) | ||
489 | parse_errx(p, "line too short to match %s", str); | ||
490 | |||
491 | while (CBS_len(&cbs) > 0) { | ||
492 | uint8_t want, got; | ||
493 | |||
494 | if (!CBS_get_last_u8(&cbs, &want)) | ||
495 | parse_errx(p, "CBS_get_last_u8"); | ||
496 | if (!CBS_get_last_u8(&p->cbs, &got)) | ||
497 | parse_errx(p, "CBS_get_last_u8"); | ||
498 | if (want != got) | ||
499 | equal = 0; | ||
500 | } | ||
501 | |||
502 | return equal; | ||
503 | } | ||
504 | |||
505 | static void | ||
506 | parse_check_label_matches(struct parse *p) | ||
507 | { | ||
508 | const char *sep = ": "; | ||
509 | |||
510 | if (!parse_get_string_cbs(p, label(p))) | ||
511 | parse_errx(p, "label mismatch %s", label(p)); | ||
512 | |||
513 | /* Now we expect either ": " or " = ". */ | ||
514 | if (!parse_peek_string_cbs(p, sep)) | ||
515 | sep = " = "; | ||
516 | if (!parse_get_string_cbs(p, sep)) | ||
517 | parse_errx(p, "error getting \"%s\"", sep); | ||
518 | } | ||
519 | |||
520 | static int | ||
521 | parse_empty_or_comment_line(struct parse *p) | ||
522 | { | ||
523 | if (CBS_len(&p->cbs) == 0) { | ||
524 | return 1; | ||
525 | } | ||
526 | if (parse_peek_string_cbs(p, "#")) { | ||
527 | if (!CBS_skip(&p->cbs, CBS_len(&p->cbs))) | ||
528 | parse_errx(p, "CBS_skip"); | ||
529 | return 1; | ||
530 | } | ||
531 | return 0; | ||
532 | } | ||
533 | |||
534 | static void | ||
535 | parse_string_match_line(struct parse *p) | ||
536 | { | ||
537 | int string_matches; | ||
538 | |||
539 | parse_check_label_matches(p); | ||
540 | |||
541 | string_matches = parse_get_string_cbs(p, match(p)); | ||
542 | parse_state_set_int(p, string_matches); | ||
543 | |||
544 | if (!string_matches) { | ||
545 | if (!CBS_skip(&p->cbs, CBS_len(&p->cbs))) | ||
546 | parse_errx(p, "CBS_skip"); | ||
547 | } | ||
548 | } | ||
549 | |||
550 | static int | ||
551 | parse_get_hex_nibble_cbs(CBS *cbs, uint8_t *out_nibble) | ||
552 | { | ||
553 | uint8_t c; | ||
554 | |||
555 | if (!CBS_get_u8(cbs, &c)) | ||
556 | return 0; | ||
557 | |||
558 | if (c >= '0' && c <= '9') { | ||
559 | *out_nibble = c - '0'; | ||
560 | return 1; | ||
561 | } | ||
562 | if (c >= 'a' && c <= 'f') { | ||
563 | *out_nibble = c - 'a' + 10; | ||
564 | return 1; | ||
565 | } | ||
566 | if (c >= 'A' && c <= 'F') { | ||
567 | *out_nibble = c - 'A' + 10; | ||
568 | return 1; | ||
569 | } | ||
570 | |||
571 | return 0; | ||
572 | } | ||
573 | |||
574 | static void | ||
575 | parse_hex_line(struct parse *p) | ||
576 | { | ||
577 | CBB cbb; | ||
578 | |||
579 | parse_check_label_matches(p); | ||
580 | |||
581 | if (!CBB_init(&cbb, 0)) | ||
582 | parse_errx(p, "CBB_init"); | ||
583 | |||
584 | while (CBS_len(&p->cbs) > 0) { | ||
585 | uint8_t hi, lo; | ||
586 | |||
587 | if (!parse_get_hex_nibble_cbs(&p->cbs, &hi)) | ||
588 | parse_errx(p, "parse_get_hex_nibble_cbs"); | ||
589 | if (!parse_get_hex_nibble_cbs(&p->cbs, &lo)) | ||
590 | parse_errx(p, "parse_get_hex_nibble_cbs"); | ||
591 | |||
592 | if (!CBB_add_u8(&cbb, hi << 4 | lo)) | ||
593 | parse_errx(p, "CBB_add_u8"); | ||
594 | } | ||
595 | |||
596 | parse_state_set_from_cbb(p, &cbb); | ||
597 | } | ||
598 | |||
599 | static void | ||
600 | parse_maybe_prepare_instruction_line(struct parse *p) | ||
601 | { | ||
602 | if (parse_instructions_parsed(p)) | ||
603 | return; | ||
604 | |||
605 | /* Should not happen due to parse_empty_or_comment_line(). */ | ||
606 | if (CBS_len(&p->cbs) == 0) | ||
607 | parse_errx(p, "empty instruction line"); | ||
608 | |||
609 | if (!parse_peek_string_cbs(p, "[")) | ||
610 | parse_errx(p, "expected instruction line"); | ||
611 | if (!parse_get_string_cbs(p, "[")) | ||
612 | parse_errx(p, "expected start of instruction line"); | ||
613 | if (!parse_get_string_end_cbs(p, "]")) | ||
614 | parse_errx(p, "expected end of instruction line"); | ||
615 | } | ||
616 | |||
617 | static void | ||
618 | parse_check_line_consumed(struct parse *p) | ||
619 | { | ||
620 | if (CBS_len(&p->cbs) > 0) | ||
621 | parse_errx(p, "%zu unprocessed bytes", CBS_len(&p->cbs)); | ||
622 | } | ||
623 | |||
624 | static int | ||
625 | parse_run_test_case(struct parse *p) | ||
626 | { | ||
627 | const struct test_parse *tctx = p->tctx; | ||
628 | |||
629 | p->state.running_test_case = 1; | ||
630 | return tctx->run_test_case(p->ctx); | ||
631 | } | ||
632 | |||
633 | static void | ||
634 | parse_reinit(struct parse *p) | ||
635 | { | ||
636 | const struct test_parse *tctx = p->tctx; | ||
637 | |||
638 | p->state.running_test_case = 0; | ||
639 | parse_line_data_clear(p); | ||
640 | tctx->finish(p->ctx); | ||
641 | tctx->init(p->ctx, p); | ||
642 | } | ||
643 | |||
644 | static int | ||
645 | parse_maybe_run_test_case(struct parse *p) | ||
646 | { | ||
647 | int failed = 0; | ||
648 | |||
649 | if (parse_must_run_test_case(p)) { | ||
650 | failed |= parse_run_test_case(p); | ||
651 | parse_reinit(p); | ||
652 | } | ||
653 | |||
654 | parse_advance(p); | ||
655 | |||
656 | return failed; | ||
657 | } | ||
658 | |||
659 | static int | ||
660 | parse_process_line(struct parse *p) | ||
661 | { | ||
662 | if (parse_empty_or_comment_line(p)) | ||
663 | return 0; | ||
664 | |||
665 | parse_maybe_prepare_instruction_line(p); | ||
666 | |||
667 | switch (parse_line_type(p)) { | ||
668 | case LINE_STRING_MATCH: | ||
669 | parse_string_match_line(p); | ||
670 | break; | ||
671 | case LINE_HEX: | ||
672 | parse_hex_line(p); | ||
673 | break; | ||
674 | default: | ||
675 | parse_errx(p, "unknown line type %d", parse_line_type(p)); | ||
676 | } | ||
677 | parse_check_line_consumed(p); | ||
678 | |||
679 | return parse_maybe_run_test_case(p); | ||
680 | } | ||
681 | |||
682 | static void | ||
683 | parse_init(struct parse *p, const char *fn, const struct test_parse *tctx, | ||
684 | void *ctx) | ||
685 | { | ||
686 | FILE *fp; | ||
687 | |||
688 | memset(p, 0, sizeof(*p)); | ||
689 | |||
690 | if ((fp = fopen(fn, "r")) == NULL) | ||
691 | err(1, "error opening %s", fn); | ||
692 | |||
693 | /* Poor man's basename since POSIX basename is stupid. */ | ||
694 | if ((p->fn = strrchr(fn, '/')) != NULL) | ||
695 | p->fn++; | ||
696 | else | ||
697 | p->fn = fn; | ||
698 | |||
699 | p->fp = fp; | ||
700 | parse_state_init(&p->state, tctx->num_states, tctx->num_instructions); | ||
701 | p->tctx = tctx; | ||
702 | p->ctx = ctx; | ||
703 | tctx->init(ctx, p); | ||
704 | } | ||
705 | |||
706 | static int | ||
707 | parse_next_line(struct parse *p) | ||
708 | { | ||
709 | ssize_t len; | ||
710 | uint8_t u8; | ||
711 | |||
712 | if ((len = getline(&p->buf, &p->buf_max, p->fp)) == -1) | ||
713 | return 0; | ||
714 | |||
715 | CBS_init(&p->cbs, (const uint8_t *)p->buf, len); | ||
716 | parse_state_new_line(&p->state); | ||
717 | |||
718 | if (!CBS_get_last_u8(&p->cbs, &u8)) | ||
719 | parse_errx(p, "CBS_get_last_u8"); | ||
720 | |||
721 | assert(u8 == '\n'); | ||
722 | |||
723 | return 1; | ||
724 | } | ||
725 | |||
726 | static void | ||
727 | parse_finish(struct parse *p) | ||
728 | { | ||
729 | parse_state_finish(&p->state); | ||
730 | |||
731 | free(p->buf); | ||
732 | |||
733 | if (ferror(p->fp)) | ||
734 | err(1, "%s", p->fn); | ||
735 | fclose(p->fp); | ||
736 | } | ||
737 | |||
738 | int | ||
739 | parse_test_file(const char *fn, const struct test_parse *tctx, void *ctx) | ||
740 | { | ||
741 | struct parse p; | ||
742 | int failed = 0; | ||
743 | |||
744 | parse_init(&p, fn, tctx, ctx); | ||
745 | |||
746 | while (parse_next_line(&p)) | ||
747 | failed |= parse_process_line(&p); | ||
748 | |||
749 | parse_finish(&p); | ||
750 | |||
751 | return failed; | ||
752 | } | ||