progs/perftest.c: Use from Glibc syscall numbers.
[catacomb] / symm / strobe.c
1 /* -*-c-*-
2 *
3 * The STROBE protocol framework
4 *
5 * (c) 2018 Straylight/Edgeware
6 */
7
8 /*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of Catacomb.
11 *
12 * Catacomb is free software: you can redistribute it and/or modify it
13 * under the terms of the GNU Library General Public License as published
14 * by the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * Catacomb is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Library General Public License for more details.
21 *
22 * You should have received a copy of the GNU Library General Public
23 * License along with Catacomb. If not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25 * USA.
26 */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include <assert.h>
31 #include <ctype.h>
32 #include <string.h>
33
34 #include <mLib/buf.h>
35
36 #include "keccak1600.h"
37 #include "strobe.h"
38
39 /*----- Magic constants ---------------------------------------------------*/
40
41 #define DDATA 0x04
42 #define DRATE 0x80
43
44 /*----- Utilities ---------------------------------------------------------*/
45
46 /* --- @crank@ --- *
47 *
48 * Arguments: @strobe_ctx *ctx@ = pointer to context block to initialize
49 *
50 * Returns: ---
51 *
52 * Use: Cycle the Keccak-p[1600, n] duplex function.
53 */
54
55 static void crank(strobe_ctx *ctx)
56 {
57 kludge64 t[25];
58 octet *p;
59 unsigned i;
60
61 /* Ensure that we've not overstepped the rate bound. */
62 assert(ctx->n <= ctx->r - 2);
63
64 /* Apply the cSHAKE and rate padding. */
65 ctx->buf[ctx->n] ^= ctx->n0;
66 ctx->buf[ctx->n + 1] ^= DDATA;
67 ctx->buf[ctx->r - 1] ^= DRATE;
68
69 /* Cycle the sponge. */
70 for (i = 0, p = ctx->buf; i < ctx->r/8; i++)
71 { LOAD64_L_(t[i], p); p += 8; }
72 keccak1600_set(&ctx->k, t, ctx->r/8);
73 keccak1600_p(&ctx->k, &ctx->k, 24);
74 keccak1600_extract(&ctx->k, t, ctx->r/8);
75 for (i = 0, p = ctx->buf; i < ctx->r/8; i++)
76 { STORE64_L_(p, t[i]); p += 8; }
77
78 /* Restart at the beginning of the buffer, and note this as a
79 * continuation.
80 */
81 ctx->n = ctx->n0 = 0;
82 }
83
84 /* --- @xorbuf@ --- *
85 *
86 * Arguments: @octet *z@ = pointer to output buffer
87 * @const octet *x, *y@ = pointer to input buffers
88 * @size_t sz@ = common buffer length
89 *
90 * Returns: ---
91 *
92 * Use: Store the bytewise XOR of the buffers @x@ and @y@ in @z@.
93 * The @x@ and @y@ may be equal, but otherwise the buffers must
94 * not overlap.
95 */
96
97 static void xorbuf(octet *z, const octet *x, const octet *y, size_t sz)
98 { size_t i; for (i = 0; i < sz; i++) *z++ = *x++ ^ *y++; }
99
100 /* --- @nonzerop@ --- *
101 *
102 * Arguments: @const octet *x@ = pointer to input buffer
103 * @size_t sz@ = buffer length
104 *
105 * Returns: ---
106 *
107 * Use: If any byte of @x@ is nonzero, then return a nonzero value
108 * between 1 and 255 inclusive; otherwise return zero.
109 */
110
111 static unsigned nonzerop(const octet *x, size_t sz)
112 {
113 unsigned z = 0;
114 size_t i;
115
116 for (i = 0; i < sz; i++) z |= *x++;
117 return (z);
118 }
119
120 /* --- @unequalp@ --- *
121 *
122 * Arguments: @const octet *x, *y@ = pointer to input buffers
123 * @size_t sz@ = common buffer length
124 *
125 * Returns: ---
126 *
127 * Use: If any respective bytes of @x@ and @y@ are unequal, then
128 * return a nonzero value between 1 and 255 inclusive; otherwise
129 * return zero.
130 */
131
132 static unsigned unequalp(const octet *x, const octet *y, size_t sz)
133 {
134 unsigned z = 0;
135 size_t i;
136
137 for (i = 0; i < sz; i++) z |= *x++ ^ *y++;
138 return (z);
139 }
140
141 /* --- @process_buffer@ --- *
142 *
143 * Arguments: @strobe_ctx *ctx@ = pointer to context block
144 * @const octet *p@ = pointer to input buffer
145 * @octet *q@ = pointer to output buffer
146 * @size_t sz@ = common buffer length
147 *
148 * Returns: ---
149 *
150 * Use: Process a portion of a STROBE input small enough to be
151 * satisfied from the internal buffer.
152 */
153
154 static void process_buffer(strobe_ctx *ctx,
155 const octet *p, octet *q, size_t sz)
156 {
157 octet *b = ctx->buf + ctx->n;
158 unsigned z = 0;
159
160 if (!(ctx->f&STRBF_CRYPTO)) {
161 /* No crypto to do. The `output' would be equal to the input, so that's
162 * rather uninteresting (and, indeed, forbidden). If there's input, then
163 * mix it into the state.
164 */
165
166 if (p && (ctx->f&STRBF_VRFOUT)) z |= nonzerop(p, sz);
167 if (p) xorbuf(b, b, p, sz);
168 } else if (!(ctx->f&STRBF_MIXOUT)) {
169 /* Mix the input into the sponge state. That means that the new state
170 * will be equal to the output.
171 */
172
173 if (p) xorbuf(b, b, p, sz);
174 if (ctx->f&STRBF_VRFOUT) z |= nonzerop(b, sz);
175 if (q) memcpy(q, b, sz);
176 } else if (p) {
177 /* Mix the output into the sponge state, so the new state will in fact be
178 * equal to the input. If the input and output buffers are equal then we
179 * have a dance to do.
180 */
181
182 if (!q) {
183 if (ctx->f&STRBF_VRFOUT) z |= unequalp(p, b, sz);
184 memcpy(b, p, sz);
185 } else {
186 xorbuf(q, p, b, sz);
187 if (q != p) memcpy(b, p, sz);
188 else xorbuf(b, b, q, sz);
189 if (ctx->f&STRBF_VRFOUT) z |= nonzerop(q, sz);
190 }
191 } else {
192 /* As above, only the input is hardwired to zero. That means that we
193 * copy state bytes to the output (if any), and just clobber the state
194 * when we're done.
195 */
196
197 if (q) memcpy(q, b, sz);
198 memset(b, 0, sz);
199 }
200
201 /* Set the @STRBF_NZERO@ flag if @z@ is nonzero. If @z@ is zero then
202 * subtracting one will set all of its bits, so, in particular, bits
203 * 8--15. Otherwise, @z@ is between 1 and 255, so bits 8--15 are clear and
204 * will remain so when we subtract one.
205 */
206 if (ctx->f&STRBF_VRFOUT) ctx->f |= ((z - 1)&STRBF_NZERO) ^ STRBF_NZERO;
207
208 /* Update the buffer cursor. */
209 ctx->n += sz;
210 }
211
212 /*----- Interface ---------------------------------------------------------*/
213
214 /* --- @strobe_init@ --- *
215 *
216 * Arguments: @strobe_ctx *ctx@ = pointer to context block to initialize
217 * @unsigned lambda@ = security parameter, in bits (must be a
218 * multiple of 32)
219 *
220 * Returns: ---
221 *
222 * Use: Initialize a STROBE context for use.
223 */
224
225 void strobe_init(strobe_ctx *ctx, unsigned lambda)
226 {
227 const char v[] = "STROBEv1.0.2";
228 kludge64 t[25];
229 octet *p;
230 buf b;
231 unsigned n, i;
232
233 /* Check the security parameter. */
234 assert(lambda%32 == 0); assert(lambda <= 704);
235 ctx->r = (1600 - 2*lambda)/8;
236
237 /* Set up the initial cSHAKE framing. */
238 buf_init(&b, ctx->buf, ctx->r);
239 buf_putu8(&b, 1); buf_putu8(&b, ctx->r);
240 buf_putu8(&b, 1); buf_putu8(&b, 0);
241 buf_putu8(&b, 1); buf_putu8(&b, 8*(sizeof(v) - 1));
242 buf_put(&b, v, sizeof(v) - 1);
243 assert(BOK(&b));
244 n = BLEN(&b); if (n%8) memset(ctx->buf + n, 0, 8 - n%8);
245
246 /* Cycle the sponge once initially, and get the first output buffer. */
247 keccak1600_init(&ctx->k);
248 for (i = 0, p = ctx->buf; i < (n + 7)/8; i++)
249 { LOAD64_L_(t[i], p); p += 8; }
250 keccak1600_set(&ctx->k, t, (n + 7)/8);
251 keccak1600_p(&ctx->k, &ctx->k, 24);
252 keccak1600_extract(&ctx->k, t, ctx->r/8);
253 for (i = 0, p = ctx->buf; i < ctx->r/8; i++)
254 { STORE64_L_(p, t[i]); p += 8; }
255
256 /* Initialize the other parts of the state. */
257 ctx->n = ctx->n0 = 0; ctx->f = 0;
258 }
259
260 /* --- @strobe_begin@ --- *
261 *
262 * Arguments: @strobe_ctx *ctx@ = pointer to context block
263 * @unsigned op@ = bitmask of flags
264 *
265 * Returns: ---
266 *
267 * Use: Begin a STROBE operation. The flags determine the behaviour
268 * of the @strobe_process@ and @strobe_done@ functions.
269 *
270 * * The @I@ bit determines the primary direction of data
271 * movement. If it's clear, data comes from the application
272 * into STROBE. If it's set, data comes from STROBE towards
273 * the application.
274 *
275 * * The @C@ bit activates cryptographic processing. If it's
276 * clear, then the input and output data would be equal, so
277 * @dest@ must be null. If it's set, then input data is
278 * XORed with the keystream on its way to the output.
279 *
280 * * The @A@ bit determines whether the application is
281 * engaged. If it's set, then the input or output buffer
282 * (according to whether @I@ is clear or set, respectively)
283 * holds the application data. If it's clear, and @I@ is
284 * clear, then zero bytes are fed in; if @I@ is set, then
285 * the output is compared with zero, and @strobe_done@
286 * reports the outcome of this comparison.
287 *
288 * * The @T@ bit determines whether the transport is engaged.
289 * If it's set, then the input or output buffer (according
290 * to whether @I@ is set or clear, respectively) holds
291 * transport data. If it's clear, and @I@ is set, then zero
292 * bytes are fed in; if @I@ is clear, then the output is
293 * discarded.
294 *
295 * * The @M@ bit marks the data as metadata, but has no other
296 * effect.
297 */
298
299 void strobe_begin(strobe_ctx *ctx, unsigned op)
300 {
301 /* Preliminary checking. We shouldn't have an operation underway, and the
302 * operation shouldn't have reserved bits set.
303 */
304 assert(!(ctx->f&STRBF_ACTIVE)); assert(!(op&~STRBF_VALIDMASK));
305
306 /* Reset our operation state. */
307 ctx->f &= STRBF_STMASK;
308
309 /* Operation framing. Chain back to the start of the previous frame and
310 * write the new operation code. Set the sticky asymmetry bit here if
311 * necessary.
312 */
313 ctx->buf[ctx->n++] ^= ctx->n0; ctx->n0 = ctx->n;
314 if (ctx->n >= ctx->r - 2) crank(ctx);
315 if (!(op&STRBF_T))
316 ctx->buf[ctx->n++] ^= U8(op);
317 else {
318 if (!(ctx->f&STRBF_INIT)) ctx->f |= STRBF_INIT | (op&STRBF_I);
319 ctx->buf[ctx->n++] ^= U8(op ^ ctx->f);
320 }
321 if (ctx->n >= ctx->r - 2 || (op&STRBF_C)) crank(ctx);
322
323 /* The operation is now underway. */
324 ctx->f |= STRBF_ACTIVE;
325
326 /* Determine whether we expect input and/or output. */
327 if (op&(op&STRBF_I ? STRBF_T : STRBF_A))
328 ctx->f |= STRBF_WANTIN;
329 if ((op&STRBF_C) && op&(op&STRBF_I ? STRBF_A : STRBF_T))
330 ctx->f |= STRBF_WANTOUT;
331
332 /* Determine whether the keystream is engaged, and how it fits in. */
333 if (op&STRBF_C) {
334 ctx->f |= STRBF_CRYPTO;
335 if ((op&(STRBF_I | STRBF_T)) != STRBF_T) ctx->f |= STRBF_MIXOUT;
336 }
337
338 /* Determine whether the output is supposed to be all-bytes-zero. */
339 if ((op&(STRBF_I | STRBF_A | STRBF_T)) == (STRBF_I | STRBF_T))
340 ctx->f |= STRBF_VRFOUT;
341
342 /* The operation is now underway. */
343 ctx->f |= STRBF_ACTIVE;
344 }
345
346 /* --- @strobe_process@ --- *
347 *
348 * Arguments: @strobe_ctx *ctx@ = pointer to context block
349 * @const void *src@ = pointer to input data, or null
350 * @void *dest@ = pointer to output data, or null
351 * @size_t sz@ = common buffer length
352 *
353 * Returns: ---
354 *
355 * Use: Process data through the active STROBE operation. The exact
356 * behaviour depends on the flags passed to @strobe_begin@; see
357 * that function for details. If @src@ is null, then the
358 * behaviour is as if the input consists of @sz@ zero bytes. If
359 * @dest@ in null, then the output is discarded.
360 */
361
362 void strobe_process(strobe_ctx *ctx, const void *src, void *dest, size_t sz)
363 {
364 const octet *p = src; octet *q = dest;
365 unsigned spare;
366
367 /* Make sure that things are set up properly. */
368 assert(ctx->f&STRBF_ACTIVE);
369 if (!(ctx->f&STRBF_WANTIN)) assert(!src);
370 if (!(ctx->f&STRBF_WANTOUT)) assert(!dest);
371
372 /* Work through the input. */
373 spare = ctx->r - ctx->n - 2;
374 if (sz < spare)
375 { process_buffer(ctx, p, q, sz); return; }
376 if (ctx->n) {
377 process_buffer(ctx, p, q, spare); crank(ctx);
378 if (p) { p += spare; }
379 if (q) { q += spare; }
380 sz -= spare;
381 }
382
383 while (sz >= ctx->r - 2) {
384 process_buffer(ctx, p, q, ctx->r - 2); crank(ctx);
385 if (p) { p += ctx->r - 2; }
386 if (q) { q += ctx->r - 2; }
387 sz -= ctx->r - 2;
388 }
389 if (sz) process_buffer(ctx, p, q, sz);
390 }
391
392 /* --- @strobe_done@ --- *
393 *
394 * Arguments: @strobe_ctx *ctx@ = pointer to context block
395 *
396 * Returns: Zero on success; @-1@ on verification failure (if @I@ and @T@
397 * are set and @A@ is clear)
398 *
399 * Use: Concludes a STROBE operation, returning the result.
400 */
401
402 int strobe_done(strobe_ctx *ctx)
403 {
404 assert(ctx->f&STRBF_ACTIVE); ctx->f &= ~STRBF_ACTIVE;
405 if (ctx->f&STRBF_VRFOUT) return (-(int)((ctx->f/STRBF_NZERO)&1u));
406 else return (0);
407 }
408
409 /* --- @strobe_key@, @strobe_ad@, @strobe_@prf@, @strobe_clrout@,
410 * @strobe_clrin@, @strobe_encout@, @strobe_encin@, @strobe_macout@,
411 * @strobe_macin@, @strobe_ratchet@ --- *
412 *
413 * Arguments: @strobe_ctx *ctx@ = pointer to context block
414 *
415 * Returns: @strobe_macin@ returns zero on success, or @-1@ on
416 * verification failure
417 *
418 * Use: Perform a STROBE operation on a single buffer.
419 */
420
421 static int op(strobe_ctx *ctx, unsigned f0, unsigned f1,
422 const void *src, void *dest, size_t sz)
423 {
424 assert(!(f1&~STRBF_M));
425
426 strobe_begin(ctx, f0 | f1);
427 strobe_process(ctx, src, dest, sz);
428 return (strobe_done(ctx));
429 }
430
431 void strobe_key(strobe_ctx *ctx, unsigned f, const void *k, size_t sz)
432 { op(ctx, STROBE_KEY, f, k, 0, sz); }
433
434 void strobe_ad(strobe_ctx *ctx, unsigned f, const void *h, size_t sz)
435 { op(ctx, STROBE_AD, f, h, 0, sz); }
436
437 void strobe_prf(strobe_ctx *ctx, unsigned f, void *t, size_t sz)
438 { op(ctx, STROBE_PRF, f, 0, t, sz); }
439
440 void strobe_clrout(strobe_ctx *ctx, unsigned f, const void *m, size_t sz)
441 { op(ctx, STROBE_CLROUT, f, m, 0, sz); }
442
443 void strobe_clrin(strobe_ctx *ctx, unsigned f, const void *m, size_t sz)
444 { op(ctx, STROBE_CLRIN, f, m, 0, sz); }
445
446 void strobe_encout(strobe_ctx *ctx, unsigned f,
447 const void *m, void *c, size_t sz)
448 { op(ctx, STROBE_ENCOUT, f, m, c, sz); }
449
450 void strobe_encin(strobe_ctx *ctx, unsigned f,
451 const void *c, void *m, size_t sz)
452 { op(ctx, STROBE_ENCIN, f, c, m, sz); }
453
454 void strobe_macout(strobe_ctx *ctx, unsigned f, void *t, size_t sz)
455 { op(ctx, STROBE_MACOUT, f, 0, t, sz); }
456
457 int strobe_macin(strobe_ctx *ctx, unsigned f, const void *t, size_t sz)
458 { return (op(ctx, STROBE_MACIN, f, t, 0, sz)); }
459
460 void strobe_ratchet(strobe_ctx *ctx, unsigned f, size_t sz)
461 { op(ctx, STROBE_RATCHET, f, 0, 0, sz); }
462
463 /*----- Test rig ----------------------------------------------------------*/
464
465 #ifdef TEST_RIG
466
467 #include <stdlib.h>
468 #include <string.h>
469
470 #include <mLib/hex.h>
471 #include <mLib/macros.h>
472 #include <mLib/testrig.h>
473
474 #define NSTATE 16
475
476 static strobe_ctx states[NSTATE];
477
478 static void dump(int rc, char win, const void *p, size_t sz)
479 {
480 dstr d = DSTR_INIT;
481 const char *q = p;
482 size_t i;
483 codec *hex;
484 int printable;
485
486 if (!p) {
487 if (!rc) putchar(win);
488 else putchar('-');
489 } else {
490 for (i = 0, printable = 1; i < sz; i++)
491 if (!ISPRINT(q[i])) { printable = 0; break; }
492 if (printable)
493 printf("`%s'", q);
494 else {
495 hex = hex_class.encoder(CDCF_LOWERC, 0, 0);
496 hex->ops->code(hex, p, sz, &d);
497 dstr_write(&d, stdout);
498 hex->ops->destroy(hex);
499 }
500 }
501 dstr_destroy(&d);
502 putchar('\n');
503 }
504
505 typedef int opfunc(strobe_ctx *, unsigned, const void *, void *, size_t);
506
507 static int op_init(strobe_ctx *ctx, unsigned f,
508 const void *p, void *q, size_t sz)
509 { strobe_init(ctx, sz); return (0); }
510
511 static int op_copy(strobe_ctx *ctx, unsigned f,
512 const void *p, void *q, size_t sz)
513 { *ctx = states[sz]; return (0); }
514
515 static int op_begin(strobe_ctx *ctx, unsigned f,
516 const void *p, void *q, size_t sz)
517 { strobe_begin(ctx, f); return (0); }
518
519 static int op_process(strobe_ctx *ctx, unsigned f,
520 const void *p, void *q, size_t sz)
521 { strobe_process(ctx, p, q, sz); return (0); }
522
523 static int op_done(strobe_ctx *ctx, unsigned f,
524 const void *p, void *q, size_t sz)
525 { return (strobe_done(ctx)); }
526
527 static int op_key(strobe_ctx *ctx, unsigned f,
528 const void *p, void *q, size_t sz)
529 { strobe_key(ctx, f, p, sz); return (0); }
530
531 static int op_ad(strobe_ctx *ctx, unsigned f,
532 const void *p, void *q, size_t sz)
533 { strobe_ad(ctx, f, p, sz); return (0); }
534
535 static int op_prf(strobe_ctx *ctx, unsigned f,
536 const void *p, void *q, size_t sz)
537 { strobe_prf(ctx, f, q, sz); return (0); }
538
539 static int op_clrout(strobe_ctx *ctx, unsigned f,
540 const void *p, void *q, size_t sz)
541 { strobe_clrout(ctx, f, p, sz); return (0); }
542
543 static int op_clrin(strobe_ctx *ctx, unsigned f,
544 const void *p, void *q, size_t sz)
545 { strobe_clrin(ctx, f, p, sz); return (0); }
546
547 static int op_encout(strobe_ctx *ctx, unsigned f,
548 const void *p, void *q, size_t sz)
549 { strobe_encout(ctx, f, p, q, sz); return (0); }
550
551 static int op_encin(strobe_ctx *ctx, unsigned f,
552 const void *p, void *q, size_t sz)
553 { strobe_encin(ctx, f, p, q, sz); return (0); }
554
555 static int op_macout(strobe_ctx *ctx, unsigned f,
556 const void *p, void *q, size_t sz)
557 { strobe_macout(ctx, f, q, sz); return (0); }
558
559 static int op_macin(strobe_ctx *ctx, unsigned f,
560 const void *p, void *q, size_t sz)
561 { return (strobe_macin(ctx, f, p, sz)); }
562
563 static int op_ratchet(strobe_ctx *ctx, unsigned f,
564 const void *p, void *q, size_t sz)
565 { strobe_ratchet(ctx, f, sz); return (0); }
566
567 static const struct optab {
568 const char *name;
569 opfunc *op;
570 } optab[] = {
571 #define OP(op) { #op, op_##op }
572 OP(init), OP(copy),
573 OP(begin), OP(process), OP(done),
574 OP(key), OP(ad), OP(prf),
575 OP(clrout), OP(clrin),
576 OP(encout), OP(encin),
577 OP(macout), OP(macin),
578 OP(ratchet),
579 { 0 }
580 #undef OP
581 };
582
583 static int verify(dstr v[])
584 {
585 int r;
586 strobe_ctx *ctx;
587 const char *p;
588 char *q;
589 const struct optab *op;
590 dstr d0 = DSTR_INIT, d1 = DSTR_INIT;
591 codec *hex;
592 unsigned f;
593 const void *src, *destref;
594 void *dest;
595 size_t sz;
596 int rc, rcref;
597 int ok;
598
599 /* First, get the register number. */
600 r = *(int *)v[0].buf; ctx = &states[r];
601
602 /* Next job is to parse the command and flags. */
603 q = v[1].buf; p = q; q += strcspn(q, "/"); if (*q) *q++ = 0;
604 for (op = optab; op->name; op++)
605 if (STRCMP(op->name, ==, p)) goto found_op;
606 abort();
607 found_op:
608
609 f = 0;
610 for (p = q; *p; p++) {
611 switch (*p) {
612 case 'I': f |= STRBF_I; break;
613 case 'C': f |= STRBF_C; break;
614 case 'A': f |= STRBF_A; break;
615 case 'T': f |= STRBF_T; break;
616 case 'M': f |= STRBF_M; break;
617 default: abort();
618 }
619 }
620
621 /* Convert the source parameter. */
622 p = v[2].buf;
623 if (*p == '*')
624 { src = 0; sz = strtoul(p + 1, 0, 0); }
625 else if (*p == '=')
626 { src = p + 1; sz = v[2].len - 1; }
627 else if (*p == '!') {
628 hex = hex_class.decoder(CDCF_IGNCASE);
629 rc = hex->ops->code(hex, p + 1, v[2].len - 1, &d0); assert(!rc);
630 src = d0.buf; sz = d0.len;
631 hex->ops->destroy(hex);
632 } else
633 abort();
634
635 /* Convert the destination parameter. */
636 p = v[3].buf;
637 if (*p == '+')
638 { destref = 0; rcref = 0; assert(v[3].len == 1); }
639 else if (*p == '-')
640 { destref = 0; rcref = -1; assert(v[3].len == 1); }
641 else if (*p == '=')
642 { destref = p + 1; assert(sz == v[3].len - 1); rcref = 0; }
643 else if (*p == '!') {
644 hex = hex_class.decoder(CDCF_IGNCASE);
645 rc = hex->ops->code(hex, p + 1, v[3].len - 1, &d1); assert(!rc);
646 destref = d1.buf; assert(sz == d1.len);
647 hex->ops->destroy(hex);
648 rcref = 0;
649 } else
650 abort();
651 if (!destref) dest = 0;
652 else dest = xmalloc(sz);
653
654 /* Do the operation. */
655 rc = op->op(ctx, f, src, dest, sz);
656
657 /* Check we got the right answer. */
658 ok = (rc == rcref && (!destref || MEMCMP(dest, ==, destref, sz)));
659 if (!ok) {
660 printf("failed test\n");
661 printf(" state = %d\n", r);
662 printf(" operation = %s%s%s%s%s%s%s\n",
663 op->name,
664 f ? "/" : "",
665 f&STRBF_I ? "I" : "",
666 f&STRBF_A ? "A" : "",
667 f&STRBF_C ? "C" : "",
668 f&STRBF_T ? "T" : "",
669 f&STRBF_M ? "M" : "");
670 printf(" input = "); dump(0, '*', src, sz);
671 printf(" computed = "); dump(rc, '+', dest, sz);
672 printf(" expected = "); dump(rcref, '+', destref, sz);
673 }
674
675 dstr_destroy(&d0);
676 dstr_destroy(&d1);
677 free(dest);
678 return (ok);
679 }
680
681 static test_chunk tests[] = {
682 { "strobe", verify,
683 { &type_int, &type_string, &type_string, &type_string, 0 } },
684 { 0, 0, { 0 } }
685 };
686
687 int main(int argc, char *argv[])
688 {
689 test_run(argc, argv, tests, SRCDIR "/t/strobe");
690 return (0);
691 }
692
693 #endif
694
695 /*----- That's all, folks -------------------------------------------------*/