/* mpicoder.c  -  Coder for the external representation of MPIs
 * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003
 *               2008 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, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "mpi-internal.h"
#include "g10lib.h"

#define MAX_EXTERN_MPI_BITS 16384

/* Helper used to scan PGP style MPIs.  Returns NULL on failure. */
static gcry_mpi_t
mpi_read_from_buffer (const unsigned char *buffer, unsigned *ret_nread,
                      int secure)
{
  int i, j;
  unsigned int nbits, nbytes, nlimbs, nread=0;
  mpi_limb_t a;
  gcry_mpi_t val = MPI_NULL;

  if ( *ret_nread < 2 )
    goto leave;
  nbits = buffer[0] << 8 | buffer[1];
  if ( nbits > MAX_EXTERN_MPI_BITS )
    {
/*       log_debug ("mpi too large (%u bits)\n", nbits); */
      goto leave;
    }
  buffer += 2;
  nread = 2;

  nbytes = (nbits+7) / 8;
  nlimbs = (nbytes+BYTES_PER_MPI_LIMB-1) / BYTES_PER_MPI_LIMB;
  val = secure? mpi_alloc_secure (nlimbs) : mpi_alloc (nlimbs);
  i = BYTES_PER_MPI_LIMB - nbytes % BYTES_PER_MPI_LIMB;
  i %= BYTES_PER_MPI_LIMB;
  j= val->nlimbs = nlimbs;
  val->sign = 0;
  for ( ; j > 0; j-- )
    {
      a = 0;
      for (; i < BYTES_PER_MPI_LIMB; i++ )
        {
          if ( ++nread > *ret_nread )
            {
/*               log_debug ("mpi larger than buffer"); */
              mpi_free (val);
              val = NULL;
              goto leave;
            }
          a <<= 8;
          a |= *buffer++;
	}
      i = 0;
      val->d[j-1] = a;
    }

 leave:
  *ret_nread = nread;
  return val;
}


/****************
 * Fill the mpi VAL from the hex string in STR.
 */
static int
mpi_fromstr (gcry_mpi_t val, const char *str)
{
  int sign = 0;
  int prepend_zero = 0;
  int i, j, c, c1, c2;
  unsigned int nbits, nbytes, nlimbs;
  mpi_limb_t a;

  if ( *str == '-' )
    {
      sign = 1;
      str++;
    }

  /* Skip optional hex prefix.  */
  if ( *str == '0' && str[1] == 'x' )
    str += 2;

  nbits = 4 * strlen (str);
  if ((nbits % 8))
    prepend_zero = 1;

  nbytes = (nbits+7) / 8;
  nlimbs = (nbytes+BYTES_PER_MPI_LIMB-1) / BYTES_PER_MPI_LIMB;

  if ( val->alloced < nlimbs )
    mpi_resize (val, nlimbs);

  i = BYTES_PER_MPI_LIMB - (nbytes % BYTES_PER_MPI_LIMB);
  i %= BYTES_PER_MPI_LIMB;
  j = val->nlimbs = nlimbs;
  val->sign = sign;
  for (; j > 0; j--)
    {
      a = 0;
      for (; i < BYTES_PER_MPI_LIMB; i++)
        {
          if (prepend_zero)
            {
              c1 = '0';
              prepend_zero = 0;
	    }
          else
            c1 = *str++;

          if (!c1)
            {
              mpi_clear (val);
              return 1;  /* Error.  */
	    }
          c2 = *str++;
          if (!c2)
            {
              mpi_clear (val);
              return 1;  /* Error.  */
	    }
          if ( c1 >= '0' && c1 <= '9' )
            c = c1 - '0';
          else if ( c1 >= 'a' && c1 <= 'f' )
            c = c1 - 'a' + 10;
          else if ( c1 >= 'A' && c1 <= 'F' )
            c = c1 - 'A' + 10;
          else
            {
              mpi_clear (val);
              return 1;  /* Error.  */
	    }
          c <<= 4;
          if ( c2 >= '0' && c2 <= '9' )
            c |= c2 - '0';
          else if( c2 >= 'a' && c2 <= 'f' )
            c |= c2 - 'a' + 10;
          else if( c2 >= 'A' && c2 <= 'F' )
            c |= c2 - 'A' + 10;
          else
            {
              mpi_clear(val);
              return 1;  /* Error. */
	    }
          a <<= 8;
          a |= c;
	}
      i = 0;
      val->d[j-1] = a;
    }

  return 0;  /* Okay.  */
}


