/*
  This file is part of TALER
  Copyright (C) 2024 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 json/donau_json.c
 * @brief helper functions for JSON processing using libjansson
 * @author Lukas Matyja
 */
#include "donau_config.h"
#include <gnunet/gnunet_util_lib.h>
#include <taler/taler_util.h>
#include <taler/taler_json_lib.h>
#include <unistr.h>
#include "donau_json_lib.h"

/**
 * Convert string value to numeric cipher value.
 *
 * @param cipher_s input string
 * @return numeric cipher value
 */
static enum GNUNET_CRYPTO_BlindSignatureAlgorithm
string_to_cipher (const char *cipher_s)
{
  if (0 == strcasecmp (cipher_s,
                       "RSA"))
    return GNUNET_CRYPTO_BSA_RSA;
  if (0 == strcasecmp (cipher_s,
                       "CS"))
    return GNUNET_CRYPTO_BSA_CS;
  return GNUNET_CRYPTO_BSA_INVALID;
}


/**
 * Parse given JSON object partially into a donation unit public key.
 *
 * Depending on the cipher in cls, it parses the corresponding public key type.
 *
 * @param cls closure, enum GNUNET_CRYPTO_BlindSignatureAlgorithm
 * @param root the json object representing data
 * @param[out] spec where to write the data
 * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
 */
static enum GNUNET_GenericReturnValue
parse_donation_unit_pub (void *cls,
                         json_t *root,
                         struct GNUNET_JSON_Specification *spec)
{
  struct DONAU_DonationUnitPublicKey *donation_unit_pub = spec->ptr;
  struct GNUNET_CRYPTO_BlindSignPublicKey *bsign_pub;
  const char *cipher;
  struct GNUNET_JSON_Specification dspec[] = {
    GNUNET_JSON_spec_string ("cipher",
                             &cipher),
    GNUNET_JSON_spec_end ()
  };
  const char *emsg;
  unsigned int eline;

  (void) cls;
  if (GNUNET_OK !=
      GNUNET_JSON_parse (root,
                         dspec,
                         &emsg,
                         &eline))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  bsign_pub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey);
  bsign_pub->rc = 1;
  bsign_pub->cipher = string_to_cipher (cipher);
  switch (bsign_pub->cipher)
  {
  case GNUNET_CRYPTO_BSA_INVALID:
    break;
  case GNUNET_CRYPTO_BSA_RSA:
    {
      struct GNUNET_JSON_Specification ispec[] = {
        GNUNET_JSON_spec_rsa_public_key (
          "rsa_public_key",
          &bsign_pub->details.rsa_public_key),
        GNUNET_JSON_spec_fixed_auto (
          "pub_key_hash",
          &bsign_pub->pub_key_hash),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (root,
                             ispec,
                             &emsg,
                             &eline))
      {
        GNUNET_break_op (0);
        GNUNET_free (bsign_pub);
        return GNUNET_SYSERR;
      }
      donation_unit_pub->bsign_pub_key = bsign_pub;
      return GNUNET_OK;
    }
  case GNUNET_CRYPTO_BSA_CS:
    {
      struct GNUNET_JSON_Specification ispec[] = {
        GNUNET_JSON_spec_fixed ("cs_public_key",
                                &bsign_pub->details.cs_public_key,
                                sizeof (bsign_pub->details.cs_public_key)),
        GNUNET_JSON_spec_fixed_auto (
          "pub_key_hash",
          &bsign_pub->pub_key_hash),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (root,
                             ispec,
                             &emsg,
                             &eline))
      {
        GNUNET_break_op (0);
        GNUNET_free (bsign_pub);
        return GNUNET_SYSERR;
      }
      donation_unit_pub->bsign_pub_key = bsign_pub;
      return GNUNET_OK;
    }
  }
  GNUNET_break_op (0);
  GNUNET_free (bsign_pub);
  return GNUNET_SYSERR;
}


/**
 * Cleanup data left from parsing donation unit public key.
 *
 * @param cls closure, NULL
 * @param[out] spec where to free the data
 */
