/*
  This file is part of TALER
  Copyright (C) 2014-2023 Taler Systems SA

  TALER 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 3, or (at your option) any later version.

  TALER 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
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file pq/pq_query_helper.c
 * @brief helper functions for Taler-specific libpq (PostGres) interactions
 * @author Johannes Casaburi
 */
#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_pq_lib.h>
#include <taler/taler_pq_lib.h>
#include "donau_util.h"
#include "donau_pq_lib.h"
#include "pq_common.h"


/**
 * Function called to convert input argument into SQL parameters.
 *
 * @param cls closure
 * @param data pointer to input argument
 * @param data_len number of bytes in @a data (if applicable)
 * @param[out] param_values SQL data to set
 * @param[out] param_lengths SQL length data to set
 * @param[out] param_formats SQL format data to set
 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
 * @param scratch_length number of entries left in @a scratch
 * @return -1 on error, number of offsets used in @a scratch otherwise
 */
static int
qconv_donation_unit_pub (void *cls,
                         const void *data,
                         size_t data_len,
                         void *param_values[],
                         int param_lengths[],
                         int param_formats[],
                         unsigned int param_length,
                         void *scratch[],
                         unsigned int scratch_length)
{
  const struct DONAU_DonationUnitPublicKey *donation_unit_pub = data;
  const struct GNUNET_CRYPTO_BlindSignPublicKey *bsp = donation_unit_pub->
                                                       bsign_pub_key;
  size_t tlen;
  size_t len;
  uint32_t be[1];
  char *buf;
  void *tbuf;

  (void) cls;
  (void) data_len;
  GNUNET_assert (1 == param_length);
  GNUNET_assert (scratch_length > 0);
  GNUNET_break (NULL == cls);
  be[0] = htonl ((uint32_t) bsp->cipher);
  switch (bsp->cipher)
  {
  case GNUNET_CRYPTO_BSA_RSA:
    tlen = GNUNET_CRYPTO_rsa_public_key_encode (
      bsp->details.rsa_public_key,
      &tbuf);
    break;
  case GNUNET_CRYPTO_BSA_CS:
    tlen = sizeof (bsp->details.cs_public_key);
    break;
  default:
    GNUNET_assert (0);
  }
  len = tlen + sizeof (be);
  buf = GNUNET_malloc (len);
  GNUNET_memcpy (buf,
                 be,
                 sizeof (be));
  switch (bsp->cipher)
  {
  case GNUNET_CRYPTO_BSA_RSA:
    GNUNET_memcpy (&buf[sizeof (be)],
                   tbuf,
                   tlen);
    GNUNET_free (tbuf);
    break;
  case GNUNET_CRYPTO_BSA_CS:
    GNUNET_memcpy (&buf[sizeof (be)],
                   &bsp->details.cs_public_key,
                   tlen);
    break;
  default:
    GNUNET_assert (0);
  }

  scratch[0] = buf;
  param_values[0] = (void *) buf;
  param_lengths[0] = len;
  param_formats[0] = 1;
  return 1;
}


struct GNUNET_PQ_QueryParam
DONAU_PQ_query_param_donation_unit_pub (
  const struct DONAU_DonationUnitPublicKey *donation_unit_pub)
{
  struct GNUNET_PQ_QueryParam res = {
    .conv = &qconv_donation_unit_pub,
    .data = donation_unit_pub,
    .num_params = 1
  };

  return res;
}


/**
 * Closure for the array result specifications.  Contains type information
 * for the generic parser extract_array_generic and out-pointers for the results.
 */
struct ArrayResultCls
{
  /**
   * Oid of the expected type, must match the oid in the header of the PQResult struct
   */
  Oid oid;

  /**
   * Target type
   */
  // enum TALER_PQ_ArrayType typ;

  /**
   * If not 0, defines the expected size of each entry
   */
  size_t same_size;

  /**
   * Out-pointer to write the number of elements in the array
   */
  size_t *num;