/* Dump the value of A in a format suitable for debugging to
   Libgcrypt's logging stream.  Note that one leading space but no
   trailing space or linefeed will be printed.  It is okay to pass
   NULL for A. */
void
gcry_mpi_dump (const gcry_mpi_t a)
{
  int i;

  log_printf (" ");
  if (!a)
    log_printf ("[MPI_NULL]");
  else
    {
      if (a->sign)
        log_printf ( "-");
#if BYTES_PER_MPI_LIMB == 2
# define X "4"
#elif BYTES_PER_MPI_LIMB == 4
# define X "8"
#elif BYTES_PER_MPI_LIMB == 8
# define X "16"
#elif BYTES_PER_MPI_LIMB == 16
# define X "32"
#else
# error please define the format here
#endif
      for (i=a->nlimbs; i > 0 ; i-- )
        {
          log_printf (i != a->nlimbs? "%0" X "lX":"%lX", (ulong)a->d[i-1]);
        }
#undef X
      if (!a->nlimbs)
        log_printf ("0");
    }
}

/* Convience function used internally. */
void
_gcry_log_mpidump (const char *text, gcry_mpi_t a)
{
  log_printf ("%s:", text);
  gcry_mpi_dump (a);
  log_printf ("\n");
}


/* Return an allocated buffer with the MPI (msb first).  NBYTES
   receives the length of this buffer.  Caller must free the return
   string.  This function returns an allocated buffer with NBYTES set
   to zero if the value of A is zero.  If sign is not NULL, it will be
   set to the sign of the A.  On error NULL is returned and ERRNO set
   appropriately.  */
static unsigned char *
do_get_buffer (gcry_mpi_t a, unsigned int *nbytes, int *sign, int force_secure)
{
  unsigned char *p, *buffer;
  mpi_limb_t alimb;
  int i;
  size_t n;

  if (sign)
    *sign = a->sign;

  *nbytes = a->nlimbs * BYTES_PER_MPI_LIMB;
  n = *nbytes? *nbytes:1; /* Allocate at least one byte.  */
  p = buffer = (force_secure || mpi_is_secure(a))? gcry_malloc_secure (n)
						 : gcry_malloc (n);
  if (!buffer)
    return NULL;

  for (i=a->nlimbs-1; i >= 0; i--)
    {
      alimb = a->d[i];
#if BYTES_PER_MPI_LIMB == 4
      *p++ = alimb >> 24;
      *p++ = alimb >> 16;
      *p++ = alimb >>  8;
      *p++ = alimb	  ;
#elif BYTES_PER_MPI_LIMB == 8
      *p++ = alimb >> 56;
      *p++ = alimb >> 48;
      *p++ = alimb >> 40;
      *p++ = alimb >> 32;
      *p++ = alimb >> 24;
      *p++ = alimb >> 16;
      *p++ = alimb >>  8;
      *p++ = alimb	  ;
#else
#     error please implement for this limb size.
#endif
    }

  /* This is sub-optimal but we need to do the shift operation because
     the caller has to free the returned buffer.  */
  for (p=buffer; *nbytes && !*p; p++, --*nbytes)
    ;
  if (p != buffer)
    memmove (buffer,p, *nbytes);
  return buffer;
}


byte *
_gcry_mpi_get_buffer (gcry_mpi_t a, unsigned int *nbytes, int *sign)
{
  return do_get_buffer (a, nbytes, sign, 0);
}

byte *
_gcry_mpi_get_secure_buffer (gcry_mpi_t a, unsigned *nbytes, int *sign)
{
  return do_get_buffer (a, nbytes, sign, 1);
}


/*
 * Use the NBYTES at BUFFER_ARG to update A.  Set the sign of a to
 * SIGN.
 */