static void
clean_donation_unit_pub (void *cls,
                         struct GNUNET_JSON_Specification *spec)
{
  struct DONAU_DonationUnitPublicKey *donation_unit_pub = spec->ptr;

  (void) cls;
  DONAU_donation_unit_pub_free (donation_unit_pub);
}


struct GNUNET_JSON_Specification
DONAU_JSON_spec_donation_unit_pub (const char *field,
                                   struct DONAU_DonationUnitPublicKey *pk)
{
  struct GNUNET_JSON_Specification ret = {
    .parser = &parse_donation_unit_pub,
    .cleaner = &clean_donation_unit_pub,
    .field = field,
    .ptr = pk
  };

  return ret;
}


/**
 * Parse given JSON object to blinded unique donation identifier.
 *
 * @param cls closure, NULL
 * @param root the json object representing data
 * @param[out] spec where to write the data
 * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
 */
static enum GNUNET_GenericReturnValue
parse_blinded_donation_identifier (void *cls,
                                   json_t *root,
                                   struct GNUNET_JSON_Specification *spec)
{
  struct DONAU_BlindedUniqueDonorIdentifier *blinded_udi = spec->ptr;
  struct GNUNET_CRYPTO_BlindedMessage *blinded_message;
  const char *cipher;
  struct GNUNET_JSON_Specification dspec[] = {
    GNUNET_JSON_spec_string ("cipher",
                             &cipher),
    GNUNET_JSON_spec_end ()
  };
  const char *emsg;
  unsigned int eline;

  (void) cls;
  if (GNUNET_OK !=
      GNUNET_JSON_parse (root,
                         dspec,
                         &emsg,
                         &eline))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  blinded_message = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
  blinded_message->rc = 1;
  blinded_message->cipher = string_to_cipher (cipher);
  switch (blinded_message->cipher)
  {
  case GNUNET_CRYPTO_BSA_INVALID:
    break;
  case GNUNET_CRYPTO_BSA_RSA:
    {
      struct GNUNET_JSON_Specification ispec[] = {
        GNUNET_JSON_spec_varsize (
          "rsa_blinded_identifier",
          &blinded_message->details.rsa_blinded_message.blinded_msg,
          &blinded_message->details.rsa_blinded_message.blinded_msg_size),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (root,
                             ispec,
                             &emsg,
                             &eline))
      {
        GNUNET_break_op (0);
        GNUNET_free (blinded_message);
        return GNUNET_SYSERR;
      }
      blinded_udi->blinded_message = blinded_message;
      return GNUNET_OK;
    }
  case GNUNET_CRYPTO_BSA_CS:
    {
      struct GNUNET_JSON_Specification ispec[] = {
        GNUNET_JSON_spec_fixed_auto (
          "cs_nonce",
          &blinded_message->details.cs_blinded_message.nonce),
        GNUNET_JSON_spec_fixed_auto (
          "cs_blinded_c0",
          &blinded_message->details.cs_blinded_message.c[0]),
        GNUNET_JSON_spec_fixed_auto (
          "cs_blinded_c1",
          &blinded_message->details.cs_blinded_message.c[1]),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (root,
                             ispec,
                             &emsg,
                             &eline))
      {
        GNUNET_break_op (0);
        GNUNET_free (blinded_message);
        return GNUNET_SYSERR;
      }
      blinded_udi->blinded_message = blinded_message;
      return GNUNET_OK;
    }
  }
  GNUNET_break_op (0);
  GNUNET_free (blinded_message);
  return GNUNET_SYSERR;
}


/**
 * Cleanup data left from parsing blinded unique donation identifier.
 *
 * @param cls closure, NULL
 * @param[out] spec where to free the data
 */
static void
clean_blinded_donation_identifier (void *cls,
                                   struct GNUNET_JSON_Specification *spec)
{
  struct TALER_BlindedPlanchet *blinded_udi = spec->ptr;

  (void) cls;
  TALER_blinded_planchet_free (blinded_udi);
}


