/* t-mpi-bit.c  - Tests for bit level functions
 * Copyright (C) 2006 Free Software Foundation, Inc.
 *
 * This file is part of Libgcrypt.
 *
 * Libgcrypt is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * Libgcrypt 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdarg.h>

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

#define PGM "t-mpi-bit"

static const char *wherestr;
static int verbose;
static int error_count;

#define xmalloc(a)    gcry_xmalloc ((a))
#define xcalloc(a,b)  gcry_xcalloc ((a),(b))
#define xfree(a)      gcry_free ((a))
#define pass() do { ; } while (0)

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

  if (!verbose)
    return;
  fprintf (stderr, "%s: ", PGM);
  va_start (arg_ptr, format);
  vfprintf (stderr, format, arg_ptr);
  va_end (arg_ptr);
}

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

  fflush (stdout);
  fprintf (stderr, "%s: ", PGM);
  if (wherestr)
    fprintf (stderr, "%s: ", wherestr);
  va_start (arg_ptr, format);
  vfprintf (stderr, format, arg_ptr);
  va_end (arg_ptr);
  error_count++;
}

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

  fflush (stdout);
  fprintf (stderr, "%s: ", PGM);
  if (wherestr)
    fprintf (stderr, "%s: ", wherestr);
  va_start (arg_ptr, format);
  vfprintf (stderr, format, arg_ptr);
  va_end (arg_ptr);
  exit (1);
}

/* Allocate a bit string consisting of '0' and '1' from the MPI
   A. Return the LENGTH least significant bits. Caller needs to xfree
   the result. */
static char *
mpi2bitstr (gcry_mpi_t a, size_t length)
{
  char *p, *buf;

  buf = p = xmalloc (length+1);
  while (length--)
    *p++ = gcry_mpi_test_bit (a, length) ? '1':'0';
  *p = 0;

  return buf;
}

/* Allocate a bit string consisting of '0' and '1' from the MPI A.  Do
   not return any leading zero bits. Caller needs to xfree the
   result. */
static char *
mpi2bitstr_nlz (gcry_mpi_t a)
{
  char *p, *buf;
  size_t length = gcry_mpi_get_nbits (a);

  if (!length)
    {
      buf = p = xmalloc (2);
      *p++ = '0';
    }
  else
    {
      buf = p = xmalloc (length + 1);
      while (length-- > 1)
        *p++ = gcry_mpi_test_bit (a, length) ? '1':'0';
      *p++ = gcry_mpi_test_bit (a, 0) ? '1':'0';
    }
  *p = 0;
  return buf;
}

/* Shift a bit string to the right. */
static void
rshiftbitstring (char *string, size_t n)
{
  size_t len = strlen (string);

  if (n > len)
    n = len;

  memmove (string+n, string, len-n);
  memset (string, '0', n);
}

/* Shift a bit string to the left. Caller needs to free the result. */
static char *
lshiftbitstring (const char *string, size_t n)
{
  size_t len = strlen (string);
  char *result;

  if (len+n+1 < len)
    die ("internal overflow\n");
  /* Allocate enough space. */
  result = xmalloc (len+n+1);
  for (; *string == '0' && string[1]; string++, len--)
    ;
  memcpy (result, string, len);
  if (*string == '0' && !string[1])
    n = 0; /* Avoid extra nulls for an only 0 string.  */
  else
    memset (result+len, '0', n);
  result[len+n] = 0;
  return result;
}


/* This is to check a bug reported by bpgcrypt at itaparica.org on
   2006-07-31 against libgcrypt 1.2.2.  */
static void
one_bit_only (int highbit)
{
  gcry_mpi_t a;
  char *result;
  int i;

  wherestr = "one_bit_only";
  show ("checking that set_%sbit does only set one bit\n", highbit?"high":"");

  a = gcry_mpi_new (0);
  gcry_mpi_randomize (a, 70, GCRY_WEAK_RANDOM);
  gcry_mpi_set_ui (a, 0);

  if (highbit)
    gcry_mpi_set_highbit (a, 42);
  else
    gcry_mpi_set_bit (a, 42);
  if (!gcry_mpi_test_bit (a, 42))
    fail ("failed to set a bit\n");
  gcry_mpi_clear_bit (a, 42);
  if (gcry_mpi_test_bit (a, 42))
    fail ("failed to clear a bit\n");
  result = mpi2bitstr (a, 70);
  assert (strlen (result) == 70);
  for (i=0; result[i]; i++)
    if ( result[i] != '0' )
      break;
  if (result[i])
    fail ("spurious bits detected\n");
  xfree (result);
  gcry_mpi_release (a);
}

/* Check that right shifting actually works for an amount larger than
   the number of bits per limb. */