  /**
   * Out-pointer. If @a typ is TALER_PQ_array_of_byte and @a same_size is 0,
   * allocate and put the array of @a num sizes here. NULL otherwise
   */
  size_t **sizes;

  /**
   * DB_connection, needed for OID-lookup for composite types
   */
  const struct GNUNET_PQ_Context *db;

  /**
   * Currency information for amount composites
   */
  char currency[TALER_CURRENCY_LEN];
};

/**
 * Closure for the array type handlers.
 *
 * May contain sizes information for the data, given (and handled) by the
 * caller.
 */
struct qconv_array_cls
{
  /**
   * If not null, contains the array of sizes (the size of the array is the
   * .size field in the ambient GNUNET_PQ_QueryParam struct). We do not free
   * this memory.
   *
   * If not null, this value has precedence over @a sizes, which MUST be NULL */
  const size_t *sizes;

  /**
   * If @a size and @a c_sizes are NULL, this field defines the same size
   * for each element in the array.
   */
  size_t same_size;

  /**
   * If true, the array parameter to the data pointer to the qconv_array is a
   * continuous byte array of data, either with @a same_size each or sizes
   * provided bytes by @a sizes;
   */
  bool continuous;

  /**
   * Type of the array elements
   */
  enum DONAU_PQ_ArrayType typ;

  /**
   * Oid of the array elements
   */
  Oid oid;

  /**
   * db context, needed for OID-lookup of basis-types
   */
  struct GNUNET_PQ_Context *db;
};

/**
 * Function called to convert input argument into SQL parameters for arrays
 *
 * Note: the format for the encoding of arrays for libpq is not very well
 * documented.  We peeked into various sources (postgresql and libpqtypes) for
 * guidance.
 *
 * @param cls Closure of type struct qconv_array_cls*
 * @param data Pointer to first element in the array
 * @param data_len Number of _elements_ in array @a data (if applicable)
 * @param[out] param_values SQL data to set
 * @param[out] param_lengths SQL length data to set
 * @param[out] param_formats SQL format data to set
 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
 * @param scratch_length number of entries left in @a scratch
 * @return -1 on error, number of offsets used in @a scratch otherwise
 */
