// version 20230904
// public domain
// djb

// 20230904: added -lm, -lrandombytes
// 20230126: initial release

#include <stdio.h>
#include <stdlib.h>
#include <math.h> // -lm
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <randombytes.h> // -lrandombytes

static void die_perm(const char *why)
{
  fprintf(stderr,"randombytes-info: fatal: %s\n",why);
  exit(100);
}

static void die_temp(const char *why,const char *why2)
{
  if (why2)
    fprintf(stderr,"randombytes-info: fatal: %s: %s\n",why,why2);
  else
    fprintf(stderr,"randombytes-info: fatal: %s\n",why);
  exit(111);
}

static void fullread(int fd,unsigned char *x,long long xbytes)
{
  while (xbytes > 0) {
    int r = 16777216;
    if (xbytes < r) r = xbytes;

    errno = 0;
    r = read(fd,x,r);
    if (r == 0) die_temp("read failed","unexpected EOF");
    if (r < 0 && errno == EINTR) continue;
    if (r < 0) die_temp("read failed",strerror(errno));

    x += r;
    xbytes -= r;
  }
}

static void fullwrite(int fd,unsigned char *x,long long xbytes)
{
  while (xbytes > 0) {
    int w = 16777216;
    if (xbytes < w) w = xbytes;

    errno = 0;
    w = write(fd,x,w);
    if (w == 0) continue;
    if (w < 0 && errno == EINTR) continue;
    if (w < 0) die_temp("write failed",strerror(errno));

    x += w;
    xbytes -= w;
  }
}

static void test_fork(void)
{
  unsigned char x[32];
  unsigned char y[32];
  int pi[2];
  pid_t child;
  int j;

  if (pipe(pi) != 0)
    die_temp("pipe failed",strerror(errno));

  child = fork();
  if (child < 0)
    die_temp("fork failed",strerror(errno));

  randombytes(x,sizeof x);

  if (child == 0) {
    close(pi[0]);
    fullwrite(pi[1],x,sizeof x);
    exit(0);
  }

  close(pi[1]);
  fullread(pi[0],y,sizeof y);
  close(pi[0]);

  printf("randombytes test_fork x ");
  for (j = 0;j < sizeof x;++j) printf("%02x",x[j]);
  printf("\n");
  printf("randombytes test_fork y ");
  for (j = 0;j < sizeof y;++j) printf("%02x",y[j]);
  printf("\n");
  fflush(stdout);

  if (memcmp(x,y,sizeof y) == 0)
    die_perm("RNG failure: 32 bytes match after fork");
}

#define TIMINGS 16

static void speed(void)
{
  unsigned char x[16384];
  struct timeval tv[TIMINGS];
  double t[TIMINGS-1];
  double avg;
  double variance;
  long long bytes, iters, i, j;

  bytes = 1;
  while (bytes <= sizeof x) {
    for (iters = 1;iters <= 64;iters *= 2) {
      for (i = 0;i < TIMINGS;++i) {
        gettimeofday(&tv[i],0);
        for (j = 0;j < iters;++j)
          randombytes(x,bytes);
      }
      printf("randombytes timing bytes %lld iters %lld ns/byte",bytes,iters);
      for (i = 1;i < TIMINGS;++i)
        t[i-1] = ((tv[i].tv_sec-tv[i-1].tv_sec)*1000000000.0+(tv[i].tv_usec-tv[i-1].tv_usec)*1000.0)/(iters*bytes);
      avg = 0;
      for (i = 1;i < TIMINGS;++i)
        avg += t[i-1];
      avg /= TIMINGS-1;
      variance = 0;
      for (i = 1;i < TIMINGS;++i)
        variance += (t[i-1]-avg)*(t[i-1]-avg);
      variance /= TIMINGS-1;
      printf(" %lf +- %lf\n",avg,sqrt(variance));
      fflush(stdout);
    }

    if (bytes >= sizeof x) break;
    bytes *= 128;
  }
}

int main()
{
  printf("randombytes source %s\n",randombytes_source());
  fflush(stdout);

  test_fork();

  speed();

  return 0;
}
