Shore up the `grand' protocol.
authorMark Wooding <mdw@distorted.org.uk>
Sun, 15 May 2016 23:25:05 +0000 (00:25 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Mon, 16 May 2016 08:20:02 +0000 (09:20 +0100)
This is a bit of a mess, really.  There are two main problems.

  * Firstly, the business of comparing function pointers simply doesn't
    work on Windows, because the dynamic linker is too hopeless.  If the
    class implementation is in a different module from Catacomb itself,
    then the vtable may have a pointer to an import-library stub, which
    won't compare equal to the library's idea of the default method
    function.

    It's basically a mistake to have tried to use the same functions as
    both user interface and default implementation.  Split the two
    apart.  Leave the hacky function-pointer comparisons in the user-
    interface functions for compatibility with existing out-of-tree
    generators, which will carry on working about as well as they ever
    did.

    Note, however, that the defaulting strategy is now less `clever',
    and implementations which don't provide native versions of all the
    methods may end up suffering more than they used to.

  * The `grand_range' function was simply broken if the `raw' method's
    output is too small.  Synthesizing a full uniform-value-in-range
    from a narrow primitive generator in single precision arithmetic is
    rather difficult, so rather than do that I've decided to insist that
    all `raw' methods have a range of at least a byte's worth, so we can
    synthesize a word generator out of bytes if necessary, and then use
    that to satisfy the original larger range request.

This is all a bit unsatisfactory, really.  I must remember to revisit it
when all of these gets made out of a proper object system.

15 files changed:
pub/bbs-rand.c
rand/dsarand.c
rand/grand.c
rand/grand.h
rand/lcrand.c
rand/rand.c
rand/sslprf.c
rand/tlsprf.c
symm/chacha.c
symm/counter-def.h
symm/mgf-def.h
symm/ofb-def.h
symm/rc4.c
symm/salsa20.c
symm/seal.c

index 58b62b1..c7dc86e 100644 (file)
@@ -348,7 +348,7 @@ static const grand_ops gops = {
   "bbs",
   GRAND_CRYPTO, 0,
   gmisc, gdestroy,
-  gword, gbyte, gword, grand_range, grand_fill
+  gword, gbyte, gword, grand_defaultrange, grand_defaultfill
 };
 
 /* --- @bbs_rand@ --- *
index 9b18ab0..70ffaf8 100644 (file)
@@ -310,7 +310,8 @@ static const grand_ops gops = {
   "dsarand",
   0, 0,
   gmisc, gdestroy,
-  grand_word, grand_byte, grand_word, grand_range, gfill
+  grand_defaultword, grand_defaultbyte, grand_defaultword,
+  grand_defaultrange, gfill
 };
 
 /* --- @dsarand_create@ --- *
index 17d248b..e445191 100644 (file)
 
 #include "grand.h"
 
+/*----- Default operations ------------------------------------------------*/
+
+/* --- @grand_defaultbyte@ --- *
+ *
+ * Arguments:  @grand *r@ = pointet to generic generator
+ *
+ * Returns:    A uniformly-distributed pseudorandom integer in the interval
+ *             %$[0, 256)$%.
+ *
+ * Use:                Default @byte@ output method.  This calls the @range@ method
+ *             to return a uniform random value between 0 and 255.
+ */
+
+octet grand_defaultbyte(grand *r)
+  { return (r->ops->range(r, 256)); }
+
+/* --- @grand_defaultword@ --- *
+ *
+ * Arguments:  @grand *r@ = pointet to generic generator
+ *
+ * Returns:    A uniformly-distributed pseudorandom integer in the interval
+ *             %$[0, 2^{32})$%.
+ *
+ * Use:                Default @word@ output method.  This calls the @fill@ method
+ *             to fill a 4-octet buffer with uniform random bytes, and then
+ *             converts them to an integer.
+ */
+
+uint32 grand_defaultword(grand *r)
+  { octet buf[4]; r->ops->fill(r, buf, sizeof(buf)); return (LOAD32(buf)); }
+
+/* --- @grand_defaultrange@ --- *
+ *
+ * Arguments:  @grand *r@ = pointet to generic generator
+ *             @uint32 l@ = limit for acceptable results
+ *
+ * Returns:    A uniformly-distributed pseudorandom integer in the interval
+ *             %$[0, l)$%.
+ *
+ * Use:                Default @range@ output method.  This falls back to either
+ *             @word@ (if the generator's @max@ is zero, or if @max < l@) or
+ *             @raw@ (otherwise).  This might recurse via @fill@ and @byte@,
+ *             but this is safe because of the constraint on the @raw@
+ *             method.
+ */
+
+uint32 grand_defaultrange(grand *r, uint32 l)
+{
+  uint32 m, z;
+  uint32 (*w)(grand */*r*/);
+  uint32 x;
+
+  /* --- Decide where to get data from --- *
+   *
+   * The choice of %$2^{32} - 1$% as a limit when using @grand_word@ isn't
+   * wonderful, but working with %$2^{32}$% is awkward and the loss of a few
+   * return values isn't significant.  The algorithm below still successfully
+   * returns uniformly distributed results.
+   *
+   * If there's a raw generator, and it can cope with the limit, then use it;
+   * otherwise use the @word@ generator, which may recurse via @fill@ and
+   * @byte@, but by that point it must be able to satisfy us.
+   */
+
+  if (r->ops->max && r->ops->max >= l) {
+    w = r->ops->raw;
+    m = r->ops->max;
+  } else {
+    assert(!r->ops->max || r->ops->max >= 256);
+    w = grand_word;
+    m = 0xffffffff;
+  }
+
+  /* --- Work out maximum acceptable return value --- *
+   *
+   * This will be the highest multiple of @l@ less than @m@.
+   */
+
+  z = m - m%l;
+
+  /* --- Generate numbers until something acceptable is found --- *
+   *
+   * This will require an expected number of attempts less than 2.
+   */
+
+  do x = w(r); while (x >= z);
+  return (x%l);
+}
+
+/* --- @grand_defaultfill@ --- *
+ *
+ * Arguments:  @grand *r@ = pointet to generic generator
+ *             @void *p@ = pointer to a buffer
+ *             @size_t sz@ = size of the buffer
+ *
+ * Returns:    ---
+ *
+ * Use:                Fills a buffer with uniformly distributed pseudorandom bytes.
+ *             This calls the @byte@ method repeatedly to fill in the output
+ *             buffer.
+ */
+
+void grand_defaultfill(grand *r, void *p, size_t sz)
+  { octet *q = p; while (sz--) *q++ = r->ops->byte(r); }
+
 /*----- Main code ---------------------------------------------------------*/
 
 /* --- @grand_byte@ --- *
 
 octet grand_byte(grand *r)
 {
-  if (r->ops->byte != grand_byte)
-    return (r->ops->byte(r));
-  else if (r->ops->word != grand_word)
-    return (r->ops->word(r) & 0xff);
-  else if (r->ops->fill != grand_fill) {
-    octet o;
-    r->ops->fill(r, &o, 1);
-    return (o);
-  } else
-    return (grand_range(r, 256));
+  if (r->ops->byte == grand_byte) return (grand_defaultbyte(r));
+  else return (r->ops->byte(r));
 }
 
 /* --- @grand_word@ --- *
@@ -67,13 +164,8 @@ octet grand_byte(grand *r)
 
 uint32 grand_word(grand *r)
 {
-  if (r->ops->word != grand_word)
-    return (r->ops->word(r));
-  else {
-    octet b[4];
-    grand_fill(r, b, sizeof(b));
-    return (LOAD32(b));
-  }
+  if (r->ops->word == grand_word) return (grand_defaultword(r));
+  else return (r->ops->word(r));
 }
 
 /* --- @grand_range@ --- *
@@ -87,44 +179,8 @@ uint32 grand_word(grand *r)
 
 uint32 grand_range(grand *r, uint32 l)
 {
-  if (r->ops->range != grand_range)
-    return (r->ops->range(r, l));
-  else {
-    uint32 m, z;
-    uint32 (*w)(grand */*r*/);
-    uint32 x;
-
-    /* --- Decide where to get data from --- *
-     *
-     * The choice of %$2^{32} - 1$% as a limit when using @grand_word@ isn't
-     * wonderful, but working with %$2^{32}$% is awkward and the loss of a
-     * few return values isn't significant.  The algorithm below still
-     * successfully returns uniformly distributed results.
-     */
-
-    if (r->ops->max) {
-      w = r->ops->raw;
-      m = r->ops->max;
-    } else {
-      w = grand_word;
-      m = 0xffffffff;
-    }
-
-    /* --- Work out maximum acceptable return value --- *
-     *
-     * This will be the highest multiple of @l@ less than @m@.
-     */
-
-    z = m - (m % l);
-
-    /* --- Generate numbers until something acceptable is found --- *
-     *
-     * This will require an expected number of attempts less than 2.
-     */
-
-    do x = w(r); while (x >= z);
-    return (x % l);
-  }
+  if (r->ops->range == grand_range) return (grand_defaultrange(r, l));
+  else return (r->ops->range(r, l));
 }
 
 /* --- @grand_fill@ --- *
@@ -141,15 +197,8 @@ uint32 grand_range(grand *r, uint32 l)
 
 void grand_fill(grand *r, void *p, size_t sz)
 {
-  if (r->ops->fill != grand_fill)
-    r->ops->fill(r, p, sz);
-  else {
-    octet *q = p;
-    while (sz) {
-      *q++ = r->ops->byte(r);
-      sz--;
-    }
-  }
+  if (r->ops->fill == grand_fill) grand_defaultfill(r, p, sz);
+  else r->ops->fill(r, p, sz);
 }
 
 /*----- That's all, folks -------------------------------------------------*/
index ec11404..b45f045 100644 (file)
@@ -51,7 +51,10 @@ typedef struct grand_ops {
 
   const char *name;                    /* Generator's name */
   unsigned f;                          /* Various flags */
-  uint32 max;                          /* Maximum raw output */
+  uint32 max;                          /* Maximum raw output, if nonzero;
+                                        * must be either zero or at least
+                                        * 256
+                                        */
 
   /* --- Maintenance methods --- */
 
@@ -60,9 +63,10 @@ typedef struct grand_ops {
 
   /* --- Output methods --- *
    *
-   * Only one of these operations need actually be implemented.  All the
-   * other operations may be synthesized.  Of course, performance is improved
-   * if more are provided.
+   * Of these, only @raw@ need be implemented directly by the generator: the
+   * others can point to provided @grand_default...@ functions, which will
+   * synthesize the necessary behaviour.  Of course, this comes at an
+   * efficiency penalty.
    */
 
   uint32 (*raw)(grand */*r*/);         /* Uniform over %$[0, max)$% */
@@ -105,6 +109,67 @@ enum {
 
 #define GRAND_BADOP assert(((void)"bad grand_misc op", 0))
 
+/*----- Default operations ------------------------------------------------*/
+
+/* --- @grand_defaultbyte@ --- *
+ *
+ * Arguments:  @grand *r@ = pointet to generic generator
+ *
+ * Returns:    A uniformly-distributed pseudorandom integer in the interval
+ *             %$[0, 256)$%.
+ *
+ * Use:                Default @byte@ output method.  This calls the @range@ method
+ *             to return a uniform random value between 0 and 255.
+ */
+
+extern octet grand_defaultbyte(grand */*r*/);
+
+/* --- @grand_defaultword@ --- *
+ *
+ * Arguments:  @grand *r@ = pointet to generic generator
+ *
+ * Returns:    A uniformly-distributed pseudorandom integer in the interval
+ *             %$[0, 2^{32})$%.
+ *
+ * Use:                Default @word@ output method.  This calls the @fill@ method
+ *             to fill a 4-octet buffer with uniform random bytes, and then
+ *             converts them to an integer.
+ */
+
+extern uint32 grand_defaultword(grand */*r*/);
+
+/* --- @grand_defaultrange@ --- *
+ *
+ * Arguments:  @grand *r@ = pointet to generic generator
+ *             @uint32 l@ = limit for acceptable results
+ *
+ * Returns:    A uniformly-distributed pseudorandom integer in the interval
+ *             %$[0, l)$%.
+ *
+ * Use:                Default @range@ output method.  This falls back to either
+ *             @word@ (if the generator's @max@ is zero, or if @max < l@) or
+ *             @raw@ (otherwise).  This might recurse via @fill@ and @byte@,
+ *             but this is safe because of the constraint on the @raw@
+ *             method.
+ */
+
+extern uint32 grand_defaultrange(grand */*r*/, uint32 /*l*/);
+
+/* --- @grand_defaultfill@ --- *
+ *
+ * Arguments:  @grand *r@ = pointet to generic generator
+ *             @void *p@ = pointer to a buffer
+ *             @size_t sz@ = size of the buffer
+ *
+ * Returns:    ---
+ *
+ * Use:                Fills a buffer with uniformly distributed pseudorandom bytes.
+ *             This calls the @byte@ method repeatedly to fill in the output
+ *             buffer.
+ */
+
+extern void grand_defaultfill(grand */*r*/, void */*p*/, size_t /*sz*/);
+
 /*----- Functions provided ------------------------------------------------*/
 
 /* --- @grand_byte@ --- *
index 6944a71..d8c4a37 100644 (file)
@@ -246,7 +246,7 @@ static const grand_ops gops = {
   "lcrand",
   LCRAND_P, 0,
   gmisc, gdestroy,
-  graw, gbyte, grand_word, grange, grand_fill
+  graw, gbyte, grand_defaultword, grange, grand_defaultfill
 };
 
 /* --- @lcrand_create@ --- *
index e0bebbf..6fcdf43 100644 (file)
@@ -560,7 +560,7 @@ static const grand_ops gops = {
   "rand",
   GRAND_CRYPTO, 0,
   gmisc, gdestroy,
-  gword, gbyte, gword, grand_range, gfill
+  gword, gbyte, gword, grand_defaultrange, gfill
 };
 
 /* --- @rand_create@ --- *
index 83a4c84..06187b3 100644 (file)
@@ -270,7 +270,7 @@ static const grand_ops grops = {
   "<sslprf-dummy>",
   GRAND_CRYPTO, 0,
   grmisc, grdestroy,
-  grword, grbyte, grword, grand_range, grfill
+  grword, grbyte, grword, grand_defaultrange, grfill
 };
 
 /* ---@sslprf_rand@ --- *
index 3f055b0..fa28faf 100644 (file)
@@ -249,7 +249,7 @@ static const grand_ops dx_grops = {
   "<tlsdx-dummy>",
   GRAND_CRYPTO, 0,
   dx_grmisc, dx_grdestroy,
-  dx_grword, dx_grbyte, dx_grword, grand_range, dx_grfill
+  dx_grword, dx_grbyte, dx_grword, grand_defaultrange, dx_grfill
 };
 
 /* ---@tlsdx_rand@ --- *
@@ -454,7 +454,7 @@ static const grand_ops prf_grops = {
   "<tlsprf-dummy>",
   GRAND_CRYPTO, 0,
   prf_grmisc, prf_grdestroy,
-  prf_grword, prf_grbyte, prf_grword, grand_range, prf_grfill
+  prf_grword, prf_grbyte, prf_grword, grand_defaultrange, prf_grfill
 };
 
 /* ---@tlsprf_rand@ --- *
index bd94ffd..e694ad2 100644 (file)
@@ -672,7 +672,7 @@ static void grdestroy(grand *r)
   static const grand_ops grops_rand_##rr = {                           \
     "chacha" #rr, GRAND_CRYPTO, 0,                                     \
     grmisc, grdestroy, grword,                                         \
-    grbyte, grword, grand_range, grfill                                        \
+    grbyte, grword, grand_defaultrange, grfill                         \
   };                                                                   \
                                                                        \
   grand *chacha##rr##_rand(const void *k, size_t ksz, const void *n)   \
@@ -714,7 +714,7 @@ CHACHA_VARS(DEFGRAND)
   static const grand_ops grxops_rand_##rr = {                          \
     "xchacha" #rr, GRAND_CRYPTO, 0,                                    \
     grmisc, grxdestroy_##rr, grword,                                   \
-    grbyte, grword, grand_range, grfill                                        \
+    grbyte, grword, grand_defaultrange, grfill                         \
   };                                                                   \
                                                                        \
   grand *xchacha##rr##_rand(const void *k, size_t ksz, const void *n)  \
index 2f35323..ead615a 100644 (file)
@@ -398,7 +398,7 @@ static const grand_ops grops = {                                    \
   #pre "-counter",                                                     \
   GRAND_CRYPTO, 0,                                                     \
   grmisc, grdestroy,                                                   \
-  grword, grbyte, grword, grand_range, grfill                          \
+  grword, grbyte, grword, grand_defaultrange, grfill                   \
 };                                                                     \
                                                                        \
 /* --- @pre_counterrand@ --- *                                         \
index b5105ed..529428f 100644 (file)
@@ -327,7 +327,7 @@ static const grand_ops grops = {                                    \
   #pre "-mgf",                                                         \
   GRAND_CRYPTO, 0,                                                     \
   grmisc, grdestroy,                                                   \
-  grword, grbyte, grword, grand_range, grfill                          \
+  grword, grbyte, grword, grand_defaultrange, grfill                   \
 };                                                                     \
                                                                        \
 /* --- @pre_mgfrand@ --- *                                             \
index 6b1357d..7f111cb 100644 (file)
@@ -410,7 +410,7 @@ static const grand_ops grops = {                                    \
   #pre "-ofb",                                                         \
   GRAND_CRYPTO, 0,                                                     \
   grmisc, grdestroy,                                                   \
-  grword, grbyte, grword, grand_range, grfill                          \
+  grword, grbyte, grword, grand_defaultrange, grfill                   \
 };                                                                     \
                                                                        \
 /* --- @pre_ofbrand@ --- *                                             \
index 38f0097..214dbc1 100644 (file)
@@ -267,7 +267,7 @@ static const grand_ops grops = {
   "rc4",
   GRAND_CRYPTO, 0,
   grmisc, grdestroy,
-  grword, grbyte, grword, grand_range, grfill
+  grword, grbyte, grword, grand_defaultrange, grfill
 };
 
 /* --- @rc4_rand@ --- *
index c12945e..4b35cbd 100644 (file)
@@ -663,7 +663,7 @@ static void grdestroy(grand *r)
   static const grand_ops grops_rand_##rr = {                           \
     SALSA20_NAME_##rr, GRAND_CRYPTO, 0,                                        \
     grmisc, grdestroy, grword,                                         \
-    grbyte, grword, grand_range, grfill                                        \
+    grbyte, grword, grand_defaultrange, grfill                         \
   };                                                                   \
                                                                        \
   grand *SALSA20_DECOR(salsa20, rr, _rand)                             \
@@ -706,7 +706,7 @@ SALSA20_VARS(DEFGRAND)
   static const grand_ops grxops_rand_##rr = {                          \
     "x" SALSA20_NAME_##rr, GRAND_CRYPTO, 0,                            \
     grmisc, grxdestroy_##rr, grword,                                   \
-    grbyte, grword, grand_range, grfill                                        \
+    grbyte, grword, grand_defaultrange, grfill                         \
   };                                                                   \
                                                                        \
   grand *SALSA20_DECOR(xsalsa20, rr, _rand)                            \
index 57cfc10..0ba56c0 100644 (file)
@@ -529,7 +529,7 @@ static const grand_ops grops = {
   "seal",
   GRAND_CRYPTO, 0,
   grmisc, grdestroy,
-  grword, grbyte, grword, grand_range, grfill
+  grword, grbyte, grword, grand_defaultrange, grfill
 };
 
 /* --- @seal_rand@ --- *