static int
qconv_array (
  void *cls,
  const void *data,
  size_t data_len,
  void *param_values[],
  int param_lengths[],
  int param_formats[],
  unsigned int param_length,
  void *scratch[],
  unsigned int scratch_length)
{
  struct qconv_array_cls *meta = cls;
  size_t num = data_len;
  size_t total_size;
  const size_t *sizes;
  bool same_sized;
  void *elements = NULL;
  bool noerror = true;
  /* needed to capture the encoded rsa signatures */
  void **buffers = NULL;
  size_t *buffer_lengths = NULL;

  (void) (param_length);
  (void) (scratch_length);

  GNUNET_assert (NULL != meta);
  GNUNET_assert (num < INT_MAX);

  sizes = meta->sizes;
  same_sized = (0 != meta->same_size);

#define RETURN_UNLESS(cond) \
        do { \
          if (! (cond)) \
          { \
            GNUNET_break ((cond)); \
            noerror = false; \
            goto DONE; \
          } \
        } while (0)

  /* Calculate sizes and check bounds */
  {
    /* num * length-field */
    size_t x = sizeof(uint32_t);
    size_t y = x * num;
    RETURN_UNLESS ((0 == num) || (y / num == x));

    /* size of header */
    total_size  = x = sizeof(struct GNUNET_PQ_ArrayHeader_P);
    total_size += y;
    RETURN_UNLESS (total_size >= x);

    /* sizes of elements */
    if (same_sized)
    {
      x = num * meta->same_size;
      RETURN_UNLESS ((0 == num) || (x / num == meta->same_size));

      y = total_size;
      total_size += x;
      RETURN_UNLESS (total_size >= y);
    }
    else  /* sizes are different per element */
    {
      switch (meta->typ)
      {
      case DONAU_PQ_array_of_blinded_du_sig:
        {
          const struct DONAU_BlindedDonationUnitSignature *du_sigs = data;
          size_t len;

          buffers  = GNUNET_new_array (num, void *);
          buffer_lengths  = GNUNET_new_array (num, size_t);

          for (size_t i = 0; i<num; i++)
          {
            const struct GNUNET_CRYPTO_BlindedSignature *bs =
              du_sigs[i].blinded_sig;

            switch (bs->cipher)
            {
            case GNUNET_CRYPTO_BSA_RSA:
              len = GNUNET_CRYPTO_rsa_signature_encode (
                bs->details.blinded_rsa_signature,
                &buffers[i]);
              RETURN_UNLESS (len != 0);
              break;
            case GNUNET_CRYPTO_BSA_CS:
              len = sizeof (bs->details.blinded_cs_answer);
              break;
            default:
              GNUNET_assert (0);
              break;
            }

            /* for the cipher and marker */
            len += 2 * sizeof(uint32_t);
            buffer_lengths[i] = len;

            y = total_size;
            total_size += len;
            RETURN_UNLESS (total_size >= y);
          }
          sizes = buffer_lengths;
          break;
        }
      case DONAU_PQ_array_of_unblinded_du_sig:
        {
          const struct DONAU_DonationUnitSignature *du_sigs = data;
          size_t len;

          buffers  = GNUNET_new_array (num,
                                       void *);
          buffer_lengths  = GNUNET_new_array (num,
                                              size_t);
          for (size_t i = 0; i<num; i++)
          {
            const struct GNUNET_CRYPTO_UnblindedSignature *ubs =
              du_sigs[i].unblinded_sig;

            switch (ubs->cipher)
            {
            case GNUNET_CRYPTO_BSA_RSA:
              len = GNUNET_CRYPTO_rsa_signature_encode (
                ubs->details.rsa_signature,
                &buffers[i]);
              RETURN_UNLESS (len != 0);
              break;
            case GNUNET_CRYPTO_BSA_CS:
              len = sizeof (ubs->details.cs_signature);
              break;
            default:
              GNUNET_assert (0);
              break;
            }

            /* for the cipher and marker */
            len += 2 * sizeof(uint32_t);
            buffer_lengths[i] = len;

            y = total_size;
            total_size += len;
            RETURN_UNLESS (total_size >= y);
          }
          sizes = buffer_lengths;
          break;
        }
      default:
        {
          GNUNET_assert (0);
          break;
        }
      }
    }

    RETURN_UNLESS (INT_MAX > total_size);
    RETURN_UNLESS (0 != total_size);

    elements = GNUNET_malloc (total_size);
  }

  /* Write data */
  {
    char *out = elements;
    struct GNUNET_PQ_ArrayHeader_P h = {
      .ndim = htonl (1),        /* We only support one-dimensional arrays */
      .has_null = htonl (0),    /* We do not support NULL entries in arrays */
      .lbound = htonl (1),      /* Default start index value */
      .dim = htonl (num),
      .oid = htonl (meta->oid),
    };

    /* Write header */
    GNUNET_memcpy (out,
                   &h,
                   sizeof(h));
    out += sizeof(h);

    /* Write elements */
    for (size_t i = 0; i < num; i++)
    {
      size_t sz = same_sized ? meta->same_size : sizes[i];

      *(uint32_t *) out = htonl (sz);
      out += sizeof(uint32_t);
      switch (meta->typ)
      {
      case DONAU_PQ_array_of_blinded_du_sig:
        {
          const struct DONAU_BlindedDonationUnitSignature *denom_sigs = data;
          const struct GNUNET_CRYPTO_BlindedSignature *bs =
            denom_sigs[i].blinded_sig;
          uint32_t be[2];

          be[0] = htonl ((uint32_t) bs->cipher);
          be[1] = htonl (0x01);         /* magic margker: blinded */
          GNUNET_memcpy (out,
                         &be,
                         sizeof(be));
          out += sizeof(be);
          sz -= sizeof(be);

          switch (bs->cipher)
          {
          case GNUNET_CRYPTO_BSA_RSA:
            /* For RSA, 'same_sized' must have been false */
            GNUNET_assert (NULL != buffers);
            GNUNET_memcpy (out,
                           buffers[i],
                           sz);
            break;
          case GNUNET_CRYPTO_BSA_CS:
            GNUNET_memcpy (out,
                           &bs->details.blinded_cs_answer,
                           sz);
            break;
          default:
            GNUNET_assert (0);
          }
          break;
        }
      case DONAU_PQ_array_of_unblinded_du_sig:
        {
          const struct DONAU_DonationUnitSignature *du_sigs = data;
          const struct GNUNET_CRYPTO_UnblindedSignature *ubs =
            du_sigs[i].unblinded_sig;
          uint32_t be[2];

          be[0] = htonl ((uint32_t) ubs->cipher);
          be[1] = htonl (0x00);         /* magic margker: unblinded */
          GNUNET_memcpy (out,
                         &be,
                         sizeof(be));
          out += sizeof(be);
          sz -= sizeof(be);

          switch (ubs->cipher)
          {
          case GNUNET_CRYPTO_BSA_RSA:
            /* For RSA, 'same_sized' must have been false */
            GNUNET_assert (NULL != buffers);
            GNUNET_memcpy (out,
                           buffers[i],
                           sz);
            break;
          case GNUNET_CRYPTO_BSA_CS:
            GNUNET_memcpy (out,
                           &ubs->details.cs_signature,
                           sz);
            break;
          default:
            GNUNET_assert (0);
          }
          break;
        }
      default:
        {
          GNUNET_assert (0);
          break;
        }
      }
      out += sz;
    }
  }
  param_values[0] = elements;
  param_lengths[0] = total_size;
  param_formats[0] = 1;
  scratch[0] = elements;

DONE:
  if (NULL != buffers)
  {
    for (size_t i = 0; i<num; i++)
      GNUNET_free (buffers[i]);
    GNUNET_free (buffers);
  }
  GNUNET_free (buffer_lengths);
  if (noerror)
    return 1;
  return -1;
}


