* This file is part of the openHiTLS project.
*
* openHiTLS is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
* Raw noise-sample collection for SP800-90B / AIS 31 evidence. The entropy
* source implementation files are #included so the tool reaches the internal
* measurement paths; the production library carries no collection code.
*
* Raw symbol: D_i = tick2_i - tick1_i, the raw 64-bit delta the conditioner
* absorbs (little-endian, 8 bytes). Assessment runs on b_i = D_i mod 256.
*
* Output is headerless:
* u64 - little-endian uint64 raw deltas (archive)
* lsb8 - one 8-bit assessment symbol per byte; also writes <outfile>.u64
* and reports u64-domain health maxima on stderr
*
* Usage: es_raw_dump <jitter|hashloop|blocks> <seq|restart> <outfile> [count] [u64|lsb8]
* seq: count = samples, default 2000000
* restart: count = rows, default 1000; fresh source state per row,
* 1000 samples per row (ea_restart matrix layout)
* blocks: production-path capture of the conditioner input through the
* linked library (seq, lsb8 only; count = runtime bytes; writes
* .startup / .journal.csv / .blocks.csv)
* Timing uses the production ES_NsTickGet path.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <time.h>
#include "hitls_build.h"
#ifndef __FILENAME__
#define __FILENAME__ "es_raw_dump.c"
#endif
#if !defined(HITLS_CRYPTO_ENTROPY) || !defined(HITLS_CRYPTO_ENTROPY_SYS) || \
(!defined(HITLS_CRYPTO_ENTROPY_NS_CPUJITTER) && !defined(HITLS_CRYPTO_ENTROPY_NS_HASHLOOP))
int main(void)
{
fprintf(stderr, "es_raw_dump: build with the entropy feature macros of the target library "
"(HITLS_CRYPTO_ENTROPY, HITLS_CRYPTO_ENTROPY_SYS, HITLS_CRYPTO_ENTROPY_NS_*)\n");
return 1;
}
#else
static, and the #define blocks pin OSR/OSR_MAX to the evaluated profile
independent of the library build flags. */
#include "../../crypto/entropy/src/es_health_test.c"
#include "../../crypto/entropy/src/es_ns_delta_common.c"
same source revision as the ES_NoiseSource layout compiled above; linking it
from a stale libhitls_crypto build reads shifted struct fields. */
#include "../../crypto/entropy/src/es_noise_source.c"
builds and gates the library-side blocks capture. */
#define RAW_EVIDENCE_OSR 3U
#ifdef HITLS_CRYPTO_ENTROPY_NS_CPUJITTER
#undef HITLS_JITTER_OSR
#undef HITLS_JITTER_OSR_MAX
#define HITLS_JITTER_OSR RAW_EVIDENCE_OSR
#define HITLS_JITTER_OSR_MAX RAW_EVIDENCE_OSR
#include "../../crypto/entropy/src/es_ns_jitter.c"
#undef HITLS_JITTER_OSR_MAX
#undef HITLS_JITTER_OSR
#endif
#ifdef HITLS_CRYPTO_ENTROPY_NS_HASHLOOP
#undef HITLS_HASHLOOP_OSR
#undef HITLS_HASHLOOP_OSR_MAX
#define HITLS_HASHLOOP_OSR RAW_EVIDENCE_OSR
#define HITLS_HASHLOOP_OSR_MAX RAW_EVIDENCE_OSR
#include "../../crypto/entropy/src/es_ns_hashloop.c"
#undef HITLS_HASHLOOP_OSR_MAX
#undef HITLS_HASHLOOP_OSR
#endif
#ifdef HITLS_CRYPTO_ENTROPY_NS_HASHLOOP
#include "crypt_eal_entropy.h"
#include "crypt_types.h"
#include "es_cf.h"
#include "es_entropy_local.h"
#include "../../crypto/eal/src/eal_entropy.h"
#endif
#define RAW_RESTART_ROW_SAMPLES 1000
#define RAW_SEQ_DEFAULT_SAMPLES 2000000ULL
#define RAW_RESTART_DEFAULT_ROWS 1000ULL
#define RAW_IO_CHUNK 4096
#define RAW_INIT_MAX_ATTEMPTS 50
typedef struct {
int32_t (*newState)(void **state);
void (*freeState)(void *state);
uint64_t (*sample)(void *state);
ES_NoiseSource *(*newNs)(void);
int32_t (*verdict)(void *state);
} RawSource;
#ifdef HITLS_CRYPTO_ENTROPY_NS_CPUJITTER
Intermittent verdicts leave the stream untouched (no osr walk, no startup
test), so recording every measurement matches the credited population; a
permanent latch zeroizes further deltas, so the dump aborts instead of
archiving zeros. */
static int32_t RawJitterNew(void **state)
{
ES_JitterState *e = NULL;
int32_t ret = JitterStateNew(&e);
if (ret != CRYPT_SUCCESS) {
return ret;
}
fprintf(stderr, "jitter memSize=%u (l1d probe=%u)\n", e->memSize,
ES_CpuJitterL1dCacheSize());
*state = e;
return CRYPT_SUCCESS;
}
static uint64_t RawJitterSample(void *state)
{
ES_JitterState *e = (ES_JitterState *)state;
JitterMeasure(e);
return e->ns.lastDelta;
}
static int32_t RawJitterVerdict(void *state)
{
return ((ES_JitterState *)state)->ns.testFailure;
}
static const RawSource g_rawJitter = {
RawJitterNew, ES_CpuJitterFree, RawJitterSample, ES_CpuJitterGetCtx, RawJitterVerdict
};
#endif
#ifdef HITLS_CRYPTO_ENTROPY_NS_HASHLOOP
same verdict handling as the jitter source above.
Iteration count is HITLS_HASHLOOP_ITERATIONS. */
static int32_t RawHashLoopNew(void **state)
{
ES_HashLoopState *e = NULL;
int32_t ret = HashLoopStateNew(&e);
if (ret != CRYPT_SUCCESS) {
return ret;
}
*state = e;
return CRYPT_SUCCESS;
}
static uint64_t RawHashLoopSample(void *state)
{
ES_HashLoopState *e = (ES_HashLoopState *)state;
HashLoopMeasure(e);
return e->ns.lastDelta;
}
static int32_t RawHashLoopVerdict(void *state)
{
return ((ES_HashLoopState *)state)->ns.testFailure;
}
static const RawSource g_rawHashLoop = {
RawHashLoopNew, ES_HashLoopFree, RawHashLoopSample, ES_HashLoopGetCtx, RawHashLoopVerdict
};
#endif
production engine with every cutoff widened past reach, so the archived
maxima come from the shipped predicates themselves rather than a mirror. */
typedef struct {
ES_DeltaNs eng;
uint32_t eqRunMax;
uint32_t stuckRunMax;
uint32_t aptMax;
} RawHealthStat;
static void RawHealthReset(RawHealthStat *hs)
{
ES_DeltaNsStartupReset(&hs->eng);
ES_DeltaNsOsrApply(&hs->eng, RAW_EVIDENCE_OSR);
hs->eng.stuckCutoff = UINT32_MAX;
hs->eng.rct.cutoff = UINT32_MAX;
hs->eng.apt.cutoff = UINT32_MAX;
hs->eng.rctPermCutoff = UINT32_MAX;
hs->eng.aptPermCutoff = UINT32_MAX;
}
static void RawHealthUpdate(RawHealthStat *hs, uint64_t d)
{
ES_DeltaNsProcessRawDelta(&hs->eng, d);
if (hs->eng.rct.count > hs->eqRunMax) {
hs->eqRunMax = hs->eng.rct.count;
}
if (hs->eng.stuckCount > hs->stuckRunMax) {
hs->stuckRunMax = hs->eng.stuckCount;
}
if (hs->eng.apt.count > hs->aptMax) {
hs->aptMax = hs->eng.apt.count;
}
}
static void RawHealthPrint(const RawHealthStat *hs)
{
assessed 8-bit symbol domain; the stuck run stays on the full delta. */
fprintf(stderr, "health: eq-run max=%u (lsb8), stuck-run max=%u (u64), apt512 max=%u (lsb8)\n",
hs->eqRunMax, hs->stuckRunMax, hs->aptMax);
}
static int WriteSamples(const RawSource *src, void *state, FILE *fp, FILE *fu, RawHealthStat *hs,
uint64_t n, int u64Out)
{
uint8_t u64Buf[RAW_IO_CHUNK * sizeof(uint64_t)];
uint8_t byteBuf[RAW_IO_CHUNK];
uint64_t done = 0;
while (done < n) {
uint32_t chunk = (uint32_t)((n - done > RAW_IO_CHUNK) ? RAW_IO_CHUNK : (n - done));
for (uint32_t i = 0; i < chunk; i++) {
uint64_t delta = src->sample(state);
this chunk is written so no zeroized record reaches the
archive. */
if (src->verdict(state) == NS_ENTROPY_PERMANENT_FAILURE) {
fprintf(stderr, "permanent health verdict after %llu samples: "
"the engine zeroizes further deltas, aborting the dump\n",
(unsigned long long)(done + i));
return 1;
}
RawHealthUpdate(hs, delta);
absorbs, so archives stay comparable across platforms. */
PUT_UINT64_LE(delta, u64Buf, i * sizeof(uint64_t));
if (!u64Out) {
byteBuf[i] = (uint8_t)(delta & 0xff);
}
}
size_t written = u64Out ? fwrite(u64Buf, sizeof(uint64_t), chunk, fp)
: fwrite(byteBuf, 1, chunk, fp);
if (written != chunk) {
perror("fwrite");
return 1;
}
if (fu != NULL && fwrite(u64Buf, sizeof(uint64_t), chunk, fu) != chunk) {
perror("fwrite");
return 1;
}
done += chunk;
}
return 0;
}
static int DumpSequential(const RawSource *src, FILE *fp, FILE *fu, uint64_t samples, int u64Out)
{
void *state = NULL;
int32_t initRet = src->newState(&state);
if (initRet != CRYPT_SUCCESS) {
fprintf(stderr, "state initialization failed: 0x%x\n", (unsigned)initRet);
return 1;
}
RawHealthStat hs = {0};
RawHealthReset(&hs);
int ret = WriteSamples(src, state, fp, fu, &hs, samples, u64Out);
src->freeState(state);
if (ret == 0) {
fprintf(stderr, "seq done: %llu samples\n", (unsigned long long)samples);
RawHealthPrint(&hs);
}
return ret;
}
#ifdef HITLS_CRYPTO_ENTROPY_NS_HASHLOOP
registration order, via a wrapper around this instance's conditioner
update, so the recorded bytes are exactly what EsCollect hands Hash_df.
EsInit leaves one block in the pool, so the first Get may drain it without
a gather (journal row got=32, len=0). Build library and tool from one
revision and keep hidden visibility on, or ELF binding lets the in-TU
copies preempt the library's entropy symbols (osrMax 3 = tool copy). */
#define BLOCKS_GET_LEN 32U
#define BLOCKS_MAX_TXN 262144U
#define BLOCKS_INIT_ATTEMPTS 3U
#define BLOCKS_ERR_CODES 4U
typedef struct {
char kind;
uint64_t start;
uint64_t end;
int64_t got;
} BlkTxn;
static uint8_t *g_blkBuf;
static uint64_t g_blkLen;
static uint64_t g_blkSize;
static uint64_t g_blkDropped;
static int32_t (*g_blkOrigUpdate)(void *ctx, uint8_t *data, uint32_t dataLen);
static uint64_t g_blkLogFails;
static int32_t g_blkErrCodes[BLOCKS_ERR_CODES];
static uint32_t g_blkErrCount;
static int32_t BlkCaptureUpdate(void *ctx, uint8_t *data, uint32_t dataLen)
{
if (g_blkLen + dataLen <= g_blkSize) {
(void)memcpy(g_blkBuf + g_blkLen, data, dataLen);
g_blkLen += dataLen;
} else {
g_blkDropped += dataLen;
}
return g_blkOrigUpdate(ctx, data, dataLen);
}
order reconstruct the block boundaries. Each read method is wrapped per
instance; the wrapper dispatches on usrdata. */
#define BLOCKS_MAX_BLOCKS 65536U
typedef struct {
void *usrdata;
const char *name;
int32_t (*orig)(void *usrdata, uint32_t timeout, uint8_t *buf, uint32_t bufLen);
} BlkSourceHook;
typedef struct {
const char *name;
uint64_t bytes;
} BlkBlock;
static BlkSourceHook g_blkHooks[ES_NS_MAX_SIZE];
static uint32_t g_blkHookCount;
static BlkBlock g_blkBlocks[BLOCKS_MAX_BLOCKS];
static uint32_t g_blkBlockCount;
static const char *g_blkActive;
static int g_blkBlockOverflow;
static int32_t BlkCaptureRead(void *usrdata, uint32_t timeout, uint8_t *buf, uint32_t bufLen)
{
BlkSourceHook *hook = NULL;
for (uint32_t i = 0; i < g_blkHookCount; i++) {
if (g_blkHooks[i].usrdata == usrdata) {
hook = &g_blkHooks[i];
break;
}
}
if (hook == NULL) {
return CRYPT_ENTROPY_ES_NS_NOT_AVA;
}
int32_t ret = hook->orig(usrdata, timeout, buf, bufLen);
if (ret != CRYPT_SUCCESS) {
return ret;
}
if (g_blkBlockOverflow) {
return ret;
}
if (g_blkActive != hook->name) {
if (g_blkBlockCount >= BLOCKS_MAX_BLOCKS) {
g_blkBlockOverflow = 1;
return ret;
}
g_blkActive = hook->name;
g_blkBlocks[g_blkBlockCount++] = (BlkBlock){hook->name, 0};
}
if (g_blkBlockCount > 0) {
g_blkBlocks[g_blkBlockCount - 1].bytes += bufLen;
}
return ret;
}
short Get alone cannot convey. Success returns immediately so the per-byte
cost inside EsCollect stays one compare. */
static void BlkRunLog(int32_t ret)
{
if (ret == CRYPT_SUCCESS) {
return;
}
g_blkLogFails++;
for (uint32_t i = 0; i < g_blkErrCount; i++) {
if (g_blkErrCodes[i] == ret) {
return;
}
}
if (g_blkErrCount < BLOCKS_ERR_CODES) {
g_blkErrCodes[g_blkErrCount++] = ret;
}
}
static int BlkWriteRange(FILE *fp, uint64_t start, uint64_t end)
{
return fwrite(g_blkBuf + start, 1, end - start, fp) == (end - start) ? 0 : 1;
}
static int DumpBlocks(const char *outfile, uint64_t targetBytes)
{
int rc = 1;
BlkTxn *journal = NULL;
CRYPT_EAL_Es *es = NULL;
FILE *fs = NULL;
FILE *fr = NULL;
FILE *fj = NULL;
uint32_t txnCount = 0;
the target plus startup/overshoot slack. */
g_blkSize = targetBytes + (1U << 20);
and undersize the buffer the 64-bit bound check then overruns. */
if (g_blkSize > SIZE_MAX / 2) {
fprintf(stderr, "capture size %llu exceeds the addressable buffer\n",
(unsigned long long)g_blkSize);
goto done;
}
g_blkBuf = malloc((size_t)g_blkSize);
journal = malloc(sizeof(BlkTxn) * BLOCKS_MAX_TXN);
if (g_blkBuf == NULL || journal == NULL) {
fprintf(stderr, "allocation failed\n");
goto done;
}
inside EsCollect between noise samples. */
(void)memset(g_blkBuf, 0, g_blkSize);
es = CRYPT_EAL_EsNew();
if (es == NULL) {
fprintf(stderr, "CRYPT_EAL_EsNew failed\n");
goto done;
}
int32_t ret = CRYPT_EAL_EsCtrl(es, CRYPT_ENTROPY_SET_CF, "sha3_256_df", strlen("sha3_256_df"));
fprintf(stderr, "set cf sha3_256_df: 0x%x\n", (unsigned)ret);
if (ret != CRYPT_SUCCESS || es->es == NULL || es->es->cfMeth == NULL ||
es->es->cfMeth->update == NULL) {
fprintf(stderr, "conditioner method not ready\n");
goto done;
}
ret = CRYPT_EAL_EsCtrl(es, CRYPT_ENTROPY_SET_LOG_CALLBACK, (void *)BlkRunLog, 0);
if (ret != CRYPT_SUCCESS) {
fprintf(stderr, "set log callback: 0x%x (continuing without it)\n", (unsigned)ret);
}
g_blkOrigUpdate = es->es->cfMeth->update;
es->es->cfMeth->update = BlkCaptureUpdate;
of any seed that fed production; discard and retry on a clean offset. */
for (uint32_t attempt = 1; attempt <= BLOCKS_INIT_ATTEMPTS; attempt++) {
g_blkLen = 0;
ret = CRYPT_EAL_EsInit(es);
if (ret == CRYPT_SUCCESS) {
break;
}
fprintf(stderr, "init attempt %u failed: 0x%x\n", attempt, (unsigned)ret);
}
if (ret != CRYPT_SUCCESS) {
goto done;
}
journal[0] = (BlkTxn){'I', 0, g_blkLen, -1};
txnCount = 1;
fprintf(stderr, "init ok, startup capture %llu bytes\n", (unsigned long long)g_blkLen);
their codes were printed per attempt above. */
g_blkLogFails = 0;
g_blkErrCount = 0;
if (es->es->cfMeth->update != BlkCaptureUpdate) {
fprintf(stderr, "wrapper displaced by init, aborting\n");
goto done;
}
the conditioner output block; verify instead of assuming. */
uint32_t cfLen = 0;
if (CRYPT_EAL_EsCtrl(es, CRYPT_ENTROPY_GET_CF_SIZE, &cfLen, sizeof(cfLen)) != CRYPT_SUCCESS ||
cfLen != BLOCKS_GET_LEN) {
fprintf(stderr, "cf output length %u mismatches BLOCKS_GET_LEN %u\n", cfLen, BLOCKS_GET_LEN);
goto done;
}
constructor produced the record: the library's own ladder bound versus
the OSR-pinned in-TU copy this file also compiles for the per-source
modes. The evidence log therefore states the profile it measured. */
uint32_t creditedCount = 0;
for (BslListNode *node = BSL_LIST_FirstNode(es->es->nsList); node != NULL;
node = BSL_LIST_GetNextNode(es->es->nsList, node)) {
ES_NoiseSource *ns = BSL_LIST_GetData(node);
fprintf(stderr, "source: %s credited=%d enabled=%d osr=%u osrMax=%u\n",
ns->name, (int)ns->credited, (int)ns->isEnable, ns->osr, ns->osrMax);
than before CRYPT_EAL_EsInit; startup blocks are consequently not
attributed, which the .startup file already separates out. */
if (ns->usrdata != NULL && ns->read != NULL && g_blkHookCount < ES_NS_MAX_SIZE) {
g_blkHooks[g_blkHookCount] = (BlkSourceHook){ns->usrdata, ns->name, ns->read};
g_blkHookCount++;
ns->read = BlkCaptureRead;
}
if (ns->credited && ns->isEnable) {
creditedCount++;
}
if (ns->credited && ns->osr != RAW_EVIDENCE_OSR) {
fprintf(stderr, "credited source settled at osr %u; the evidence profile requires %u\n",
ns->osr, RAW_EVIDENCE_OSR);
goto done;
}
}
a library carrying only one credited source must not silently yield a
single-source stream under the same file name. */
if (creditedCount != 2U) {
fprintf(stderr, "%u credited sources active; the dual-source evidence profile requires 2\n",
creditedCount);
goto done;
}
uint64_t runtimeBytes = 0;
uint32_t voided = 0;
while (runtimeBytes < targetBytes && txnCount < BLOCKS_MAX_TXN) {
uint8_t out[BLOCKS_GET_LEN];
BlkTxn *txn = &journal[txnCount];
*txn = (BlkTxn){'G', g_blkLen, 0, 0};
uint32_t got = CRYPT_EAL_EsEntropyGet(es, out, BLOCKS_GET_LEN);
txn->end = g_blkLen;
txn->got = (int64_t)got;
txnCount++;
if (got == BLOCKS_GET_LEN) {
runtimeBytes += txn->end - txn->start;
} else {
voided++;
break;
}
}
the captured stream and the block journal describing different byte
ranges; discard the run instead of writing misaligned evidence. */
if (voided != 0 || g_blkLogFails != 0 || g_blkBlockOverflow) {
fprintf(stderr, "blocks: evidence discarded, %u voided transactions, %llu library failures, "
"block table overflow=%d", voided, (unsigned long long)g_blkLogFails, g_blkBlockOverflow);
for (uint32_t i = 0; i < g_blkErrCount; i++) {
fprintf(stderr, "%s0x%x", i == 0 ? ", codes: " : " ", (unsigned)g_blkErrCodes[i]);
}
fprintf(stderr, "\n");
goto done;
}
char path[4096];
if (strlen(outfile) + sizeof(".journal.csv") > sizeof(path)) {
fprintf(stderr, "outfile path too long\n");
goto done;
}
(void)snprintf(path, sizeof(path), "%s.startup", outfile);
fs = fopen(path, "wb");
fr = fopen(outfile, "wb");
(void)snprintf(path, sizeof(path), "%s.journal.csv", outfile);
fj = fopen(path, "w");
if (fs == NULL || fr == NULL || fj == NULL) {
fprintf(stderr, "output open failed\n");
goto done;
}
fprintf(fj, "idx,kind,start,end,len,got\n");
int werr = 0;
uint64_t txnMin = UINT64_MAX;
uint64_t txnMax = 0;
uint64_t txnSum = 0;
uint32_t txnOk = 0;
for (uint32_t i = 0; i < txnCount; i++) {
const BlkTxn *t = &journal[i];
uint64_t len = t->end - t->start;
fprintf(fj, "%u,%c,%llu,%llu,%llu,%lld\n", i, t->kind, (unsigned long long)t->start,
(unsigned long long)t->end, (unsigned long long)len, (long long)t->got);
if (t->kind == 'I') {
werr |= BlkWriteRange(fs, t->start, t->end);
} else {
werr |= BlkWriteRange(fr, t->start, t->end);
if (len > 0) {
txnOk++;
txnSum += len;
txnMin = len < txnMin ? len : txnMin;
txnMax = len > txnMax ? len : txnMax;
}
}
}
order. Offsets are absolute in <outfile>.startup + <outfile> as
captured; startup blocks are unattributed (hooks install after init). */
(void)snprintf(path, sizeof(path), "%s.blocks.csv", outfile);
FILE *fb = fopen(path, "w");
if (fb == NULL) {
fprintf(stderr, "block journal open failed\n");
werr |= 1;
} else {
fprintf(fb, "idx,source,start,len\n");
uint64_t off = journal[0].end;
for (uint32_t i = 0; i < g_blkBlockCount; i++) {
fprintf(fb, "%u,%s,%llu,%llu\n", i, g_blkBlocks[i].name,
(unsigned long long)off, (unsigned long long)g_blkBlocks[i].bytes);
off += g_blkBlocks[i].bytes;
}
werr |= fclose(fb) != 0;
}
werr |= fclose(fs) != 0;
werr |= fclose(fr) != 0;
werr |= fclose(fj) != 0;
fs = NULL;
fr = NULL;
fj = NULL;
fprintf(stderr, "transactions: %u total, %u gather-backed ok, dropped=%llu\n",
txnCount, txnOk, (unsigned long long)g_blkDropped);
if (txnOk > 0) {
fprintf(stderr, "gather size bytes: min=%llu avg=%llu max=%llu\n",
(unsigned long long)txnMin, (unsigned long long)(txnSum / txnOk),
(unsigned long long)txnMax);
}
short run fail the exit code just like a failed write. */
rc = (werr != 0 || g_blkDropped != 0 || runtimeBytes < targetBytes) ? 1 : 0;
fprintf(stderr, "blocks: %u recorded", g_blkBlockCount);
if (g_blkBlockCount > 0) {
uint64_t bmin = UINT64_MAX, bmax = 0, bsum = 0;
for (uint32_t i = 0; i < g_blkBlockCount; i++) {
uint64_t v = g_blkBlocks[i].bytes;
bsum += v;
bmin = v < bmin ? v : bmin;
bmax = v > bmax ? v : bmax;
}
fprintf(stderr, ", bytes min=%llu avg=%llu max=%llu",
(unsigned long long)bmin, (unsigned long long)(bsum / g_blkBlockCount),
(unsigned long long)bmax);
}
fprintf(stderr, "\n");
fprintf(stderr, "blocks production done: startup=%llu runtime=%llu bytes, %s\n",
(unsigned long long)journal[0].end, (unsigned long long)runtimeBytes,
rc == 0 ? "ok" : "FAILED");
done:
if (fs != NULL) {
(void)fclose(fs);
}
if (fr != NULL) {
(void)fclose(fr);
}
if (fj != NULL) {
(void)fclose(fj);
}
if (es != NULL) {
CRYPT_EAL_EsFree(es);
}
free(g_blkBuf);
g_blkBuf = NULL;
free(journal);
return rc;
}
#endif
static void FreeDetachedNs(ES_NoiseSource *ns)
{
if (ns == NULL) {
return;
}
if (ns->usrdata != NULL && ns->deinit != NULL) {
ns->deinit(ns->usrdata);
}
BSL_SAL_Free(ns->name);
BSL_SAL_Free(ns);
}
static BslList *InitProductionSource(const RawSource *src, ES_NoiseSource **outNs, uint32_t *attempts,
int32_t *lastRet)
{
*lastRet = CRYPT_SUCCESS;
for (uint32_t attempt = 0; attempt < RAW_INIT_MAX_ATTEMPTS; attempt++) {
BslList *list = BSL_LIST_New(sizeof(BslListNode));
ES_NoiseSource *ns = src->newNs();
(*attempts)++;
if (list == NULL) {
*lastRet = CRYPT_MEM_ALLOC_FAIL;
FreeDetachedNs(ns);
continue;
}
if (ns == NULL) {
*lastRet = CRYPT_MEM_ALLOC_FAIL;
ES_NsListFree(list);
continue;
}
int32_t ret = BSL_LIST_AddElement(list, ns, BSL_LIST_POS_END);
if (ret != CRYPT_SUCCESS) {
*lastRet = ret;
FreeDetachedNs(ns);
ES_NsListFree(list);
continue;
}
ret = ES_NsListInit(list, true);
if (ret == CRYPT_SUCCESS) {
*outNs = ns;
return list;
}
*lastRet = ret;
ES_NsListFree(list);
}
return NULL;
}
static int DumpRestart(const RawSource *src, FILE *fp, FILE *fu, uint64_t rows, int u64Out)
{
RawHealthStat hs = {0};
RawHealthReset(&hs);
uint64_t totalAttempts = 0;
uint64_t osrCounts[NS_DELTA_OSR_MAX + 1] = {0};
for (uint64_t row = 0; row < rows; row++) {
ES_NoiseSource *ns = NULL;
uint32_t attempts = 0;
int32_t lastRet = CRYPT_SUCCESS;
BslList *list = InitProductionSource(src, &ns, &attempts, &lastRet);
totalAttempts += attempts;
if (list == NULL) {
fprintf(stderr, "row %llu: production source initialization failed after %u attempts, last ret=0x%x\n",
(unsigned long long)row, attempts, (unsigned)lastRet);
return 1;
}
uint32_t osr = ns->osr;
if (osr <= NS_DELTA_OSR_MAX) {
osrCounts[osr]++;
}
RawHealthReset(&hs);
int ret = WriteSamples(src, ns->usrdata, fp, fu, &hs, RAW_RESTART_ROW_SAMPLES, u64Out);
ES_NsListFree(list);
if (ret != 0) {
return ret;
}
if ((row + 1) % 100 == 0) {
fprintf(stderr, "\r%llu / %llu rows", (unsigned long long)(row + 1),
(unsigned long long)rows);
}
}
fprintf(stderr, "\nproduction init: %llu successful restarts, %llu total attempts; effective osr",
(unsigned long long)rows, (unsigned long long)totalAttempts);
for (uint32_t osr = NS_DELTA_OSR_MIN; osr <= NS_DELTA_OSR_MAX; osr++) {
if (osrCounts[osr] != 0) {
fprintf(stderr, " %u=%llu", osr, (unsigned long long)osrCounts[osr]);
}
}
fprintf(stderr, "\nrestart done: %llu rows x %d samples\n",
(unsigned long long)rows, RAW_RESTART_ROW_SAMPLES);
RawHealthPrint(&hs);
return 0;
}
int main(int argc, char **argv)
{
if (argc < 4) {
fprintf(stderr,
"usage: %s <jitter|hashloop|blocks> <seq|restart> <outfile> [count] [u64|lsb8]\n"
" jitter, hashloop : production noise-source raw symbols for assessment\n"
" blocks : production-path capture of the conditioner input via the\n"
" linked library (seq only, lsb8 only; count = runtime bytes;\n"
" writes <outfile>, <outfile>.startup, <outfile>.journal.csv)\n",
argv[0]);
return 1;
}
const RawSource *src = NULL;
int blocksMode = 0;
if (strcmp(argv[1], "jitter") == 0) {
#ifdef HITLS_CRYPTO_ENTROPY_NS_CPUJITTER
src = &g_rawJitter;
#else
fprintf(stderr, "jitter source not compiled in (HITLS_CRYPTO_ENTROPY_NS_CPUJITTER)\n");
return 1;
#endif
} else if (strcmp(argv[1], "hashloop") == 0) {
#ifdef HITLS_CRYPTO_ENTROPY_NS_HASHLOOP
src = &g_rawHashLoop;
fprintf(stderr, "hashloop: production es_ns_hashloop.c path, HITLS_HASHLOOP_ITERATIONS=%d\n",
HITLS_HASHLOOP_ITERATIONS);
#else
fprintf(stderr, "hashloop source not compiled in (HITLS_CRYPTO_ENTROPY_NS_HASHLOOP)\n");
return 1;
#endif
} else if (strcmp(argv[1], "blocks") == 0) {
#ifdef HITLS_CRYPTO_ENTROPY_NS_HASHLOOP
blocksMode = 1;
fprintf(stderr, "blocks: production-path capture through the linked library\n");
#else
fprintf(stderr, "blocks source needs HITLS_CRYPTO_ENTROPY_NS_HASHLOOP\n");
return 1;
#endif
} else {
fprintf(stderr, "unknown source: %s\n", argv[1]);
return 1;
}
int restartMode = (strcmp(argv[2], "restart") == 0);
if (!restartMode && strcmp(argv[2], "seq") != 0) {
fprintf(stderr, "unknown mode: %s\n", argv[2]);
return 1;
}
if (blocksMode && restartMode) {
fprintf(stderr, "blocks supports seq only\n");
return 1;
}
uint64_t count = restartMode ? RAW_RESTART_DEFAULT_ROWS : RAW_SEQ_DEFAULT_SAMPLES;
if (argc > 4) {
char *end = NULL;
errno = 0;
unsigned long long v = strtoull(argv[4], &end, 10);
if (argv[4][0] == '-' || errno != 0 || end == argv[4] || *end != '\0' || v == 0) {
fprintf(stderr, "invalid count: %s\n", argv[4]);
return 1;
}
count = v;
}
int u64Out = 1;
if (argc > 5) {
if (strcmp(argv[5], "lsb8") == 0) {
u64Out = 0;
} else if (strcmp(argv[5], "u64") != 0) {
fprintf(stderr, "unknown format: %s\n", argv[5]);
return 1;
}
}
#ifdef HITLS_CRYPTO_ENTROPY_NS_HASHLOOP
if (blocksMode) {
if (argc > 5 && strcmp(argv[5], "u64") == 0) {
fprintf(stderr, "blocks captures the conditioner input byte stream; u64 is unavailable\n");
return 1;
}
return DumpBlocks(argv[3], count);
}
#endif
FILE *fp = fopen(argv[3], "wb");
if (fp == NULL) {
perror("fopen");
return 1;
}
FILE *fu = NULL;
if (!u64Out) {
char upath[4096];
int m = snprintf(upath, sizeof(upath), "%s.u64", argv[3]);
if (m < 0 || (size_t)m >= sizeof(upath)) {
fprintf(stderr, "outfile path too long\n");
(void)fclose(fp);
return 1;
}
fu = fopen(upath, "wb");
if (fu == NULL) {
perror("fopen");
(void)fclose(fp);
return 1;
}
}
int ret = restartMode ? DumpRestart(src, fp, fu, count, u64Out)
: DumpSequential(src, fp, fu, count, u64Out);
if (fclose(fp) != 0) {
perror("fclose");
ret = 1;
}
if (fu != NULL && fclose(fu) != 0) {
perror("fclose");
ret = 1;
}
return ret;
}
#endif