chiark / gitweb /
math/gfx-sqr.c: Use bithacking rather than a table for squaring.
[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 -------------------------------------------------*/