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.
"bbs",
GRAND_CRYPTO, 0,
gmisc, gdestroy,
- gword, gbyte, gword, grand_range, grand_fill
+ gword, gbyte, gword, grand_defaultrange, grand_defaultfill
};
/* --- @bbs_rand@ --- *
"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@ --- *
#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@ --- *
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@ --- *
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@ --- *
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 -------------------------------------------------*/
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 --- */
/* --- 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)$% */
#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@ --- *
"lcrand",
LCRAND_P, 0,
gmisc, gdestroy,
- graw, gbyte, grand_word, grange, grand_fill
+ graw, gbyte, grand_defaultword, grange, grand_defaultfill
};
/* --- @lcrand_create@ --- *
"rand",
GRAND_CRYPTO, 0,
gmisc, gdestroy,
- gword, gbyte, gword, grand_range, gfill
+ gword, gbyte, gword, grand_defaultrange, gfill
};
/* --- @rand_create@ --- *
"<sslprf-dummy>",
GRAND_CRYPTO, 0,
grmisc, grdestroy,
- grword, grbyte, grword, grand_range, grfill
+ grword, grbyte, grword, grand_defaultrange, grfill
};
/* ---@sslprf_rand@ --- *
"<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@ --- *
"<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@ --- *
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) \
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) \
#pre "-counter", \
GRAND_CRYPTO, 0, \
grmisc, grdestroy, \
- grword, grbyte, grword, grand_range, grfill \
+ grword, grbyte, grword, grand_defaultrange, grfill \
}; \
\
/* --- @pre_counterrand@ --- * \
#pre "-mgf", \
GRAND_CRYPTO, 0, \
grmisc, grdestroy, \
- grword, grbyte, grword, grand_range, grfill \
+ grword, grbyte, grword, grand_defaultrange, grfill \
}; \
\
/* --- @pre_mgfrand@ --- * \
#pre "-ofb", \
GRAND_CRYPTO, 0, \
grmisc, grdestroy, \
- grword, grbyte, grword, grand_range, grfill \
+ grword, grbyte, grword, grand_defaultrange, grfill \
}; \
\
/* --- @pre_ofbrand@ --- * \
"rc4",
GRAND_CRYPTO, 0,
grmisc, grdestroy,
- grword, grbyte, grword, grand_range, grfill
+ grword, grbyte, grword, grand_defaultrange, grfill
};
/* --- @rc4_rand@ --- *
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) \
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) \
"seal",
GRAND_CRYPTO, 0,
grmisc, grdestroy,
- grword, grbyte, grword, grand_range, grfill
+ grword, grbyte, grword, grand_defaultrange, grfill
};
/* --- @seal_rand@ --- *