static void
test_rshift (int pass)
{
  gcry_mpi_t a, b;
  char *result, *result2;
  int i;

  wherestr = "test_rshift";
  show ("checking that rshift works as expected (pass %d)\n", pass);

  a = gcry_mpi_new (0);
  b = gcry_mpi_new (0);
  gcry_mpi_randomize (a, 70, GCRY_WEAK_RANDOM);

  for (i=0; i < 75; i++)
    {
      gcry_mpi_rshift (b, a, i);

      result = mpi2bitstr (b, 72);
      result2 = mpi2bitstr (a, 72);
      rshiftbitstring (result2, i);
      if (strcmp (result, result2))
        {
          show ("got =%s\n", result);
          show ("want=%s\n", result2);
          fail ("rshift by %d failed\n", i);
        }
      xfree (result);
      xfree (result2);
    }

  /* Again. This time using in-place operation. */
  gcry_mpi_randomize (a, 70, GCRY_WEAK_RANDOM);

  for (i=0; i < 75; i++)
    {
      gcry_mpi_release (b);
      b = gcry_mpi_copy (a);
      gcry_mpi_rshift (b, b, i);

      result = mpi2bitstr (b, 72);
      result2 = mpi2bitstr (a, 72);
      rshiftbitstring (result2, i);
      if (strcmp (result, result2))
        {
          show ("got =%s\n", result);
          show ("want=%s\n", result2);
          fail ("in-place rshift by %d failed\n", i);
        }
      xfree (result2);
      xfree (result);
    }

  gcry_mpi_release (b);
  gcry_mpi_release (a);
}

/* Check that left shifting works correctly.  */
static void
test_lshift (int pass)
{
  static int size_list[] = {1, 31, 32, 63, 64, 65, 70, 0};
  int size_idx;
  gcry_mpi_t a, b;
  char *tmpstr, *result, *result2;
  int i;

  wherestr = "test_lshift";
  show ("checking that lshift works as expected (pass %d)\n", pass);

  for (size_idx=0; size_list[size_idx]; size_idx++)
    {
      a = gcry_mpi_new (0);
      b = gcry_mpi_new (0);

      /* gcry_mpi_randomize rounds up to full bytes, thus we need to
         use gcry_mpi_clear_highbit to fix that.  */
      gcry_mpi_randomize (a, size_list[size_idx], GCRY_WEAK_RANDOM);
      gcry_mpi_clear_highbit (a, size_list[size_idx]);

      for (i=0; i < 75; i++)
        {
          gcry_mpi_lshift (b, a, i);

          result = mpi2bitstr_nlz (b);
          tmpstr = mpi2bitstr_nlz (a);
          result2 = lshiftbitstring (tmpstr, i);
          xfree (tmpstr);
          if (strcmp (result, result2))
            {
              show ("got =%s\n", result);
              show ("want=%s\n", result2);
              fail ("lshift by %d failed\n", i);
            }
          xfree (result);
          xfree (result2);
        }

      /* Again. This time using in-place operation. */
      gcry_mpi_randomize (a, size_list[size_idx], GCRY_WEAK_RANDOM);
      gcry_mpi_clear_highbit (a, size_list[size_idx]);

      for (i=0; i < 75; i++)
        {
          gcry_mpi_release (b);
          b = gcry_mpi_copy (a);
          gcry_mpi_lshift (b, b, i);

          result = mpi2bitstr_nlz (b);
          tmpstr = mpi2bitstr_nlz (a);
          result2 = lshiftbitstring (tmpstr, i);
          xfree (tmpstr);
          if (strcmp (result, result2))
            {
              show ("got =%s\n", result);
              show ("want=%s\n", result2);
              fail ("in-place lshift by %d failed\n", i);
            }
          xfree (result2);
          xfree (result);
        }

      gcry_mpi_release (b);
      gcry_mpi_release (a);
    }
}


/* Bug fixed on 2014-05-09:
      a = gcry_mpi_new (1523);
      gcry_mpi_set_bit (a, 1536);
      didn't initialized all limbs in A.  */
static void
set_bit_with_resize (void)
{
  gcry_mpi_t a;
  int i;

  wherestr = "set_bit_with_resize";
  show ("checking that set_bit initializes all limbs\n");

  a = gcry_mpi_new (1536);
  gcry_mpi_set_bit (a, 1536);

  if (!gcry_mpi_test_bit (a, 1536))
    fail ("failed to set a bit\n");
  for (i=0; i < 1536; i++)
    {
      if (gcry_mpi_test_bit (a, i))
        {
          fail ("spurious bit detected\n");
          break;
        }
    }
  if (gcry_mpi_test_bit (a, 1537))
    fail ("more bits set than expected\n");
  gcry_mpi_release (a);

  wherestr = "set_highbit_with_resize";
  show ("checking that set_highbit initializes all limbs\n");

  a = gcry_mpi_new (1536);
  gcry_mpi_set_highbit (a, 1536);

  if (!gcry_mpi_test_bit (a, 1536))
    fail ("failed to set a bit\n");
  for (i=0; i < 1536; i++)
    {
      if (gcry_mpi_test_bit (a, i))
        {
          fail ("spurious bit detected\n");
          break;
        }
    }
  if (gcry_mpi_test_bit (a, 1537))
    fail ("more bits set than expected\n");
  gcry_mpi_release (a);
}


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

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

  if (!gcry_check_version (GCRYPT_VERSION))
    die ("version mismatch\n");

  gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
  gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
  if (debug)
    gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1u, 0);

  gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);

  one_bit_only (0);
  one_bit_only (1);
  for (i=0; i < 5; i++)
    test_rshift (i); /* Run several times due to random initializations. */

  for (i=0; i < 5; i++)
    test_lshift (i); /* Run several times due to random initializations. */

  set_bit_with_resize ();

  show ("All tests completed. Errors: %d\n", error_count);
  return error_count ? 1 : 0;
}
