/*
  This file is part of Challenger
  Copyright (C) 2023 Taler Systems SA

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

  Challenger 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
  Challenger; see the file COPYING.GPL.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file include/challenger_database_plugin.h
 * @brief database access for Challenger
 * @author Christian Grothoff
 */
#ifndef CHALLENGER_DATABASE_PLUGIN_H
#define CHALLENGER_DATABASE_PLUGIN_H

#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_db_lib.h>
#include <jansson.h>
#include <taler/taler_util.h>


/**
 * Nonce used to uniquely (and unpredictably) identify validations.
 */
struct CHALLENGER_ValidationNonceP
{
  /**
   * 256-bit nonce used to identify validations.
   */
  uint32_t value[256 / 32];
};


/**
 * Nonce to uniquely (and unpredictably) identify access tokens.
 */
struct CHALLENGER_AccessTokenP
{
  /**
   * 256-bit nonce used to identify grants.
   */
  uint32_t value[256 / 32];
};


/**
 * Handle to interact with the database.
 *
 * Functions ending with "_TR" run their OWN transaction scope
 * and MUST NOT be called from within a transaction setup by the
 * caller.  Functions ending with "_NT" require the caller to
 * setup a transaction scope.  Functions without a suffix are
 * simple, single SQL queries that MAY be used either way.
 */
struct CHALLENGER_DatabasePlugin
{

  /**
   * Closure for all callbacks.
   */
  void *cls;

  /**
   * Name of the library which generated this plugin.  Set by the
   * plugin loader.
   */
  char *library_name;

  /**
   * Drop challenger tables. Used for testcases.
   *
   * @param cls closure
   * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
   */
  enum GNUNET_GenericReturnValue
    (*drop_tables)(void *cls);


  /**
   * Create the necessary tables if they are not present
   *
   * @param cls the @e cls of this struct with the plugin-specific state
   * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
   */
  enum GNUNET_GenericReturnValue
    (*create_tables)(void *cls);


  /**
   * Do a pre-flight check that we are not in an uncommitted transaction.
   * If we are, try to commit the previous transaction and output a warning.
   * Does not return anything, as we will continue regardless of the outcome.
   *
   * @param cls the `struct PostgresClosure` with the plugin-specific state
   * @return #GNUNET_OK if everything is fine
   *         #GNUNET_NO if a transaction was rolled back
   *         #GNUNET_SYSERR on hard errors
   */
  enum GNUNET_GenericReturnValue
    (*preflight)(void *cls);


  /**
   * Start a transaction.
   *
   * @param cls the `struct PostgresClosure` with the plugin-specific state
   * @param name unique name identifying the transaction (for debugging),
   *             must point to a constant
   * @return #GNUNET_OK on success
   */
  enum GNUNET_GenericReturnValue
    (*begin_transaction)(void *cls,
                         const char *name);


  /**
   * Commit the current transaction of a database connection.
   *
   * @param cls the `struct PostgresClosure` with the plugin-specific state
   * @return transaction status code
   */
  enum GNUNET_DB_QueryStatus
    (*commit_transaction)(void *cls);


  /**
   * Roll back the current transaction of a database connection.
   *
   * @param cls the `struct PostgresClosure` with the plugin-specific state
   */
  void
  (*rollback) (void *cls);

  /**
   * Function called to perform "garbage collection" on the
   * database, expiring records we no longer require.
   *
   * @param cls closure
   * @param expire expiration time to use
   * @return transaction status
   */
  enum GNUNET_DB_QueryStatus
    (*gc)(void *cls,
          struct GNUNET_TIME_Absolute expire);


  /**
   * Add client to the list of authorized clients.
   *
   * @param cls
   * @param client_url URL of the client
   * @param client_secret authorization secret for the client
   * @param[out] client_id set to the client ID on success
   * @return transaction status
   */
  enum GNUNET_DB_QueryStatus
    (*client_add)(void *cls,
                  const char *client_url,
                  const char *client_secret,
                  uint64_t *client_id);


  /**
   * Modify client in the list of authorized clients.
   *
   * @param cls
   * @param client_id the client ID on success
   * @param client_url URL of the client
   * @param client_secret authorization secret for the client, NULL to not modify the secret
   * @return transaction status
   */
  enum GNUNET_DB_QueryStatus
    (*client_modify)(void *cls,
                     uint64_t client_id,
                     const char *client_url,
                     const char *client_secret);

  /**
   * Delete client from the list of authorized clients.
   *
   * @param cls
   * @param client_url URL of the client
   * @return transaction status
   */
  enum GNUNET_DB_QueryStatus
    (*client_delete)(void *cls,
                     const char *client_url);


  /**
   * Check if a client is in the list of authorized clients. If @a
   * counter_increment is non-zero, the validation counter of the
   * client is incremented by the given value if the client was found.
   *
   * @param cls
   * @param client_id ID of the client
   * @param client_secret secret of the client
   * @param counter_increment change in validation counter
   * @param[out] client_url set client redirect URL (if known)
   * @return transaction status
   */
  enum GNUNET_DB_QueryStatus
    (*client_check)(void *cls,
                    uint64_t client_id,
                    const char *client_secret,
                    uint32_t counter_increment,
                    char **client_url);