void
_gcry_mpi_set_buffer (gcry_mpi_t a, const void *buffer_arg,
                      unsigned int nbytes, int sign)
{
  const unsigned char *buffer = (const unsigned char*)buffer_arg;
  const unsigned char *p;
  mpi_limb_t alimb;
  int nlimbs;
  int i;

  nlimbs = (nbytes + BYTES_PER_MPI_LIMB - 1) / BYTES_PER_MPI_LIMB;
  RESIZE_IF_NEEDED(a, nlimbs);
  a->sign = sign;

  for (i=0, p = buffer+nbytes-1; p >= buffer+BYTES_PER_MPI_LIMB; )
    {
#if BYTES_PER_MPI_LIMB == 4
      alimb  = *p--	    ;
      alimb |= *p-- <<  8 ;
      alimb |= *p-- << 16 ;
      alimb |= *p-- << 24 ;
#elif BYTES_PER_MPI_LIMB == 8
      alimb  = (mpi_limb_t)*p--	;
      alimb |= (mpi_limb_t)*p-- <<  8 ;
      alimb |= (mpi_limb_t)*p-- << 16 ;
      alimb |= (mpi_limb_t)*p-- << 24 ;
      alimb |= (mpi_limb_t)*p-- << 32 ;
      alimb |= (mpi_limb_t)*p-- << 40 ;
      alimb |= (mpi_limb_t)*p-- << 48 ;
      alimb |= (mpi_limb_t)*p-- << 56 ;
#else
#       error please implement for this limb size.
#endif
      a->d[i++] = alimb;
    }
  if ( p >= buffer )
    {
#if BYTES_PER_MPI_LIMB == 4
      alimb  = *p--;
      if (p >= buffer)
        alimb |= *p-- <<  8;
      if (p >= buffer)
        alimb |= *p-- << 16;
      if (p >= buffer)
        alimb |= *p-- << 24;
#elif BYTES_PER_MPI_LIMB == 8
      alimb  = (mpi_limb_t)*p--;
      if (p >= buffer)
        alimb |= (mpi_limb_t)*p-- << 8;
      if (p >= buffer)
        alimb |= (mpi_limb_t)*p-- << 16;
      if (p >= buffer)
        alimb |= (mpi_limb_t)*p-- << 24;
      if (p >= buffer)
        alimb |= (mpi_limb_t)*p-- << 32;
      if (p >= buffer)
        alimb |= (mpi_limb_t)*p-- << 40;
      if (p >= buffer)
        alimb |= (mpi_limb_t)*p-- << 48;
      if (p >= buffer)
        alimb |= (mpi_limb_t)*p-- << 56;
#else
#     error please implement for this limb size.
#endif
      a->d[i++] = alimb;
    }
  a->nlimbs = i;
  gcry_assert (i == nlimbs);
}


/* Convert the external representation of an integer stored in BUFFER
   with a length of BUFLEN into a newly create MPI returned in
   RET_MPI.  If NBYTES is not NULL, it will receive the number of
   bytes actually scanned after a successful operation.  */
