/**
** Simple entropy harvester based upon the havege RNG
**
** Copyright 2018-2022 Jirka Hladky hladky DOT jiri AT gmail DOT com
** Copyright 2009-2014 Gary Wuertz gary@issiweb.com
** Copyright 2011-2012 BenEleventh Consulting manolson@beneleventh.com
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
**
*/
/**
* This compile unit implements the havege algorithm as an inteface to
* either a single collector in the calling process or an interface to
* multiple collector processes (experimental).
*/
#include "config.h"
#include
#include
#include
#include
#include
#include "havegetest.h"
#include "havegetune.h"
/**
* The library version interface results in a pair of version definitions
* which must agree yet must also be string literals. No foolproof build
* mechanism could be devised to ensure this, so a run-time check was added
* instead - if the two definitions do not agree, the interface is diabled.
*/
#define INTERFACE_DISABLED() strcmp(PACKAGE_VERSION,HAVEGE_PREP_VERSION)
#if NUMBER_CORES>1
#include
#include
#include
#include
#include
/**
* Collection thread directory
*/
typedef struct {
pid_t main; /* the main thread */
H_UINT exit_ct; /* shutdown counter */
H_UINT count; /* associated count */
H_UINT last; /* last output */
H_UINT *out; /* buffer pointer */
H_UINT fatal; /* fatal error in last */
sem_t flags[1]; /* thread signals */
} H_THREAD;
/**
* Local prototypes
*/
static int havege_exit(H_PTR h_ptr);
static void havege_ipc(H_PTR h_ptr, H_UINT n, H_UINT sz);
static int havege_rngChild(H_PTR h_ptr, H_UINT cNumber);
static void havege_unipc(H_PTR h_ptr);
#endif
/**
* Main allocation
*/
#ifdef ONLINE_TESTS_ENABLE
typedef struct {
struct h_anchor info; /* Application anchor */
HOST_CFG cfg; /* Runtime environment */
procShared std; /* Shared test data */
} H_SETUP;
static int testsConfigure(H_UINT *tot, H_UINT *run, char *options);
static void testsStatus(procShared *tps, char *tot, char *prod);
static void testReport(H_COLLECT * h_ctxt, H_UINT action, H_UINT prod, H_UINT state, H_UINT ct);
static void testReportA(H_PTR h, procA *context);
static void testReportB(H_PTR h, procB *context);
#else
typedef struct {
struct h_anchor info; /* Application anchor */
HOST_CFG cfg; /* Runtime environment */
} H_SETUP;
#endif
/**
* Local prototypes
*/
static void havege_mute(const char *format, ...);
/**
* Initialize the environment based upon the tuning survey. This includes,
* allocation the output buffer (in shared memory if mult-threaded) and
* fitting the collection code to the tuning inputs.
*/
H_PTR havege_create( /* RETURN: app state */
H_PARAMS *params) /* IN: input params */
{
H_SETUP *anchor;
HOST_CFG *env;
H_PTR h = 0;
H_UINT n = params->nCores;
H_UINT sz = params->ioSz;
if (INTERFACE_DISABLED())
return NULL;
if (0 == n)
n = 1;
if (0 == sz)
sz = DEFAULT_BUFSZ;
anchor = (H_SETUP *)calloc(1, sizeof(H_SETUP));
if (NULL==anchor)
return h;
h = &anchor->info;
h->print_msg = params->msg_out==0? havege_mute : params->msg_out;
h->metering = params->metering;
env = &anchor->cfg;
havege_tune(env, params);
h->error = H_NOERR;
h->arch = ARCH;
h->inject = params->injection;
h->n_cores = n;
h->havege_opts = params->options;
h->i_collectSz = params->collectSize==0? NDSIZECOLLECT : params->collectSize;
h->i_readSz = sz;
h->tuneData = env;
h->cpu = &env->cpus[env->a_cpu];
h->instCache = &env->caches[env->i_tune];
h->dataCache = &env->caches[env->d_tune];
#ifdef ONLINE_TESTS_ENABLE
{
static const H_UINT tests[5] = {B_RUN, A_RUN};
H_UINT tot=0,run=0;
H_UINT i, j;
procShared *tps = (procShared *)&anchor->std;
if (testsConfigure(&tot, &run, params->testSpec)) {
h->error = H_NOTESTSPEC;
return h;
}
for(i=j=0;i<2;i++)
if (0!=(tot & tests[i])) {
tps->testsUsed |= tests[i];
tps->totTests[j].action = tests[i];
tps->totTests[j++].options = tot;
}
for(i=j=0;i<2;i++)
if (0!=(run & tests[i])) {
tps->testsUsed |= tests[i];
tps->runTests[j].action = tests[i];
tps->runTests[j++].options = run;
}
testsStatus(tps, tps->totText, tps->prodText);
tps->report = testReport;
h->testData = tps;
if (havege_test(tps, params)) {
h->error = H_NOTESTMEM;
return h;
}
}
#endif
#if NUMBER_CORES>1
havege_ipc(h, n, sz);
#else
h->io_buf = malloc(sz);
h->threads = NULL;
#endif
if (NULL==h->io_buf) {
h->error = H_NOBUF;
return h;
}
havege_ndsetup(h);
return h;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
void havege_reparent(
H_PTR hptr)
{
#if NUMBER_CORES>1
H_THREAD *t = (H_THREAD *)hptr->threads;
if (0 == t)
return; /* single-threaded */
t->main = getpid();
#endif
}
#pragma GCC diagnostic pop
/**
* Destructor. In a multi-collector build, this method should be called from a signal handler
* to avoid creating processes.
*/
void havege_destroy( /* RETURN: none */
H_PTR hptr) /* IN-OUT: app anchor */
{
if (NULL != hptr) {
H_COLLECT *htemp;
void *temp;
#if NUMBER_CORES>1
if (!havege_exit(hptr))
return; /* only main thread continues */
#endif
if (0 != (temp=hptr->io_buf)) {
hptr->io_buf = 0;
free(temp);
}
#ifdef ONLINE_TESTS_ENABLE
if (0 != (temp=hptr->testData)) {
double *g = ((procShared *)temp)->G;
hptr->testData = 0;
if (0 != g)
free(g);
}
#endif
if (0 != (htemp=hptr->collector)) {
hptr->collector = 0;
havege_nddestroy(htemp);
}
free(hptr);
}
}
/**
* Read random words. In the single-collector case, input is read by the calling
* the collection method directly. In the multi-collector case, the request info is
* signaled to the collector last read and the caller waits for a completion signal.
*/
int havege_rng( /* RETURN: number words read */
H_PTR h, /* IN-OUT: app state */
H_UINT *buffer, /* OUT: read buffer */
H_UINT sz) /* IN: number words to read */
{
#if NUMBER_CORES>1
H_THREAD *t = (H_THREAD *) h->threads;
t->count = sz;
t->out = buffer;
if (0!=sem_post(&t->flags[t->last]))
h->error = H_NORQST;
else if (0!=sem_wait(&t->flags[h->n_cores]))
h->error = H_NOCOMP;
else if (H_NOERR != t->fatal)
h->error = t->fatal;
#else
H_UINT i;
for(i=0;icollector);
h->error = ((H_COLLECT *)h->collector)->havege_err;
#endif
return h->error==(H_UINT)H_NOERR? (int) sz : -1;
}
/**
* Start the entropy collector.
*/
int havege_run( /* RETURN: NZ on failure */
H_PTR h) /* IN-OUT: app anchor */
{
int i = 0;
#if NUMBER_CORES>1
for(i = 0; i < h->n_cores;i++)
if (0 == havege_rngChild(h, i))
return 1;
#else
if (NULL==(h->collector = havege_ndcreate(h, i)))
return 1;
#endif
return 0;
}
/**
* Report concealed setup data
*/
void havege_status( /* RETURN: none */
H_PTR h_ptr, /* IN: app state */
H_STATUS h_sts) /* OUT: app state */
{
if (0 != h_sts) {
HOST_CFG *en = (HOST_CFG *) (h_ptr->tuneData);
CACHE_INST *cd = (CACHE_INST *)(h_ptr->dataCache);
CACHE_INST *ci = (CACHE_INST *)(h_ptr->instCache);
CPU_INST *cp = (CPU_INST *) (h_ptr->cpu);
procShared *ps = (procShared *)(h_ptr->testData);
h_sts->version = HAVEGE_PREP_VERSION;
h_sts->buildOptions = en->buildOpts;
h_sts->cpuSources = en->cpuOpts;
h_sts->i_cacheSources = en->icacheOpts;
h_sts->d_cacheSources = en->dcacheOpts;
h_sts->vendor = cp->vendor;
h_sts->d_cache = cd->size;
h_sts->i_cache = ci->size;
h_sts->tot_tests = (0 != ps)? ps->totText :"";
h_sts->prod_tests = (0 != ps)? ps->prodText :"";
if (0 != ps) {
memcpy(h_sts->n_tests, ps->meters, (H_OLT_PROD_B_P+1) * sizeof(H_UINT));
h_sts->last_test8 = ps->lastCoron;
}
}
}
/**
* Standard status presetations
*/
int havege_status_dump( /* RETURN: output length */
H_PTR hptr, /* IN: app state */
H_SD_TOPIC topic, /* IN: presentation topic */
char *buf, /* OUT: output area */
size_t len) /* IN: size of buf */
{
struct h_status status;
int n = 0;
if (buf != 0) {
*buf = 0;
len -= 1;
havege_status(hptr, &status);
switch(topic) {
case H_SD_TOPIC_BUILD:
n += snprintf(buf, len, "ver: %s; arch: %s; vend: %s; build: (%s); collect: %uK",
status.version,
hptr->arch,
status.vendor,
status.buildOptions,
hptr->i_collectSz/1024
);
break;
case H_SD_TOPIC_TUNE:
n += snprintf(buf, len, "cpu: (%s); data: %uK (%s); inst: %uK (%s); idx: %u/%u; sz: %u/%u",
status.cpuSources,
status.d_cache,
status.d_cacheSources,
status.i_cache,
status.i_cacheSources,
hptr->i_maxidx - hptr->i_idx, hptr->i_maxidx,
hptr->i_sz, hptr->i_maxsz
);
break;
case H_SD_TOPIC_TEST:
{
H_UINT m;
if (strlen(status.tot_tests)>0) {
n += snprintf(buf+n, len-n, "tot tests(%s): ", status.tot_tests);
if ((m = status.n_tests[ H_OLT_TOT_A_P] + status.n_tests[ H_OLT_TOT_A_F])>0)
n += snprintf(buf+n, len-n, "A:%u/%u ", status.n_tests[ H_OLT_TOT_A_P], m);
if ((m = status.n_tests[ H_OLT_TOT_B_P] + status.n_tests[ H_OLT_TOT_B_F])>0)
n += snprintf(buf+n, len, "B:%u/%u ", status.n_tests[ H_OLT_TOT_B_P], m);
}
if (strlen(status.prod_tests)>0) {
n += snprintf(buf+n, len-n, "continuous tests(%s): ", status.prod_tests);
if ((m = status.n_tests[ H_OLT_PROD_A_P] + status.n_tests[ H_OLT_PROD_A_F])>0)
n += snprintf(buf+n, len-n, "A:%u/%u ", status.n_tests[ H_OLT_PROD_A_P], m);
if ((m = status.n_tests[ H_OLT_PROD_B_P] + status.n_tests[ H_OLT_PROD_B_F])>0)
n += snprintf(buf+n, len, "B:%u/%u ", status.n_tests[ H_OLT_PROD_B_P], m);
}
if (n>0)
n += snprintf(buf+n, len-n, " last entropy estimate %g", status.last_test8);
}
break;
case H_SD_TOPIC_SUM:
{
char units[] = {'T', 'G', 'M', 'K', 0};
double factor[2];
factor[0] = 1024.0 * 1024.0 * 1024.0 * 1024.0;
factor[1] = factor[0];
double sz = ((double)hptr->n_fills * hptr->i_collectSz) * sizeof(H_UINT);
double ent = ((double) hptr->n_entropy_bytes);
int i[2];
for (i[0]=0;0 != units[i[0]];i[0]++) {
if (sz >= factor[0])
break;
factor[0] /= 1024.0;
}
for (i[1]=0;0 != units[i[1]];i[1]++) {
if (ent >= factor[1])
break;
factor[1] /= 1024.0;
}
n = snprintf(buf, len, "fills: %u, generated: %.4g %c bytes, RNDADDENTROPY: %.4g %c bytes",
hptr->n_fills,
sz / factor[0],
units[i[0]],
ent / factor[1],
units[i[1]]
);
}
break;
}
}
return n;
}
/**
* Return-check library prep version. Calling havege_version() with a NULL version
* returns the definition of HAVEGE_PREP_VERSION used to build the library. Calling
* with HAVEGE_PREP_VERSION as the version checks if this headers definition is
* compatible with that of the library, returning NULL if the input is incompatible
* with the library.
*/
const char *havege_version(const char *version)
{
if (INTERFACE_DISABLED())
return NULL;
/**
* Version check academic at the moment, but initial idea is to do a table
* lookup on the library version to get a pattern to match against the
* input version.
*/
if (version) {
H_UINT l_interface=0, l_revision=0, l_age=0;
H_UINT p, p_interface, p_revision, p_patch;
#ifdef HAVEGE_LIB_VERSION
sscanf(HAVEGE_LIB_VERSION, "%u:%u:%u", &l_interface, &l_revision, &l_age);
#endif
(void)l_interface;(void)l_revision;(void)l_age;(void)p_patch; /* No check for now */
p = sscanf(version, "%u.%u.%u", &p_interface, &p_revision, &p_patch);
if (p!=3 || p_interface != 1 || p_revision != 9)
return NULL;
}
return HAVEGE_PREP_VERSION;
}
/**
* Place holder if output display not provided
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
static void havege_mute( /* RETURN: none */
const char *format, /* IN: printf format */
...) /* IN: args */
{
;
}
#pragma GCC diagnostic pop
#if NUMBER_CORES > 1
/**
* Cleanup collector(s). In a multi-collector environment, need to kill
* children to avoid zombies.
*/
static int havege_exit( /* RETURN: NZ if child */
H_PTR h_ptr) /* IN: app state */
{
H_THREAD *t = (H_THREAD *)h_ptr->threads;
pid_t p;
H_UINT i;
if (0 == t)
return 0; /* Must be main thread */
t->fatal = H_EXIT;
for(i=0;iexit_ct;i++)
(void)sem_post(&t->flags[i]);
if (getpid() != t->main)
return 1;
do { /* Wait for children */
p = wait(NULL);
} while(p != -1 && errno != ECHILD);
for(i=0;iexit_ct;i++)
(void)sem_destroy(&t->flags[i]);
if (i==h_ptr->n_cores)
(void)sem_destroy(&t->flags[i]);
havege_unipc(h_ptr); /* unmap memory */
return 0;
}
/**
* Initialize IPC mechanism. This consists of setting up a shared memory area
* containing the output buffer and the collection thread directory.
*/
static void havege_ipc( /* RETURN: None */
H_PTR h, /* IN: app state */
H_UINT n, /* IN: number of threads */
H_UINT sz) /* IN: size of buffer */
{
void *m;
H_THREAD *t;
H_UINT m_sz;
int i;
if (n > NUMBER_CORES) {
h->error = H_NOCORES;
return;
}
m = mmap(NULL,
m_sz = sz + sizeof(H_THREAD) + n * sizeof(sem_t),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
0,
0);
if (m != MAP_FAILED) {
h->io_buf = m;
h->m_sz = m_sz;
t = (H_THREAD *)((char *) m + sz);
memset(t, 0, sizeof(H_THREAD));
t->main = getpid();
h->threads = t;
for(i=0;i<=n;i++)
if(sem_init(&t->flags[i],1,0) < 0) {
h->error = H_NOINIT;
break;
}
t->exit_ct = i;
}
}
/**
* Child harvester task. If task fails to start H_PTR::error will be set to reason.
* If task fails after start, H_THREAD::fatal will be set to the reason and a completion
* will be posted to prevent the main thread from hanging waiting for a response.
*/
static int havege_rngChild(/* RETURN: none */
H_PTR h_ptr, /* IN: app state */
H_UINT cNumber) /* IN: collector index */
{
H_COLLECT *h_ctxt = 0;
H_THREAD *thds = (H_THREAD *) h_ptr->threads;
H_UINT cNext, i, r;
int pid;
switch(pid=fork()) {
case 0:
h_ctxt = havege_ndcreate(h_ptr, cNumber);
if (NULL != h_ctxt) {
cNext = (cNumber + 1) % h_ptr->n_cores;
while(1) {
if (0!=sem_wait(&thds->flags[cNumber])) {
thds->fatal = H_NOWAIT;
break;
}
if (H_NOERR != thds->fatal) {
havege_nddestroy(h_ctxt);
exit(0);
}
thds->last = cNumber;
r = h_ctxt->havege_szFill - h_ctxt->havege_nptr;
if (thds->count < r)
r = thds->count;
for(i=0;iout[i] = havege_ndread(h_ctxt);
thds->fatal = h_ctxt->havege_err;
if (0==(thds->count -= i)) {
if (0!=sem_post(&thds->flags[h_ptr->n_cores])) {
thds->fatal = H_NODONE;
break;
}
continue;
}
thds->out += i;
if (0!=sem_post(&thds->flags[cNext])) {
thds->fatal = H_NOPOST;
break;
}
#ifdef HAVE_SCHED_YIELD
(void)sched_yield();
#endif
(void)havege_ndread(h_ctxt);
h_ctxt->havege_nptr = 0;
}
havege_nddestroy(h_ctxt);
}
else thds->fatal = h_ptr->error; /* h_ptr is a copy!! */
(void)sem_post(&thds->flags[h_ptr->n_cores]); /* announce death! */
break;
case -1:
h_ptr->error = H_NOTASK;
}
return pid;
}
/**
* Unmap memory
*/
static void havege_unipc( /* RETURN: none */
H_PTR h_ptr) /* IN: app state */
{
if (0 != h_ptr->m_sz) {
munmap(h_ptr->io_buf, h_ptr->m_sz);
h_ptr->io_buf = 0;
}
}
#endif
#ifdef ONLINE_TESTS_ENABLE
/**
* Interpret options string as settings. The option string consists of terms
* like "[t|c][a[1-8][w]|b[w]]".
*/
static int testsConfigure( /* RETURN: non-zero on error */
H_UINT *tot, /* OUT: tot test options */
H_UINT *run, /* OUT: run test options */
char *options) /* IN: option string */
{
H_UINT section=0;
int c;
if (options==0)
options = DEFAULT_TEST_OPTIONS;
while(0 != (c = *options++)) {
switch(c) {
case 'T': case 't': /* tot test */
section = 't';
*tot = 0;
break;
case 'C': case 'c': /* production test */
section = 'c';
*run = 0;
break;
case 'A': case 'a':
if (!section) return 1;
c = atoi(options);
if (c >= 1 && c < 9) {
c = 1<totTests;
for(i=0;i<2;i++,p = tps->runTests, dst = prod) {
for(j=0;j<2;j++,p++) {
switch(p->action) {
case A_RUN:
*dst++ = 'A';
if (0!=(m = p->options & A_CYCLE)) {
for(k=0;m>>=1 != 0;k++);
*dst++ = '0' + k;
}
if (0 != (p->options & A_WARN))
*dst++ = 'w';
break;
case B_RUN:
*dst++ = 'B';
if (0 != (p->options & B_WARN))
*dst++ = 'w';
break;
}
*dst = 0;
}
}
}
/**
* Reporting unit for tests
*/
static void testReport(
H_COLLECT * h_ctxt, /* IN-OUT: collector context */
H_UINT action, /* IN: A_RUN or B_RUN */
H_UINT prod, /* IN: 0==tot, else continuous */
H_UINT state, /* IN: state variable */
H_UINT ct) /* IN: bytes consumed */
{
H_PTR h_ptr = (H_PTR)(h_ctxt->havege_app);
onlineTests *context = (onlineTests *) h_ctxt->havege_tests;
char *result;
switch(state) {
case TEST_DONE: result = "success"; break;
case TEST_RETRY: result = "retry"; break;
case TEST_IGNORE: result = "warning"; break;
default: result = "failure";
}
h_ptr->print_msg("AIS-31 %s procedure %s: %s %d bytes fill %d\n",
prod==0? "tot" : "continuous", action==A_RUN? "A":"B", result, ct, h_ptr->n_fills);
if (0 != (h_ptr->havege_opts & (H_DEBUG_OLTR|H_DEBUG_OLT)))
switch(action){
case A_RUN:
testReportA(h_ptr, context->pA);
break;
case B_RUN:
testReportB(h_ptr, context->pB);
break;
}
}
/**
* Reporting unit for procedure A. Results are 0-257*(1-[4 or 5])
*/
static void testReportA( /* RETURN: nothing */
H_PTR h_ptr, /* IN: application instance */
procA *p) /* IN: proc instance */
{
static const char * pa_tests[6] = {"test0","test1","test2","test3","test4","test5"};
H_UINT ran[6],sum[6];
H_UINT ct, i, j, k;
for (i=0;i<6;i++)
ran[i] = sum[i] = 0;
for(i=0;itestRun;i++){
ct = p->results[i].testResult;
j = ct>>8;
ran[j] += 1;
if (0==(ct & 0xff))
sum[j] += 1;
}
h_ptr->print_msg("procedure A: %s:%d/%d, %s:%d/%d, %s:%d/%d, %s:%d/%d, %s:%d/%d, %s:%d/%d\n",
pa_tests[0], sum[0], ran[0],
pa_tests[1], sum[1], ran[1],
pa_tests[2], sum[2], ran[2],
pa_tests[3], sum[3], ran[3],
pa_tests[4], sum[4], ran[4],
pa_tests[5], sum[5], ran[5]
);
for(i=k=0;itestRun;i++){
ct = p->results[i].testResult;
j = ct>>8;
if (j==1)
k+=1;
if (0!=(ct & 0xff))
h_ptr->print_msg(" %s[%d] failed with %d\n", pa_tests[j%6],k,p->results[i].finalValue);
}
}
/**
* Reporting unit for procedure B. Results are 6a-6b-7a[0]-7a[1]-7b[0]-7b[1]-7b[2]-7b[3]-8
*/
static void testReportB( /* RETURN: nothing */
H_PTR h_ptr, /* IN: application instance */
procB *p) /* IN: proc instance */
{
static const char * pb_tests[5] = {"test6a","test6b","test7a","test7b","test8"};
H_UINT ct, i, j, ran[5],sum[5];
for (i=0;i<5;i++) {
ran[i] = sum[i] = 0;
}
for(i=0;itestNbr;i++){
ct = p->results[i].testResult;
j = ct>>8;
ran[j] += 1;
if (0==(ct & 0xff))
sum[j] += 1;
}
h_ptr->print_msg("procedure B: %s:%d/%d, %s:%d/%d, %s:%d/%d, %s:%d/%d, %s:%d/%d\n",
pb_tests[0], sum[0], ran[0],
pb_tests[1], sum[1], ran[1],
pb_tests[2], sum[2], ran[2],
pb_tests[3], sum[3], ran[3],
pb_tests[4], sum[4], ran[4]
);
for(i=0;i<5;i++)
ran[i] = p->testNbr;
for(i=0;itestNbr;i++){
ct = p->results[i].testResult;
j = ct>>8;
if (i < ran[j]) ran[j] = i;
if (0!=(ct & 0xff))
h_ptr->print_msg(" %s[%d] failed with %g\n", pb_tests[j],i-ran[j],p->results[i].finalValue);
}
}
#endif