  /**
   * Check if a client is in the list of authorized clients.
   *
   * @param cls
   * @param client_url client redirect URL (if known)
   * @param client_secret secret of the client
   * @param[out] set to client_id ID of the client if found
   * @return transaction status
   */
  enum GNUNET_DB_QueryStatus
    (*client_check2)(void *cls,
                     const char *client_url,
                     const char *client_secret,
                     uint64_t *client_id);


  /**
   * Start validation process by setting up a validation entry. Allows
   * the respective user who learns the @a nonce to later begin the
   * process.
   *
   * @param cls closure
   * @param client_id ID of the client
   * @param nonce unique nonce to use to identify the validation
   * @param expiration_time when will the validation expire
   * @param initial_address address the user should validate,
   *        NULL if the user should enter it themselves
   * @return transaction status
   */
  enum GNUNET_DB_QueryStatus
    (*setup_nonce)(void *cls,
                   uint64_t client_id,
                   const struct CHALLENGER_ValidationNonceP *nonce,
                   struct GNUNET_TIME_Absolute expiration_time,
                   const json_t *initial_address);


  /**
   * Set the user-provided address and PKCE parameters in a validation process.
   * Updates the address and decrements the "addresses left" counter. If the
   * address did not change, the operation is successful even without
   * the counter change.
   *
   * @param cls
   * @param nonce unique nonce to use to identify the validation
   * @param client_id client that initiated the validation
   * @param client_scope scope of the validation
   * @param client_state state of the client
   * @param client_redirect_uri where to redirect at the end, NULL to use a unique one registered for the client
   * @param code_challenge PKCE code challenge
   * @param code_challenge_method PKCE code challenge method
   * @param[out] last_address set to the last address used
   * @param[out] address_attempts_left set to number of address changing attempts left for this address
   * @param[out] pin_transmissions_left set to number of times the PIN can still be re-requested
   * @param[out] auth_attempts_left set to number of authentication attempts remaining
   * @param[out] solved set to true if the challenge is already solved
   * @param[out] last_tx_time set to the last time when we (presumably) send a PIN to @a last_address; 0 if never sent
   * @return transaction status:
   *   #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the address was changed
   *   #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if we do not permit further changes to the address (attempts exhausted)
   *   #GNUNET_DB_STATUS_HARD_ERROR on failure
   */
  enum GNUNET_DB_QueryStatus
    (*authorize_start)(void *cls,
                       const struct CHALLENGER_ValidationNonceP *nonce,
                       uint64_t client_id,
                       const char *client_scope,
                       const char *client_state,
                       const char *client_redirect_uri,
                       const char *code_challenge,
                       uint32_t code_challenge_method,
                       json_t **last_address,
                       uint32_t *address_attempts_left,
                       uint32_t *pin_transmissions_left,
                       uint32_t *auth_attempts_left,
                       bool *solved,
                       struct GNUNET_TIME_Absolute *last_tx_time);

  /**
   * Set the user-provided address in a validation process. Updates
   * the address and decrements the "addresses left" counter.  If the
   * address did not change, the operation is successful even without
   * the counter change.
   *
   * @param cls closure
   * @param nonce unique nonce to use to identify the validation
   * @param address the new address to validate
   * @param validation_duration minimum time between transmissions
   * @param[in,out] tan set to the PIN/TAN last send to @a address, input should be random PIN/TAN to use if address did not change
   * @param[out] state set to client's OAuth2 state if available
   * @param[out] last_tx_time set to the last time when we (presumably) send a PIN to @a address, input should be current time to use if the existing value for tx_time is past @a next_tx_time
   * @param[out] pin_transmit set to true if we should transmit the @a last_pin to the @a address
   * @param[out] auth_attempts_left set to number of attempts the user has left on this pin
   * @param[out] client_redirect_uri redirection URI of the client (for reporting failures)
   * @param[out] address_refused set to true if the address was refused (address change attempts exhausted)
   * @param[out] solved set to true if the challenge is already solved
   * @return transaction status:
   *   #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the address was changed
   *   #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if we do not permit further changes to the address (attempts exhausted)
   *   #GNUNET_DB_STATUS_HARD_ERROR on failure
   */
  enum GNUNET_DB_QueryStatus
    (*challenge_set_address_and_pin)(
    void *cls,
    const struct CHALLENGER_ValidationNonceP *nonce,
    const json_t *address,
    struct GNUNET_TIME_Relative validation_duration,
    uint32_t *tan,
    char **state,
    struct GNUNET_TIME_Absolute *last_tx_time,
    uint32_t *auth_attempts_left,
    bool *pin_transmit,
    char **client_redirect_uri,
    bool *address_refused,
    bool *solved);


