/* random.c - part of the Libgcrypt test suite.
   Copyright (C) 2005 Free Software Foundation, Inc.

   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 2 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, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
   USA.  */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>

#include "../src/gcrypt.h"

static int verbose;

static void
die (const char *format, ...)
{
  va_list arg_ptr;

  va_start (arg_ptr, format);
  vfprintf (stderr, format, arg_ptr);
  va_end (arg_ptr);
  exit (1);
}


static void
print_hex (const char *text, const void *buf, size_t n)
{
  const unsigned char *p = buf;

  fputs (text, stdout);
  for (; n; n--, p++)
    printf ("%02X", *p);
  putchar ('\n');
}


static int
writen (int fd, const void *buf, size_t nbytes)
{
  size_t nleft = nbytes;
  int nwritten;

  while (nleft > 0)
    {
      nwritten = write (fd, buf, nleft);
      if (nwritten < 0)
        {
          if (errno == EINTR)
            nwritten = 0;
          else
            return -1;
        }
      nleft -= nwritten;
      buf = (const char*)buf + nwritten;
    }

  return 0;
}

static int
readn (int fd, void *buf, size_t buflen, size_t *ret_nread)
{
  size_t nleft = buflen;
  int nread;

  while ( nleft > 0 )
    {
      nread = read ( fd, buf, nleft );
      if (nread < 0)
        {
          if (nread == EINTR)
            nread = 0;
          else
            return -1;
        }
      else if (!nread)
        break; /* EOF */
      nleft -= nread;
      buf = (char*)buf + nread;
    }
  if (ret_nread)
    *ret_nread = buflen - nleft;
  return 0;
}



/* Check that forking won't return the same random. */
static void
check_forking (void)
{
  pid_t pid;
  int rp[2];
  int i, status;
  size_t nread;
  char tmp1[16], tmp1c[16], tmp1p[16];

  /* We better make sure that the RNG has been initialzied. */
  gcry_randomize (tmp1, sizeof tmp1, GCRY_STRONG_RANDOM);
  if (verbose)
    print_hex ("initial random: ", tmp1, sizeof tmp1);

  if (pipe (rp) == -1)
    die ("pipe failed: %s\n", strerror (errno));

  pid = fork ();
  if (pid == (pid_t)(-1))
    die ("fork failed: %s\n", strerror (errno));
  if (!pid)
    {
      gcry_randomize (tmp1c, sizeof tmp1c, GCRY_STRONG_RANDOM);
      if (writen (rp[1], tmp1c, sizeof tmp1c))
        die ("write failed: %s\n", strerror (errno));
      if (verbose)
        {
          print_hex ("  child random: ", tmp1c, sizeof tmp1c);
          fflush (stdout);
        }
      _exit (0);
    }
  gcry_randomize (tmp1p, sizeof tmp1p, GCRY_STRONG_RANDOM);
  if (verbose)
    print_hex (" parent random: ", tmp1p, sizeof tmp1p);

  close (rp[1]);
  if (readn (rp[0], tmp1c, sizeof tmp1c, &nread))
    die ("read failed: %s\n", strerror (errno));
  if (nread != sizeof tmp1c)
    die ("read too short\n");

  while ( (i=waitpid (pid, &status, 0)) == -1 && errno == EINTR)
    ;
  if (i != (pid_t)(-1)
      && WIFEXITED (status) && !WEXITSTATUS (status))
    ;
  else
    die ("child failed\n");

  if (!memcmp (tmp1p, tmp1c, sizeof tmp1c))
    die ("parent and child got the same random number\n");
}



/* Check that forking won't return the same nonce. */
static void
check_nonce_forking (void)
{
  pid_t pid;
  int rp[2];
  int i, status;
  size_t nread;
  char nonce1[10], nonce1c[10], nonce1p[10];

  /* We won't get the same nonce back if we never initialized the
     nonce subsystem, thus we get one nonce here and forget about
     it. */
  gcry_create_nonce (nonce1, sizeof nonce1);
  if (verbose)
    print_hex ("initial nonce: ", nonce1, sizeof nonce1);

  if (pipe (rp) == -1)
    die ("pipe failed: %s\n", strerror (errno));

  pid = fork ();
  if (pid == (pid_t)(-1))
    die ("fork failed: %s\n", strerror (errno));
  if (!pid)
    {
      gcry_create_nonce (nonce1c, sizeof nonce1c);
      if (writen (rp[1], nonce1c, sizeof nonce1c))
        die ("write failed: %s\n", strerror (errno));
      if (verbose)
        {
          print_hex ("  child nonce: ", nonce1c, sizeof nonce1c);
          fflush (stdout);
        }
      _exit (0);
    }
  gcry_create_nonce (nonce1p, sizeof nonce1p);
  if (verbose)
    print_hex (" parent nonce: ", nonce1p, sizeof nonce1p);

  close (rp[1]);
  if (readn (rp[0], nonce1c, sizeof nonce1c, &nread))
    die ("read failed: %s\n", strerror (errno));
  if (nread != sizeof nonce1c)
    die ("read too short\n");

  while ( (i=waitpid (pid, &status, 0)) == -1 && errno == EINTR)
    ;
  if (i != (pid_t)(-1)
      && WIFEXITED (status) && !WEXITSTATUS (status))
    ;
  else
    die ("child failed\n");

  if (!memcmp (nonce1p, nonce1c, sizeof nonce1c))
    die ("parent and child got the same nonce\n");
}






int
main (int argc, char **argv)
{
  int debug = 0;

  if ((argc > 1) && (! strcmp (argv[1], "--verbose")))
    verbose = 1;
  else if ((argc > 1) && (! strcmp (argv[1], "--debug")))
    verbose = debug = 1;

  signal (SIGPIPE, SIG_IGN);

  gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
  if (!gcry_check_version (GCRYPT_VERSION))
    die ("version mismatch\n");

  gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
  if (debug)
    gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1u, 0);

  check_forking ();
  check_nonce_forking ();

  return 0;
}