struct GNUNET_JSON_Specification
DONAU_JSON_spec_blinded_donation_identifier (const char *field,
                                             struct
                                             DONAU_BlindedUniqueDonorIdentifier
                                             *
                                             blinded_udi)
{
  struct GNUNET_JSON_Specification ret = {
    .parser = &parse_blinded_donation_identifier,
    .cleaner = &clean_blinded_donation_identifier,
    .field = field,
    .ptr = blinded_udi
  };

  blinded_udi->blinded_message = NULL;
  return ret;
}


/**
 * Parse given JSON object to blinded donation unit signature.
 *
 * @param cls closure, NULL
 * @param root the json object representing data
 * @param[out] spec where to write the data
 * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
 */
static enum GNUNET_GenericReturnValue
parse_blinded_donation_unit_sig (void *cls,
                                 json_t *root,
                                 struct GNUNET_JSON_Specification *spec)
{
  struct DONAU_BlindedDonationUnitSignature *du_sig = spec->ptr;
  struct GNUNET_CRYPTO_BlindedSignature *blinded_sig;
  const char *cipher;
  struct GNUNET_JSON_Specification dspec[] = {
    GNUNET_JSON_spec_string ("cipher",
                             &cipher),
    GNUNET_JSON_spec_end ()
  };
  const char *emsg;
  unsigned int eline;

  (void) cls;
  if (GNUNET_OK !=
      GNUNET_JSON_parse (root,
                         dspec,
                         &emsg,
                         &eline))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  blinded_sig = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature);
  blinded_sig->cipher = string_to_cipher (cipher);
  blinded_sig->rc = 1;
  switch (blinded_sig->cipher)
  {
  case GNUNET_CRYPTO_BSA_INVALID:
    break;
  case GNUNET_CRYPTO_BSA_RSA:
    {
      struct GNUNET_JSON_Specification ispec[] = {
        GNUNET_JSON_spec_rsa_signature (
          "blinded_rsa_signature",
          &blinded_sig->details.blinded_rsa_signature),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (root,
                             ispec,
                             &emsg,
                             &eline))
      {
        GNUNET_break_op (0);
        GNUNET_free (blinded_sig);
        return GNUNET_SYSERR;
      }
      du_sig->blinded_sig = blinded_sig;
      return GNUNET_OK;
    }
  case GNUNET_CRYPTO_BSA_CS:
    {
      struct GNUNET_JSON_Specification ispec[] = {
        GNUNET_JSON_spec_uint32 ("b",
                                 &blinded_sig->details.blinded_cs_answer.b),
        GNUNET_JSON_spec_fixed_auto ("s",
                                     &blinded_sig->details.blinded_cs_answer.
                                     s_scalar),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (root,
                             ispec,
                             &emsg,
                             &eline))
      {
        GNUNET_break_op (0);
        GNUNET_free (blinded_sig);
        return GNUNET_SYSERR;
      }
      du_sig->blinded_sig = blinded_sig;
      return GNUNET_OK;
    }
  }
  GNUNET_break_op (0);
  GNUNET_free (blinded_sig);
  return GNUNET_SYSERR;
}


/**
 * Cleanup data left from parsing donation unit public key.
 *
 * @param cls closure, NULL
 * @param[out] spec where to free the data
 */
static void
clean_blinded_donation_unit_sig (void *cls,
                                 struct GNUNET_JSON_Specification *spec)
{
  struct DONAU_BlindedDonationUnitSignature *du_sig = spec->ptr;

  (void) cls;
  DONAU_blinded_donation_unit_sig_free (du_sig);
}


struct GNUNET_JSON_Specification
DONAU_JSON_spec_blinded_donation_unit_sig (const char *field,
                                           struct
                                           DONAU_BlindedDonationUnitSignature *
                                           sig)
{
  struct GNUNET_JSON_Specification ret = {
    .parser = &parse_blinded_donation_unit_sig,
    .cleaner = &clean_blinded_donation_unit_sig,
    .field = field,
    .ptr = sig
  };

  sig->blinded_sig = NULL;
  return ret;
}