  /**
   * Check PIN entered to validate an address.
   *
   * @param cls
   * @param nonce unique nonce to use to identify the validation
   * @param new_pin the PIN the user entered
   * @param[out] solved set to true if the PIN was correct
   * @param[out] exhausted set to true if the number of attempts to enter the correct PIN has been exhausted
   * @param[out] no_challenge set to true if we never even issued a challenge
   * @param[out] state set to client's OAuth2 state if available
   * @param[out] addr_left set to number of address changes remaining
   * @param[out] auth_attempts_left set to number of authentication attempts remaining
   * @param[out] pin_transmissions_left set to number of times the PIN can still be re-requested
   * @param[out] client_redirect_uri set to OAuth2 client redirect URI
   * @return transaction status:
   *   #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the nonce was found
   *   #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if we do not know the nonce
   *   #GNUNET_DB_STATUS_HARD_ERROR on failure
   */
  enum GNUNET_DB_QueryStatus
    (*validate_solve_pin)(void *cls,
                          const struct CHALLENGER_ValidationNonceP *nonce,
                          uint32_t new_pin,
                          bool *solved,
                          bool *exhausted,
                          bool *no_challenge,
                          char **state,
                          uint32_t *addr_left,
                          uint32_t *auth_attempts_left,
                          uint32_t *pin_transmissions_left,
                          char **client_redirect_uri);


  /**
   * Return validation details.  Used by ``/solve``, ``/auth`` and
   * ``/info`` endpoints to authorize and return validated user
   * address to the client.
   *
   * @param cls
   * @param nonce unique nonce to use to identify the validation
   * @param[out] client_secret set to secret of client (for client that setup the challenge)
   * @param[out] address set to client-provided address
   * @param[out] client_scope set to OAuth2 scope
   * @param[out] client_state set to client state
   * @param[out] client_redirect_uri set to client redirect URL
   * @return transaction status:
   *   #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the nonce was found
   *   #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if we do not know the nonce
   *   #GNUNET_DB_STATUS_HARD_ERROR on failure
   */
  enum GNUNET_DB_QueryStatus
    (*validation_get)(void *cls,
                      const struct CHALLENGER_ValidationNonceP *nonce,
                      char **client_secret,
                      json_t **address,
                      char **client_scope,
                      char **client_state,
                      char **client_redirect_uri);


  /**
   * Return address known for a particular nonce.
   *
   * @param cls
   * @param nonce unique nonce to use to identify the validation
   * @param[out] address set to client-provided address, can be set to NULL!
   * @return transaction status:
   *   #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the nonce was found
   *   #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if we do not know the nonce
   *   #GNUNET_DB_STATUS_HARD_ERROR on failure
   */
  enum GNUNET_DB_QueryStatus
    (*address_get)(void *cls,
                   const struct CHALLENGER_ValidationNonceP *nonce,
                   json_t **address);


  /**
   * Return validation details including PKCE parameters. Used by `/solve`, `/auth`, and
   * `/info` endpoints to authorize and return validated user address to the client.
   *
   * @param cls
   * @param nonce unique nonce to use to identify the validation
   * @param[out] client_secret set to secret of client (for client that setup the challenge)
   * @param[out] address set to client-provided address
   * @param[out] client_scope set to OAuth2 scope
   * @param[out] client_state set to client state
   * @param[out] client_redirect_uri set to client redirect URL
   * @param[out] code_challenge set to PKCE code challenge
   * @param[out] code_challenge_method set to PKCE code challenge method
   * @return transaction status:
   *   #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the nonce was found
   *   #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if we do not know the nonce
   *   #GNUNET_DB_STATUS_HARD_ERROR on failure
   */
  enum GNUNET_DB_QueryStatus
    (*validation_get_pkce)(void *cls,
                           const struct CHALLENGER_ValidationNonceP *nonce,
                           char **client_secret,
                           json_t **address,
                           char **client_scope,
                           char **client_state,
                           char **client_redirect_uri,
                           char **code_challenge,
                           uint32_t *code_challenge_method);

  /**
   * Add access @a grant to address under @a nonce.
   *
   * @param cls closure
   * @param nonce validation process to grant access to
   * @param grant grant token that grants access
   * @param grant_expiration for how long should the grant be valid
   * @param address_expiration for how long after validation do we consider addresses to be valid
   * @return transaction status
   */
  enum GNUNET_DB_QueryStatus
    (*token_add_token)(void *cls,
                       const struct CHALLENGER_ValidationNonceP *nonce,
                       const struct CHALLENGER_AccessTokenP *grant,
                       struct GNUNET_TIME_Relative grant_expiration,
                       struct GNUNET_TIME_Relative address_expiration);


  /**
   * Return @a address which @a grant gives access to.
   *
   * @param cls closure
   * @param grant grant token that grants access
   * @param[out] rowid account identifier within challenger
   * @param[out] address set to the address under @a grant
   * @param[out] address_expiration set to how long we consider @a address to be valid
   * @return transaction status
   */
  enum GNUNET_DB_QueryStatus
    (*info_get_token)(void *cls,
                      const struct CHALLENGER_AccessTokenP *grant,
                      uint64_t *rowid,
                      json_t **address,
                      struct GNUNET_TIME_Timestamp *address_expiration);


};
#endif
