#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/syscall.h>

#include <sys/random.h>
#include <openssl/rand.h>

#ifndef GETENTROPY_MAX
#define GETENTROPY_MAX 256
#endif

typedef unsigned char byte;

typedef void randomize_f(byte *ptr, size_t len);

static void
rand_entropy(byte *ptr, size_t len) {
	while(len > GETENTROPY_MAX) {
		int r = getentropy(ptr, GETENTROPY_MAX);
		assert(r == 0);
		ptr += GETENTROPY_MAX;
		len -= GETENTROPY_MAX;
	}
	if(len > 0) {
		int r = getentropy(ptr, len);
		assert(r == 0);
	}
}

static void
rand_vgetrandom(byte *ptr, size_t len) {
  int r = getrandom(ptr, len, 0);
  assert(r >= 0);
}

static void
rand_getrandom(byte *ptr, size_t len) {
  size_t ret;

  /* simply call getrandom syscall */
  ret = syscall(SYS_getrandom, ptr, len, GRND_NONBLOCK);
  assert(ret >= 0);
}

static void
rand_openssl(byte *ptr, size_t len) {
  int r = RAND_bytes(ptr, len);
  assert(r == 1);
}

static uint64_t
nanotime(void) {
	struct timespec tv;
	int r = clock_gettime(CLOCK_MONOTONIC, &tv);
	assert(r == 0);
	return((uint64_t)tv.tv_nsec +
	       (uint64_t)tv.tv_sec * 1000*1000*1000);
}

static byte
consume(byte *ptr, size_t len) {
	byte eat = 0;
	while(len-- > 0) {
		eat ^= *ptr++;
	}
	return(eat);
}

static byte
bench(randomize_f *randomize, size_t count, size_t len) {
	byte *ptr = malloc(len);
	assert(ptr != NULL);

	uint64_t start = nanotime();

	byte eat = 0;
	for(size_t i = 0; i < count; i++) {
		randomize(ptr, len);
		eat ^= consume(ptr, len);
	}

	uint64_t finish = nanotime();
	uint64_t ns = finish - start;
	printf("%11"PRIu64, ns / count);

	free(ptr);
	return(eat);
}

int main(void) {
	printf("init openssl");
	byte eat = bench(rand_openssl, 1, 1);
	printf("\n\n");

	printf("        len"
	       "    entropy"
	       "    openssl"
	       " vgetrandom"
	       "  getrandom\n"
	       );

	size_t count = 10000;
	for(size_t len = 16; len <= 1024; len *= 4) {
		printf("%11zu", len);
		eat |= bench(rand_entropy, count, len);
		eat |= bench(rand_openssl, count, len);
		eat |= bench(rand_vgetrandom, count, len);
		eat |= bench(rand_getrandom, count, len);
		printf("\n");
	}

	return(!eat);
}
