/*
     This file is part of GNUnet.
     Copyright (C) 2012, 2013 GNUnet e.V.

     GNUnet is free software: you can redistribute it and/or modify it
     under the terms of the GNU Affero General Public License as published
     by the Free Software Foundation, either version 3 of the License,
     or (at your option) any later version.

     GNUnet 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
     Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */

/**
 * @file util/crypto_mpi.c
 * @brief Helper functions for libgcrypt MPIs
 * @author Christian Grothoff
 * @author Florian Dold
 */
#include "platform.h"
#include <gcrypt.h>
#include "gnunet_crypto_lib.h"


#define LOG(kind, ...) GNUNET_log_from (kind, "util-crypto-mpi", __VA_ARGS__)

/**
 * Log an error message at log-level 'level' that indicates
 * a failure of the command 'cmd' with the message given
 * by gcry_strerror(rc).
 */
#define LOG_GCRY(level, cmd, rc) do { LOG (level, _ ( \
                                             "`%s' failed at %s:%d with error: %s\n"), \
                                           cmd, __FILE__, __LINE__, \
                                           gcry_strerror (rc)); } while (0)


/**
 * If target != size, move @a target bytes to the end of the size-sized
 * buffer and zero out the first @a target - @a size bytes.
 *
 * @param buf original buffer
 * @param size number of bytes in @a buf
 * @param target target size of the buffer
 */
static void
adjust (void *buf,
        size_t size,
        size_t target)
{
  char *p = buf;

  if (size < target)
  {
    memmove (&p[target - size], buf, size);
    memset (buf, 0, target - size);
  }
}


/**
 * Output the given MPI value to the given buffer in
 * network byte order.
 * The MPI @a val may not be negative.
 *
 * @param buf where to output to
 * @param size number of bytes in @a buf
 * @param val value to write to @a buf
 */
void
GNUNET_CRYPTO_mpi_print_unsigned (void *buf,
                                  size_t size,
                                  gcry_mpi_t val)
{
  size_t rsize;
  int rc;

  if (gcry_mpi_get_flag (val, GCRYMPI_FLAG_OPAQUE))
  {
    /* Store opaque MPIs left aligned into the buffer.  */
    unsigned int nbits;
    const void *p;

    p = gcry_mpi_get_opaque (val, &nbits);
    GNUNET_assert (p);
    rsize = (nbits + 7) / 8;
    if (rsize > size)
      rsize = size;
    GNUNET_memcpy (buf, p, rsize);
    if (rsize < size)
      memset (buf + rsize, 0, size - rsize);
  }
  else
  {
    /* Store regular MPIs as unsigned integers right aligned into
       the buffer.  */
    rsize = size;
    if (0 !=
        (rc = gcry_mpi_print (GCRYMPI_FMT_USG,
                              buf,
                              rsize, &rsize,
                              val)))
    {
      LOG_GCRY (GNUNET_ERROR_TYPE_ERROR,
                "gcry_mpi_print",
                rc);
      GNUNET_assert (0);
    }
    adjust (buf, rsize, size);
  }
}


/**
 * Convert data buffer into MPI value.
 * The buffer is interpreted as network
 * byte order, unsigned integer.
 *
 * @param result where to store MPI value (allocated)
 * @param data raw data (GCRYMPI_FMT_USG)
 * @param size number of bytes in @a data
 */
void
GNUNET_CRYPTO_mpi_scan_unsigned (gcry_mpi_t *result,
                                 const void *data,
                                 size_t size)
{
  int rc;

  if (0 != (rc = gcry_mpi_scan (result,
                                GCRYMPI_FMT_USG,
                                data, size, &size)))
  {
    LOG_GCRY (GNUNET_ERROR_TYPE_ERROR,
              "gcry_mpi_scan",
              rc);
    GNUNET_assert (0);
  }
}


/**
 * Convert little endian data buffer into MPI value.
 * The buffer is interpreted as network
 * byte order, unsigned integer.
 *
 * @param result where to store MPI value (allocated)
 * @param data raw data (GCRYMPI_FMT_USG)
 * @param size number of bytes in @a data
 */
void
GNUNET_CRYPTO_mpi_scan_unsigned_le (gcry_mpi_t *result,
                                    const void *data,
                                    size_t size)
{
  int rc;

  if (0 != (rc = gcry_mpi_scan (result,
                                GCRYMPI_FMT_USG,
                                data, size, &size)))
  {
    LOG_GCRY (GNUNET_ERROR_TYPE_ERROR,
              "gcry_mpi_scan",
              rc);
    GNUNET_assert (0);
  }
}


/* end of crypto_mpi.c */