struct GNUNET_JSON_PackSpec
DONAU_JSON_pack_donation_unit_sig (
  const char *name,
  const struct DONAU_DonationUnitSignature *sig)
{
  const struct GNUNET_CRYPTO_UnblindedSignature *bs;
  struct GNUNET_JSON_PackSpec ps = {
    .field_name = name,
  };

  if (NULL == sig)
    return ps;
  bs = sig->unblinded_sig;
  switch (bs->cipher)
  {
  case GNUNET_CRYPTO_BSA_INVALID:
    break;
  case GNUNET_CRYPTO_BSA_RSA:
    ps.object = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("cipher",
                               "RSA"),
      GNUNET_JSON_pack_rsa_signature ("rsa_signature",
                                      bs->details.rsa_signature));
    return ps;
  case GNUNET_CRYPTO_BSA_CS:
    ps.object = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("cipher",
                               "CS"),
      GNUNET_JSON_pack_data_auto ("cs_signature_r",
                                  &bs->details.cs_signature.r_point),
      GNUNET_JSON_pack_data_auto ("cs_signature_s",
                                  &bs->details.cs_signature.s_scalar));
    return ps;
  }
  GNUNET_assert (0);
  return ps;
}


struct GNUNET_JSON_PackSpec
DONAU_JSON_pack_blinded_donation_unit_sig (
  const char *name,
  const struct DONAU_BlindedDonationUnitSignature *sig)
{
  const struct GNUNET_CRYPTO_BlindedSignature *bs;
  struct GNUNET_JSON_PackSpec ps = {
    .field_name = name,
  };

  if (NULL == sig)
    return ps;
  bs = sig->blinded_sig;
  switch (bs->cipher)
  {
  case GNUNET_CRYPTO_BSA_INVALID:
    break;
  case GNUNET_CRYPTO_BSA_RSA:
    ps.object = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("cipher",
                               "RSA"),
      GNUNET_JSON_pack_rsa_signature ("blinded_rsa_signature",
                                      bs->details.blinded_rsa_signature));
    return ps;
  case GNUNET_CRYPTO_BSA_CS:
    ps.object = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("cipher",
                               "CS"),
      GNUNET_JSON_pack_uint64 ("b",
                               bs->details.blinded_cs_answer.b),
      GNUNET_JSON_pack_data_auto ("s",
                                  &bs->details.blinded_cs_answer.s_scalar));
    return ps;
  }
  GNUNET_assert (0);
  return ps;
}


struct GNUNET_JSON_PackSpec
DONAU_JSON_pack_blinded_donation_identifier (
  const char *name,
  const struct DONAU_BlindedUniqueDonorIdentifier *blinded_udi)
{
  const struct GNUNET_CRYPTO_BlindedMessage *bm;
  struct GNUNET_JSON_PackSpec ps = {
    .field_name = name,
  };

  if (NULL == blinded_udi)
    return ps;
  bm = blinded_udi->blinded_message;
  switch (bm->cipher)
  {
  case GNUNET_CRYPTO_BSA_INVALID:
    break;
  case GNUNET_CRYPTO_BSA_RSA:
    ps.object = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("cipher",
                               "RSA"),
      GNUNET_JSON_pack_data_varsize (
        "rsa_blinded_identifier",
        bm->details.rsa_blinded_message.blinded_msg,
        bm->details.rsa_blinded_message.blinded_msg_size));
    return ps;
  case GNUNET_CRYPTO_BSA_CS:
    ps.object = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("cipher",
                               "CS"),
      GNUNET_JSON_pack_data_auto (
        "cs_nonce",
        &bm->details.cs_blinded_message.nonce),
      GNUNET_JSON_pack_data_auto (
        "cs_blinded_c0",
        &bm->details.cs_blinded_message.c[0]),
      GNUNET_JSON_pack_data_auto (
        "cs_blinded_c1",
        &bm->details.cs_blinded_message.c[1]));
    return ps;
  }
  GNUNET_assert (0);
  return ps;
}


/* end of json/donau_json.c */