/**
 * Callback to cleanup a qconv_array_cls to be used during
 * GNUNET_PQ_cleanup_query_params_closures
 */
static void
qconv_array_cls_cleanup (void *cls)
{
  GNUNET_free (cls);
}


/**
 * Function to generate a typ specific query parameter and corresponding closure
 *
 * @param num Number of elements in @a elements
 * @param continuous If true, @a elements is an continuous array of data
 * @param elements Array of @a num elements, either continuous or pointers
 * @param sizes Array of @a num sizes, one per element, may be NULL
 * @param same_size If not 0, all elements in @a elements have this size
 * @param typ Supported internal type of each element in @a elements
 * @param oid Oid of the type to be used in Postgres
 * @param[in,out] db our database handle for looking up OIDs
 * @return Query parameter
 */
static struct GNUNET_PQ_QueryParam
query_param_array_generic (
  unsigned int num,
  bool continuous,
  const void *elements,
  const size_t *sizes,
  size_t same_size,
  enum DONAU_PQ_ArrayType typ,
  Oid oid,
  struct GNUNET_PQ_Context *db)
{
  struct qconv_array_cls *meta = GNUNET_new (struct qconv_array_cls);

  meta->typ = typ;
  meta->oid = oid;
  meta->sizes = sizes;
  meta->same_size = same_size;
  meta->continuous = continuous;
  meta->db = db;

  {
    struct GNUNET_PQ_QueryParam res = {
      .conv = qconv_array,
      .conv_cls = meta,
      .conv_cls_cleanup = qconv_array_cls_cleanup,
      .data = elements,
      .size = num,
      .num_params = 1,
    };

    return res;
  }
}