gcry_error_t
gcry_mpi_scan (struct gcry_mpi **ret_mpi, enum gcry_mpi_format format,
               const void *buffer_arg, size_t buflen, size_t *nscanned)
{
  const unsigned char *buffer = (const unsigned char*)buffer_arg;
  struct gcry_mpi *a = NULL;
  unsigned int len;
  int secure = (buffer && gcry_is_secure (buffer));

  if (format == GCRYMPI_FMT_SSH)
    len = 0;
  else
    len = buflen;

  if (format == GCRYMPI_FMT_STD)
    {
      const unsigned char *s = buffer;

      a = secure? mpi_alloc_secure ((len+BYTES_PER_MPI_LIMB-1)
                                    /BYTES_PER_MPI_LIMB)
                : mpi_alloc ((len+BYTES_PER_MPI_LIMB-1)/BYTES_PER_MPI_LIMB);
      if (len)
        {
          a->sign = !!(*s & 0x80);
          if (a->sign)
            {
              /* FIXME: we have to convert from 2compl to magnitude format */
              mpi_free (a);
              return gcry_error (GPG_ERR_INTERNAL);
	    }
          else
            _gcry_mpi_set_buffer (a, s, len, 0);
	}
      if (ret_mpi)
        {
          mpi_normalize ( a );
          *ret_mpi = a;
	}
      else
        mpi_free(a);
      return 0;
    }
  else if (format == GCRYMPI_FMT_USG)
    {
      a = secure? mpi_alloc_secure ((len+BYTES_PER_MPI_LIMB-1)
                                    /BYTES_PER_MPI_LIMB)
                : mpi_alloc ((len+BYTES_PER_MPI_LIMB-1)/BYTES_PER_MPI_LIMB);

      if (len)
        _gcry_mpi_set_buffer (a, buffer, len, 0);
      if (ret_mpi)
        {
          mpi_normalize ( a );
          *ret_mpi = a;
	}
      else
        mpi_free(a);
      return 0;
    }
  else if (format == GCRYMPI_FMT_PGP)
    {
      a = mpi_read_from_buffer (buffer, &len, secure);
      if (nscanned)
        *nscanned = len;
      if (ret_mpi && a)
        {
          mpi_normalize (a);
          *ret_mpi = a;
	}
      else if (a)
        {
          mpi_free(a);
          a = NULL;
        }
      return a? 0 : gcry_error (GPG_ERR_INV_OBJ);
    }
  else if (format == GCRYMPI_FMT_SSH)
    {
      const unsigned char *s = buffer;
      size_t n;

      /* This test is not strictly necessary and an assert (!len)
         would be sufficient.  We keep this test in case we later
         allow the BUFLEN argument to act as a sanitiy check.  Same
         below. */
      if (len && len < 4)
        return gcry_error (GPG_ERR_TOO_SHORT);

      n = (s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3]);
      s += 4;
      if (len)
        len -= 4;
      if (len && n > len)
        return gcry_error (GPG_ERR_TOO_LARGE);

      a = secure? mpi_alloc_secure ((n+BYTES_PER_MPI_LIMB-1)
                                    /BYTES_PER_MPI_LIMB)
                : mpi_alloc ((n+BYTES_PER_MPI_LIMB-1)/BYTES_PER_MPI_LIMB);
      if (n)
        {
          a->sign = !!(*s & 0x80);
          if (a->sign)
            {
              /* FIXME: we have to convert from 2compl to magnitude format */
              mpi_free(a);
              return gcry_error (GPG_ERR_INTERNAL);
	    }
          else
            _gcry_mpi_set_buffer( a, s, n, 0 );
	}
      if (nscanned)
        *nscanned = n+4;
      if (ret_mpi)
        {
          mpi_normalize ( a );
          *ret_mpi = a;
        }
      else
        mpi_free(a);
      return 0;
    }
  else if (format == GCRYMPI_FMT_HEX)
    {
      /* We can only handle C strings for now.  */
      if (buflen)
        return gcry_error (GPG_ERR_INV_ARG);

      a = secure? mpi_alloc_secure (0) : mpi_alloc(0);
      if (mpi_fromstr (a, (const char *)buffer))
        {
          mpi_free (a);
          return gcry_error (GPG_ERR_INV_OBJ);
        }
      if (ret_mpi)
        {
          mpi_normalize ( a );
          *ret_mpi = a;
	}
      else
        mpi_free(a);
      return 0;
    }
  else
    return gcry_error (GPG_ERR_INV_ARG);
}


/* Convert the big integer A into the external representation
   described by FORMAT and store it in the provided BUFFER which has
   been allocated by the user with a size of BUFLEN bytes.  NWRITTEN
   receives the actual length of the external representation unless it
   has been passed as NULL.  BUFFER may be NULL to query the required
   length.  */
