mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
466 lines
11 KiB
C
466 lines
11 KiB
C
/*
|
|
* Copyright (C) 2017 Bas Stottelaar <basstottelaar@gmail.com>
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
|
|
#include "test.h"
|
|
|
|
#include "fmt.h"
|
|
#include "random.h"
|
|
#include "xtimer.h"
|
|
|
|
#ifdef MODULE_PERIPH_HWRNG
|
|
#include "periph/hwrng.h"
|
|
#endif
|
|
|
|
/**
|
|
* @brief Seed for initializing random module.
|
|
*/
|
|
uint32_t seed = 0;
|
|
|
|
/**
|
|
* @brief Source of randomness.
|
|
*/
|
|
rng_source_t source = 0;
|
|
|
|
/**
|
|
* @brief Initialize the RNG, is needed.
|
|
*
|
|
* @param[in] name The test name.
|
|
*/
|
|
static void test_init(char *name)
|
|
{
|
|
/* prepare RNG source */
|
|
if (source == RNG_PRNG) {
|
|
random_init(seed);
|
|
}
|
|
#ifdef MODULE_PERIPH_HWRNG
|
|
else if (source == RNG_HWRNG) {
|
|
hwrng_init();
|
|
}
|
|
#endif
|
|
|
|
/* print test overview */
|
|
printf("Running %s test, with seed %" PRIu32 " using ", name, seed);
|
|
|
|
if (source == RNG_PRNG) {
|
|
#if MODULE_PRNG_FORTUNA
|
|
puts("Fortuna PRNG.\n");
|
|
#elif MODULE_PRNG_MERSENNE
|
|
puts("Mersenne Twister PRNG.\n");
|
|
#elif MODULE_PRNG_MINSTD
|
|
puts("Park & Miller Minimal Standard PRNG.\n");
|
|
#elif MODULE_PRNG_MUSL_LCG
|
|
puts("Musl C PRNG.\n");
|
|
#elif MODULE_PRNG_SHA1PRNG
|
|
puts("SHA1 PRNG.\n");
|
|
#elif MODULE_PRNG_SHA256PRNG
|
|
puts("SHA256 PRNG.\n");
|
|
#elif MODULE_PRNG_TINYMT32
|
|
puts("Tiny Mersenne Twister PRNG.\n");
|
|
#elif MODULE_PRNG_XORSHIFT
|
|
puts("XOR Shift PRNG.\n");
|
|
#elif MODULE_PRNG_HWRNG
|
|
puts("Hardware RNG.\n");
|
|
#else
|
|
puts("unknown PRNG.\n");
|
|
#endif
|
|
}
|
|
#ifdef MODULE_PERIPH_HWRNG
|
|
else if (source == RNG_HWRNG) {
|
|
puts("HW RNG.\n");
|
|
}
|
|
#endif
|
|
else if (source == RNG_CONSTANT) {
|
|
puts("constant value.\n");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Retrieve a 32-bit number of the source RNG.
|
|
*/
|
|
static inline uint32_t test_get_uint32(void)
|
|
{
|
|
if (source == RNG_PRNG) {
|
|
return random_uint32();
|
|
}
|
|
#ifdef MODULE_PERIPH_HWRNG
|
|
else if (source == RNG_HWRNG) {
|
|
uint32_t result;
|
|
hwrng_read(&result, 4);
|
|
|
|
return result;
|
|
}
|
|
#endif
|
|
else if (source == RNG_CONSTANT) {
|
|
/* use the seed as the constant value */
|
|
return seed;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Retrieve a 32-bit number of the source RNG on [a,b)-interval
|
|
*/
|
|
static inline uint32_t test_get_uint32_range(uint32_t a, uint32_t b)
|
|
{
|
|
if (source == RNG_PRNG) {
|
|
return random_uint32_range(a, b);
|
|
}
|
|
#ifdef MODULE_PERIPH_HWRNG
|
|
else if (source == RNG_HWRNG) {
|
|
puts("Range feature not supported by HWRNG");
|
|
}
|
|
#endif
|
|
else if (source == RNG_CONSTANT) {
|
|
/* use the seed as the constant value */
|
|
return seed;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Helper for printing `passed` or `failed` depending on condition
|
|
*
|
|
* @param[in] condition The test condition.
|
|
*/
|
|
void test_pass_fail(bool condition)
|
|
{
|
|
if (condition) {
|
|
puts("passed");
|
|
}
|
|
else {
|
|
puts("failed");
|
|
}
|
|
}
|
|
|
|
void test_distributions(uint32_t samples)
|
|
{
|
|
char tmp[16] = { 0 };
|
|
|
|
uint32_t distributions[32] = { 0 };
|
|
|
|
/* initialize test */
|
|
test_init("distributions");
|
|
|
|
/* take random samples */
|
|
while (samples--) {
|
|
uint32_t value = test_get_uint32();
|
|
|
|
/* count bits */
|
|
for (int i = 0; i < 32; i++) {
|
|
if (value & (UINT32_C(1) << i)) {
|
|
distributions[i]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* sum the total number of ones */
|
|
uint64_t total = 0;
|
|
uint32_t min = UINT32_MAX;
|
|
uint32_t max = 0;
|
|
|
|
for (int i = 0; i < 32; i++) {
|
|
total += distributions[i];
|
|
|
|
if (distributions[i] < min) {
|
|
min = distributions[i];
|
|
}
|
|
|
|
if (distributions[i] > max) {
|
|
max = distributions[i];
|
|
}
|
|
}
|
|
|
|
/* if total is zero, it would yield a division by zero */
|
|
if ((total / 100) == 0) {
|
|
puts("Total ones is zero.\n");
|
|
return;
|
|
}
|
|
|
|
/* print the distribution to screen */
|
|
fmt_u64_dec(tmp, total / 32);
|
|
printf("For 32-bit samples (min = %" PRIu32 ", max = %" PRIu32 ", avg = %s):\n", min, max, tmp);
|
|
|
|
/* print a bar for each bin, scaling max to 100% */
|
|
for (int i = 0; i < 32; i++) {
|
|
printf("%02d: ", i);
|
|
|
|
/* calculate the width of the bar (max 4 + 75 chars) */
|
|
uint8_t bars = (distributions[i] / (max / 75));
|
|
|
|
for (unsigned int j = 0; j < bars; j++) {
|
|
printf("#");
|
|
}
|
|
|
|
puts("");
|
|
}
|
|
|
|
puts("");
|
|
}
|
|
|
|
void test_dump(uint32_t samples)
|
|
{
|
|
test_init("dump");
|
|
|
|
while (samples--) {
|
|
printf("%" PRIu32 "\n", test_get_uint32());
|
|
}
|
|
}
|
|
|
|
void test_dump_range(uint32_t samples, uint32_t low_thresh, uint32_t high_thresh)
|
|
{
|
|
test_init("dump range");
|
|
|
|
while (samples--) {
|
|
printf("%" PRIu32 "\n", test_get_uint32_range(low_thresh, high_thresh));
|
|
}
|
|
}
|
|
|
|
void test_fips(void)
|
|
{
|
|
uint8_t last_bit = UINT8_MAX;
|
|
|
|
uint32_t ones = 0;
|
|
uint32_t poker[16] = { 0 };
|
|
|
|
uint32_t runs = 1;
|
|
uint32_t runs_ones[6] = { 0 };
|
|
uint32_t runs_zeroes[6] = { 0 };
|
|
|
|
uint32_t longruns = 1;
|
|
uint32_t longruns_max = 0;
|
|
|
|
/* initialize test */
|
|
test_init("FIPS 140-2");
|
|
|
|
/* FIPS 140-2 needs 20.000 bits, which are 625 32-bit random numbers */
|
|
for (int i = 0; i < 625; i++) {
|
|
uint32_t value = test_get_uint32();
|
|
|
|
for (int j = 0; j < 4; j++) {
|
|
uint8_t byte = ((uint8_t *) &value)[j];
|
|
|
|
/* poker */
|
|
poker[byte >> 4]++;
|
|
poker[byte & 0x0f]++;
|
|
|
|
for (int k = 0; k < 8; k++) {
|
|
uint8_t bit = (byte >> k) & 0x01;
|
|
|
|
/* monobit */
|
|
if (bit) {
|
|
ones++;
|
|
}
|
|
|
|
/* run length */
|
|
if (bit == last_bit) {
|
|
runs++;
|
|
}
|
|
else {
|
|
if (runs > 6) {
|
|
runs = 6;
|
|
}
|
|
|
|
if (last_bit) {
|
|
runs_ones[runs - 1]++;
|
|
}
|
|
else {
|
|
runs_zeroes[runs - 1]++;
|
|
}
|
|
|
|
runs = 1;
|
|
}
|
|
|
|
/* longruns */
|
|
if (bit == last_bit) {
|
|
longruns++;
|
|
}
|
|
else {
|
|
if (longruns > longruns_max) {
|
|
longruns_max = longruns;
|
|
}
|
|
|
|
longruns = 1;
|
|
}
|
|
|
|
last_bit = bit;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* for a constant stream of bits, the last (long)run must be added */
|
|
if (runs > 6) {
|
|
runs = 6;
|
|
}
|
|
|
|
if (last_bit) {
|
|
runs_ones[runs - 1]++;
|
|
}
|
|
else {
|
|
runs_zeroes[runs - 1]++;
|
|
}
|
|
|
|
if (longruns > longruns_max) {
|
|
longruns_max = longruns;
|
|
}
|
|
|
|
/* monobit test result */
|
|
printf("- Monobit test: ");
|
|
|
|
test_pass_fail(!((ones >= 10275) || (ones <= 9725)));
|
|
|
|
/* poker test result */
|
|
uint32_t result = 0;
|
|
|
|
for (int i = 0; i < 16; i++) {
|
|
result += poker[i] * poker[i];
|
|
}
|
|
|
|
printf("- Poker test: ");
|
|
|
|
test_pass_fail(!((result > 1576928) || (result < 1563176)));
|
|
|
|
/* runs test result */
|
|
bool passed = true;
|
|
uint32_t min[6] = { 2343, 1135, 542, 251, 111, 111 };
|
|
uint32_t max[6] = { 2657, 1365, 708, 373, 201, 201 };
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
if (runs_ones[i] < min[i] || runs_ones[i] > max[i]) {
|
|
passed = false;
|
|
}
|
|
|
|
if (runs_zeroes[i] < min[i] || runs_zeroes[i] > max[i]) {
|
|
passed = false;
|
|
}
|
|
}
|
|
|
|
printf("- Run test: ");
|
|
|
|
test_pass_fail(passed);
|
|
|
|
/* longruns test result */
|
|
printf("- Longrun test: ");
|
|
|
|
test_pass_fail(longruns_max < 26);
|
|
}
|
|
|
|
void test_entropy(uint32_t samples)
|
|
{
|
|
uint8_t buffer[256] = { 0 };
|
|
uint32_t length = 0;
|
|
|
|
/* initialize test */
|
|
test_init("entropy");
|
|
|
|
/* take samples */
|
|
for (uint32_t i = 0; i < samples; i++) {
|
|
uint32_t value = test_get_uint32();
|
|
|
|
buffer[((uint8_t *) &value)[0]]++;
|
|
buffer[((uint8_t *) &value)[1]]++;
|
|
buffer[((uint8_t *) &value)[2]]++;
|
|
buffer[((uint8_t *) &value)[3]]++;
|
|
|
|
length += 4;
|
|
}
|
|
|
|
/* calculate entropy */
|
|
float entropy = 0.0;
|
|
|
|
for (int i = 0; i < 256; i++) {
|
|
if (buffer[i] != 0) {
|
|
float count = (float) buffer[i] / (float) length;
|
|
entropy += (-count * log2f(count));
|
|
}
|
|
}
|
|
|
|
/* print results */
|
|
/* Use 'fmt/print_float' to work on all platforms (atmega)
|
|
* Stdout should be flushed before to prevent garbled output. */
|
|
printf("Calculated ");
|
|
#if defined(MODULE_NEWLIB) || defined(MODULE_PICOLIBC)
|
|
/* no fflush on msp430 */
|
|
fflush(stdout);
|
|
#endif
|
|
print_float(entropy, 6);
|
|
printf(" bits of entropy from %" PRIu32 " samples.\n", samples);
|
|
}
|
|
|
|
void cb_speed_timeout(void *arg)
|
|
{
|
|
unsigned *running = arg;
|
|
*running = 0;
|
|
}
|
|
|
|
void test_speed(uint32_t duration)
|
|
{
|
|
char tmp1[16] = { 0 }, tmp2[16] = { 0 }, tmp3[16] = { 0 };
|
|
|
|
uint64_t samples = 0;
|
|
|
|
/* initialize test */
|
|
test_init("speed");
|
|
printf("Running speed test for %" PRIu32 " seconds\n", duration);
|
|
|
|
/* collect samples as long as timer has not expired */
|
|
unsigned running = 1;
|
|
xtimer_t xt = {
|
|
.callback = cb_speed_timeout,
|
|
.arg = &running,
|
|
};
|
|
uint32_t start_usec = xtimer_now_usec();
|
|
xtimer_set(&xt, duration * US_PER_SEC);
|
|
while (running) {
|
|
test_get_uint32();
|
|
samples++;
|
|
}
|
|
uint32_t actual_duration_usec = xtimer_now_usec() - start_usec;
|
|
|
|
/* print results */
|
|
fmt_u64_dec(tmp1, samples);
|
|
fmt_u64_dec(tmp2, (samples * 4000000 / 1024) / actual_duration_usec);
|
|
fmt_s32_dfp(tmp3, actual_duration_usec, -6);
|
|
printf("Collected %s samples in %s seconds (%s KiB/s).\n", tmp1, tmp3, tmp2);
|
|
}
|
|
|
|
void test_speed_range(uint32_t duration, uint32_t low_thresh, uint32_t high_thresh)
|
|
{
|
|
char tmp1[16] = { 0 }, tmp2[16] = { 0 }, tmp3[16] = { 0 };
|
|
|
|
uint64_t samples = 0;
|
|
|
|
/* initialize test */
|
|
test_init("speed range");
|
|
|
|
/* collect samples as long as timer has not expired */
|
|
unsigned running = 1;
|
|
xtimer_t xt = {
|
|
.callback = cb_speed_timeout,
|
|
.arg = &running,
|
|
};
|
|
uint32_t start_usec = xtimer_now_usec();
|
|
xtimer_set(&xt, duration * US_PER_SEC);
|
|
while (running) {
|
|
test_get_uint32_range(low_thresh, high_thresh);
|
|
samples++;
|
|
}
|
|
uint32_t actual_duration_usec = xtimer_now_usec() - start_usec;
|
|
|
|
/* print results */
|
|
fmt_u64_dec(tmp1, samples);
|
|
fmt_u64_dec(tmp2, (samples * 4000000 / 1024) / actual_duration_usec);
|
|
fmt_s32_dfp(tmp3, actual_duration_usec, -6);
|
|
printf("Collected %s samples in %s seconds (%s KiB/s).\n", tmp1, tmp3, tmp2);
|
|
}
|