struct GNUNET_PQ_QueryParam
DONAU_PQ_query_param_array_blinded_donation_unit_sig (
  size_t num,
  const struct DONAU_BlindedDonationUnitSignature *du_sigs,
  struct GNUNET_PQ_Context *db)
{
  Oid oid;

  GNUNET_assert (GNUNET_OK ==
                 GNUNET_PQ_get_oid_by_name (db,
                                            "bytea",
                                            &oid));
  return query_param_array_generic (num,
                                    true,
                                    du_sigs,
                                    NULL,
                                    0,
                                    DONAU_PQ_array_of_blinded_du_sig,
                                    oid,
                                    NULL);
}


struct GNUNET_PQ_QueryParam
DONAU_PQ_query_param_array_donation_unit_sig (
  size_t num,
  const struct DONAU_DonationUnitSignature *du_sigs,
  struct GNUNET_PQ_Context *db)
{
  Oid oid;

  GNUNET_assert (GNUNET_OK ==
                 GNUNET_PQ_get_oid_by_name (db,
                                            "bytea",
                                            &oid));
  return query_param_array_generic (num,
                                    true,
                                    du_sigs,
                                    NULL,
                                    0,
                                    DONAU_PQ_array_of_unblinded_du_sig,
                                    oid,
                                    NULL);
}


/**
 * Extract data from a Postgres database @a result as array of a specific type
 * from row @a row.  The type information and optionally additional
 * out-parameters are given in @a cls which is of type array_result_cls.
 *
 * @param cls closure of type array_result_cls
 * @param result where to extract data from
 * @param row row to extract data from
 * @param fname name (or prefix) of the fields to extract from
 * @param[in,out] dst_size where to store size of result, may be NULL
 * @param[out] dst where to store the result
 * @return
 *   #GNUNET_YES if all results could be extracted
 *   #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
 */
static enum GNUNET_GenericReturnValue
extract_array_generic (
  void *cls,
  PGresult *result,
  int row,
  const char *fname,
  size_t *dst_size,
  void *dst)
{
  const struct ArrayResultCls *info = cls;
  int data_sz;
  char *data;
  // void *out = NULL;
  struct DONAU_BlindedDonationUnitSignature *du_sigs = NULL;
  struct GNUNET_PQ_ArrayHeader_P header;
  int col_num;

  GNUNET_assert (NULL != dst);
  *((void **) dst) = NULL;

  #define FAIL_IF(cond) \
          do { \
            if ((cond)) \
            { \
              GNUNET_break (! (cond)); \
              goto FAIL; \
            } \
          } while (0)

  col_num = PQfnumber (result,
                       fname);
  FAIL_IF (0 > col_num);
  data_sz = PQgetlength (result,
                         row,
                         col_num);
  FAIL_IF (0 > data_sz);
  FAIL_IF (sizeof(header) > (size_t) data_sz);

  data = PQgetvalue (result, row, col_num);
  FAIL_IF (NULL == data);

  {
    struct GNUNET_PQ_ArrayHeader_P *h =
      (struct GNUNET_PQ_ArrayHeader_P *) data;

    header.ndim = ntohl (h->ndim);
    header.has_null = ntohl (h->has_null);
    header.oid = ntohl (h->oid);
    header.dim = ntohl (h->dim);
    header.lbound = ntohl (h->lbound);

    FAIL_IF (1 != header.ndim);
    FAIL_IF (INT_MAX <= header.dim);
    FAIL_IF (0 != header.has_null);
    FAIL_IF (1 != header.lbound);
    FAIL_IF (info->oid != header.oid);
  }

  if (NULL != info->num)
    *info->num = header.dim;

  {
    char *in = data + sizeof(header);

    if (0 == header.dim)
    {
      if (NULL != dst_size)
        *dst_size = 0;
      goto FAIL;
    }
    du_sigs = GNUNET_new_array (header.dim,
                                struct DONAU_BlindedDonationUnitSignature);
    *((void **) dst) = du_sigs;

    /* copy data */
    for (uint32_t i = 0; i < header.dim; i++)
    {
      struct DONAU_BlindedDonationUnitSignature *du_sig = &du_sigs[i];
      struct GNUNET_CRYPTO_BlindedSignature *bs;
      uint32_t be[2];
      uint32_t val;
      size_t sz;

      GNUNET_memcpy (&val,
                     in,
                     sizeof(val));
      sz = ntohl (val);
      FAIL_IF (sizeof(be) > sz);
      in += sizeof(val);
      GNUNET_memcpy (&be,
                     in,
                     sizeof(be));
      FAIL_IF (0x01 != ntohl (be[1]));      /* magic marker: blinded */

      in += sizeof(be);
      sz -= sizeof(be);
      bs = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature);
      bs->cipher = ntohl (be[0]);
      bs->rc = 1;
      switch (bs->cipher)
      {
      case GNUNET_CRYPTO_BSA_RSA:
        bs->details.blinded_rsa_signature
          = GNUNET_CRYPTO_rsa_signature_decode (in,
                                                sz);
        if (NULL == bs->details.blinded_rsa_signature)
        {
          GNUNET_free (bs);
          FAIL_IF (true);
        }
        break;
      case GNUNET_CRYPTO_BSA_CS:
        if (sizeof(bs->details.blinded_cs_answer) != sz)
        {
          GNUNET_free (bs);
          FAIL_IF (true);
        }
        GNUNET_memcpy (&bs->details.blinded_cs_answer,
                       in,
                       sz);
        break;
      default:
        GNUNET_free (bs);
        FAIL_IF (true);
      }
      du_sig->blinded_sig = bs;
      in += sz;
    }
    return GNUNET_OK;
  }