gcry_error_t
gcry_mpi_print (enum gcry_mpi_format format,
                unsigned char *buffer, size_t buflen,
                size_t *nwritten, struct gcry_mpi *a)
{
  unsigned int nbits = mpi_get_nbits (a);
  size_t len;
  size_t dummy_nwritten;

  if (!nwritten)
    nwritten = &dummy_nwritten;

  len = buflen;
  *nwritten = 0;
  if (format == GCRYMPI_FMT_STD)
    {
      unsigned char *tmp;
      int extra = 0;
      unsigned int n;

      if (a->sign)
        return gcry_error (GPG_ERR_INTERNAL); /* Can't handle it yet. */

      tmp = _gcry_mpi_get_buffer (a, &n, NULL);
      if (!tmp)
        return gpg_error_from_syserror ();
      if (n && (*tmp & 0x80))
        {
          n++;
          extra=1;
	}

      if (buffer && n > len)
        {
          /* The provided buffer is too short. */
          gcry_free (tmp);
          return gcry_error (GPG_ERR_TOO_SHORT);
	}
      if (buffer)
        {
          unsigned char *s = buffer;

          if (extra)
            *s++ = 0;
          memcpy (s, tmp, n-extra);
	}
      gcry_free(tmp);
      *nwritten = n;
      return 0;
    }
  else if (format == GCRYMPI_FMT_USG)
    {
      unsigned int n = (nbits + 7)/8;

      /* Note:  We ignore the sign for this format.  */
      /* FIXME: for performance reasons we should put this into
	 mpi_aprint because we can then use the buffer directly.  */
      if (buffer && n > len)
        return gcry_error (GPG_ERR_TOO_SHORT);
      if (buffer)
        {
          unsigned char *tmp;

          tmp = _gcry_mpi_get_buffer (a, &n, NULL);
          if (!tmp)
            return gpg_error_from_syserror ();
          memcpy (buffer, tmp, n);
          gcry_free (tmp);
	}
      *nwritten = n;
      return 0;
    }
  else if (format == GCRYMPI_FMT_PGP)
    {
      unsigned int n = (nbits + 7)/8;

      /* The PGP format can only handle unsigned integers.  */
      if( a->sign )
        return gcry_error (GPG_ERR_INV_ARG);

      if (buffer && n+2 > len)
        return gcry_error (GPG_ERR_TOO_SHORT);

      if (buffer)
        {
          unsigned char *tmp;
          unsigned char *s = buffer;

          s[0] = nbits >> 8;
          s[1] = nbits;

          tmp = _gcry_mpi_get_buffer (a, &n, NULL);
          if (!tmp)
            return gpg_error_from_syserror ();
          memcpy (s+2, tmp, n);
          gcry_free (tmp);
	}
      *nwritten = n+2;
      return 0;
    }
  else if (format == GCRYMPI_FMT_SSH)
    {
      unsigned char *tmp;
      int extra = 0;
      unsigned int n;

      if (a->sign)
        return gcry_error (GPG_ERR_INTERNAL); /* Can't handle it yet.  */

      tmp = _gcry_mpi_get_buffer (a, &n, NULL);
      if (!tmp)
        return gpg_error_from_syserror ();
      if (n && (*tmp & 0x80))
        {
          n++;
          extra=1;
	}

      if (buffer && n+4 > len)
        {
          gcry_free(tmp);
          return gcry_error (GPG_ERR_TOO_SHORT);
	}

      if (buffer)
        {
          unsigned char *s = buffer;

          *s++ = n >> 24;
          *s++ = n >> 16;
          *s++ = n >> 8;
          *s++ = n;
          if (extra)
            *s++ = 0;

          memcpy (s, tmp, n-extra);
	}
      gcry_free (tmp);
      *nwritten = 4+n;
      return 0;
    }
  else if (format == GCRYMPI_FMT_HEX)
    {
      unsigned char *tmp;
      int i;
      int extra = 0;
      unsigned int n = 0;

      tmp = _gcry_mpi_get_buffer (a, &n, NULL);
      if (!tmp)
        return gpg_error_from_syserror ();
      if (!n || (*tmp & 0x80))
        extra = 2;

      if (buffer && 2*n + extra + !!a->sign + 1 > len)
        {
          gcry_free(tmp);
          return gcry_error (GPG_ERR_TOO_SHORT);
	}
      if (buffer)
        {
          unsigned char *s = buffer;

          if (a->sign)
            *s++ = '-';
          if (extra)
            {
              *s++ = '0';
              *s++ = '0';
	    }

          for (i=0; i < n; i++)
            {
              unsigned int c = tmp[i];

              *s++ = (c >> 4) < 10? '0'+(c>>4) : 'A'+(c>>4)-10 ;
              c &= 15;
              *s++ = c < 10? '0'+c : 'A'+c-10 ;
	    }
          *s++ = 0;
          *nwritten = s - buffer;
	}
      else
        {
          *nwritten = 2*n + extra + !!a->sign + 1;
	}
      gcry_free (tmp);
      return 0;
    }
  else
    return gcry_error (GPG_ERR_INV_ARG);
}


/*
 * Like gcry_mpi_print but this function allocates the buffer itself.
 * The caller has to supply the address of a pointer.  NWRITTEN may be
 * NULL.
 */
gcry_error_t
gcry_mpi_aprint (enum gcry_mpi_format format,
                 unsigned char **buffer, size_t *nwritten,
		 struct gcry_mpi *a)
{
  size_t n;
  gcry_error_t rc;

  *buffer = NULL;
  rc = gcry_mpi_print (format, NULL, 0, &n, a);
  if (rc)
    return rc;

  *buffer = mpi_is_secure(a) ? gcry_malloc_secure (n) : gcry_malloc (n);
  if (!*buffer)
    return gpg_error_from_syserror ();
  rc = gcry_mpi_print( format, *buffer, n, &n, a );
  if (rc)
    {
      gcry_free(*buffer);
      *buffer = NULL;
    }
  else if (nwritten)
    *nwritten = n;
  return rc;
}