FAIL:
  if (NULL != du_sigs)
  {
    for (size_t i = 0; i < *info->num; i++)
      if (NULL != du_sigs[i].blinded_sig)
        GNUNET_CRYPTO_blinded_sig_decref (du_sigs[i].blinded_sig);
    GNUNET_free (du_sigs);
    *((void **) dst) = NULL;
  }
  return GNUNET_SYSERR;
#undef FAIL_IF
}


/**
 * Cleanup of the data and closure of an array spec.
 */
static void
array_cleanup (void *cls,
               void *rd)
{
  struct ArrayResultCls *info = cls;
  void **dst = rd;
  struct DONAU_BlindedDonationUnitSignature *du_sigs = *dst;

  if ( (0 == info->same_size) &&
       (NULL != info->sizes) )
    GNUNET_free (*(info->sizes));

  GNUNET_assert (NULL != info->num);
  for (size_t i = 0; i < *info->num; i++)
    if (NULL != du_sigs[i].blinded_sig)
      GNUNET_CRYPTO_blinded_sig_decref (du_sigs[i].blinded_sig);
  GNUNET_free (info);
  GNUNET_free (du_sigs);
  *dst = NULL;
}


struct GNUNET_PQ_ResultSpec
DONAU_PQ_result_spec_array_blinded_donation_unit_sig (
  struct GNUNET_PQ_Context *db,
  const char *name,
  size_t *num,
  struct DONAU_BlindedDonationUnitSignature **du_sigs)
{
  struct ArrayResultCls *info = GNUNET_new (struct ArrayResultCls);

  *num = 0;
  *du_sigs = NULL;
  info->num = num;
  // info->typ = TALER_PQ_array_of_blinded_denom_sig;
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_PQ_get_oid_by_name (db,
                                            "bytea",
                                            &info->oid));
  {
    struct GNUNET_PQ_ResultSpec res = {
      .conv = extract_array_generic,
      .cleaner = array_cleanup,
      .dst = (void *) du_sigs,
      .fname = name,
      .cls = info
    };

    return res;
  }
}


/* end of pq/pq_query_helper.c */
