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

  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU Affero 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 Affero Public License for more details.

  You should have received a copy of the GNU Affero Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file auditor/report-lib.c
 * @brief helper library to facilitate generation of audit reports
 * @author Christian Grothoff
 */
#include "platform.h"
#include "report-lib.h"

/**
 * Handle to access the exchange's database.
 */
struct TALER_EXCHANGEDB_Plugin *TALER_ARL_edb;

/**
 * Which currency are we doing the audit for?
 */
char *TALER_ARL_currency;

/**
 * How many fractional digits does the currency use?
 */
struct TALER_Amount TALER_ARL_currency_round_unit;

/**
 * Our configuration.
 */
const struct GNUNET_CONFIGURATION_Handle *TALER_ARL_cfg;

/**
 * Handle to access the auditor's database.
 */
struct TALER_AUDITORDB_Plugin *TALER_ARL_adb;

/**
 * Master public key of the exchange to audit.
 */
struct TALER_MasterPublicKeyP TALER_ARL_master_pub;

/**
 * Public key of the auditor.
 */
struct TALER_AuditorPublicKeyP TALER_ARL_auditor_pub;

/**
 * REST API endpoint of the auditor.
 */
char *TALER_ARL_auditor_url;

/**
 * REST API endpoint of the exchange.
 */
char *TALER_ARL_exchange_url;

/**
 * At what time did the auditor process start?
 */
struct GNUNET_TIME_Absolute start_time;

/**
 * Results about denominations, cached per-transaction, maps denomination pub hashes
 * to `const struct TALER_EXCHANGEDB_DenominationKeyInformation`.
 */
static struct GNUNET_CONTAINER_MultiHashMap *denominations;

/**
 * Results about denominations, cached per-transaction, maps row/serial ID's
 * to `const struct TALER_EXCHANGEDB_DenominationKeyInformation`.
 */
static struct GNUNET_CONTAINER_MultiUuidmap *denominations_by_serial;

/**
 * Helper to convert a serial/row id to a uuid for the lookup
 * in a uuid hash table.
 *
 * @param serial serial id of entry
 * @param[out] uuid uuid to write
 */
static void
serial_to_uuid (
  uint64_t serial,
  struct GNUNET_Uuid *uuid)
{
  uuid->value[0] = serial;
  uuid->value[1] = serial >> 32;
  uuid->value[2] = 0;
  uuid->value[3] = 0;
}


/**
 * Function called with the results of iterate_denomination_info(),
 * or directly (!).  Used to check and add the respective denomination
 * to our hash table.
 *
 * @param cls closure, NULL
 * @param denom_serial table row of the denomaination
 * @param denom_pub public key, sometimes NULL (!)
 * @param issue issuing information with value, fees and other info about the denomination.
 */
static void
add_denomination (
  void *cls,
  uint64_t denom_serial,
  const struct TALER_DenominationPublicKey *denom_pub,
  const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue)
{
  (void) cls;
  (void) denom_pub;
  if (NULL !=
      GNUNET_CONTAINER_multihashmap_get (denominations,
                                         &issue->denom_hash.hash))
    return; /* value already known */
#if GNUNET_EXTRA_LOGGING >= 1
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Tracking denomination `%s' (%s)\n",
                GNUNET_h2s (&issue->denom_hash.hash),
                TALER_amount2s (&issue->value));
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Withdraw fee is %s\n",
                TALER_amount2s (&issue->fees.withdraw));
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Start time is %s\n",
                GNUNET_TIME_timestamp2s (issue->start));
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Expire deposit time is %s\n",
                GNUNET_TIME_timestamp2s (issue->expire_deposit));
  }
#endif
  {
    struct TALER_EXCHANGEDB_DenominationKeyInformation *i;
    struct GNUNET_Uuid uuid;

    i = GNUNET_new (struct TALER_EXCHANGEDB_DenominationKeyInformation);
    *i = *issue;
    GNUNET_assert (GNUNET_OK ==
                   GNUNET_CONTAINER_multihashmap_put (denominations,
                                                      &issue->denom_hash.hash,
                                                      i,
                                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
    serial_to_uuid (denom_serial, &uuid);
    GNUNET_assert (GNUNET_OK ==
                   GNUNET_CONTAINER_multiuuidmap_put (denominations_by_serial,
                                                      &uuid,
                                                      i,
                                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
  }
}


enum GNUNET_DB_QueryStatus
TALER_ARL_get_denomination_info_by_hash (
  const struct TALER_DenominationHashP *dh,
  const struct TALER_EXCHANGEDB_DenominationKeyInformation **issuep)
{
  enum GNUNET_DB_QueryStatus qs;

  if (NULL == denominations)
  {
    denominations = GNUNET_CONTAINER_multihashmap_create (256,
                                                          GNUNET_NO);
    if (NULL  == denominations_by_serial)
      denominations_by_serial = GNUNET_CONTAINER_multiuuidmap_create (256,
                                                                      GNUNET_NO);

    qs = TALER_ARL_edb->iterate_denomination_info (TALER_ARL_edb->cls,
                                                   &add_denomination,
                                                   NULL);
    if (0 > qs)
    {
      GNUNET_break (0);
      *issuep = NULL;
      return qs;
    }
  }
  {
    const struct TALER_EXCHANGEDB_DenominationKeyInformation *i;

    i = GNUNET_CONTAINER_multihashmap_get (denominations,
                                           &dh->hash);
    if (NULL != i)
    {
      /* cache hit */
      *issuep = i;
      return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    }
  }
  /* maybe database changed since we last iterated, give it one more shot */
  {
    struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
    uint64_t denom_serial;

    qs = TALER_ARL_edb->get_denomination_info (TALER_ARL_edb->cls,
                                               dh,
                                               &denom_serial,
                                               &issue);
    if (qs <= 0)
    {
      GNUNET_break (qs >= 0);
      if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    "Denomination %s not found\n",
                    TALER_B2S (dh));
      return qs;
    }

    add_denomination (NULL,
                      denom_serial,
                      NULL,
                      &issue);
  }
  {
    const struct TALER_EXCHANGEDB_DenominationKeyInformation *i;

    i = GNUNET_CONTAINER_multihashmap_get (denominations,
                                           &dh->hash);
    if (NULL != i)
    {
      /* cache hit */
      *issuep = i;
      return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    }
  }
  /* We found more keys, but not the denomination we are looking for :-( */
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Denomination %s not found\n",
              TALER_B2S (dh));
  return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
}


enum GNUNET_DB_QueryStatus
TALER_ARL_get_denomination_info_by_serial (
  uint64_t denom_serial,
  const struct TALER_EXCHANGEDB_DenominationKeyInformation **issuep)
{
  enum GNUNET_DB_QueryStatus qs;
  struct GNUNET_Uuid uuid;

  serial_to_uuid (denom_serial, &uuid);

  if (NULL == denominations_by_serial)
  {
    denominations_by_serial = GNUNET_CONTAINER_multiuuidmap_create (256,
                                                                    GNUNET_NO);
    if (NULL == denominations)
      denominations = GNUNET_CONTAINER_multihashmap_create (256,
                                                            GNUNET_NO);

    qs = TALER_ARL_edb->iterate_denomination_info (TALER_ARL_edb->cls,
                                                   &add_denomination,
                                                   NULL);
    if (0 > qs)
    {
      GNUNET_break (0);
      *issuep = NULL;
      return qs;
    }
  }
  {
    const struct TALER_EXCHANGEDB_DenominationKeyInformation *i;

    i = GNUNET_CONTAINER_multiuuidmap_get (denominations_by_serial,
                                           &uuid);
    if (NULL != i)
    {
      /* cache hit */
      *issuep = i;
      return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    }
  }
  /* maybe database changed since we last iterated, give it one more shot */
  {
    struct TALER_EXCHANGEDB_DenominationKeyInformation issue;

    qs = TALER_ARL_edb->get_denomination_by_serial (TALER_ARL_edb->cls,
                                                    denom_serial,
                                                    &issue);
    if (qs <= 0)
    {
      GNUNET_break (qs >= 0);
      if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    "Denomination with serial %lu not found\n",
                    denom_serial);
      return qs;
    }

    add_denomination (NULL,
                      denom_serial,
                      NULL,
                      &issue);
  }

  {
    const struct TALER_EXCHANGEDB_DenominationKeyInformation *i;

    i = GNUNET_CONTAINER_multiuuidmap_get (denominations_by_serial,
                                           &uuid);
    if (NULL != i)
    {
      /* cache hit */
      *issuep = i;
      return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    }
  }
  /* We found more keys, but not the denomination we are looking for :-( */
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Denomination with serial %lu not found\n",
              denom_serial);
  return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
}


enum GNUNET_DB_QueryStatus
TALER_ARL_get_denomination_info (
  const struct TALER_DenominationPublicKey *denom_pub,
  const struct TALER_EXCHANGEDB_DenominationKeyInformation **issue,
  struct TALER_DenominationHashP *dh)
{
  struct TALER_DenominationHashP hc;

  if (NULL == dh)
    dh = &hc;
  TALER_denom_pub_hash (denom_pub,
                        dh);
  return TALER_ARL_get_denomination_info_by_hash (dh,
                                                  issue);
}


/**
 * Perform the given @a analysis within a transaction scope.
 * Commit on success.
 *
 * @param analysis analysis to run
 * @param analysis_cls closure for @a analysis
 * @return #GNUNET_OK if @a analysis successfully committed,
 *         #GNUNET_NO if we had an error on commit (retry may help)
 *         #GNUNET_SYSERR on hard errors
 */
static enum GNUNET_GenericReturnValue
transact (TALER_ARL_Analysis analysis,
          void *analysis_cls)
{
  int ret;
  enum GNUNET_DB_QueryStatus qs;

  ret = TALER_ARL_adb->start (TALER_ARL_adb->cls);
  if (GNUNET_OK != ret)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  if (GNUNET_OK !=
      TALER_ARL_edb->preflight (TALER_ARL_edb->cls))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  ret = TALER_ARL_edb->start (TALER_ARL_edb->cls,
                              "auditor");
  if (GNUNET_OK != ret)
  {
    GNUNET_break (0);
    TALER_ARL_edb->rollback (TALER_ARL_edb->cls);
    return GNUNET_SYSERR;
  }
  qs = analysis (analysis_cls);
  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
  {
    qs = TALER_ARL_edb->commit (TALER_ARL_edb->cls);
    if (0 > qs)
    {
      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Exchange DB commit failed, rolling back transaction\n");
      TALER_ARL_adb->rollback (TALER_ARL_adb->cls);
    }
    else
    {
      qs = TALER_ARL_adb->commit (TALER_ARL_adb->cls);
      if (0 > qs)
      {
        GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Auditor DB commit failed!\n");
      }
    }
  }
  else
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Processing failed; rolling back transaction\n");
    TALER_ARL_adb->rollback (TALER_ARL_adb->cls);
    TALER_ARL_edb->rollback (TALER_ARL_edb->cls);
  }
  switch (qs)
  {
  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    return GNUNET_OK;
  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    return GNUNET_OK;
  case GNUNET_DB_STATUS_SOFT_ERROR:
    return GNUNET_NO;
  case GNUNET_DB_STATUS_HARD_ERROR:
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
TALER_ARL_setup_sessions_and_run (TALER_ARL_Analysis ana,
                                  void *ana_cls)
{
  enum GNUNET_DB_QueryStatus qs;

  if (GNUNET_SYSERR ==
      TALER_ARL_edb->preflight (TALER_ARL_edb->cls))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to initialize exchange connection.\n");
    return GNUNET_SYSERR;
  }
  if (GNUNET_SYSERR ==
      TALER_ARL_adb->preflight (TALER_ARL_adb->cls))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to initialize auditor session.\n");
    return GNUNET_SYSERR;
  }

  for (unsigned int retries=0; retries<3; retries++)
  {
    qs = transact (ana,
                   ana_cls);
    if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
      break;
  }
  if (qs < 0)
    return GNUNET_SYSERR;
  return GNUNET_OK;
}


void
TALER_ARL_amount_add_ (struct TALER_Amount *sum,
                       const struct TALER_Amount *a1,
                       const struct TALER_Amount *a2,
                       const char *filename,
                       const char *functionname,
                       unsigned int line)
{
  enum TALER_AmountArithmeticResult aar;
  const char *msg;
  char *a2s;

  aar = TALER_amount_add (sum,
                          a1,
                          a2);
  if (aar >= 0)
    return;
  switch (aar)
  {
  case TALER_AAR_INVALID_RESULT_OVERFLOW:
    msg =
      "arithmetic overflow in amount addition (likely the database is corrupt, see manual)";
    break;
  case TALER_AAR_INVALID_NORMALIZATION_FAILED:
    msg =
      "normalization failed in amount addition (likely the database is corrupt, see manual)";
    break;
  case TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE:
    msg =
      "incompatible currencies in amount addition (likely bad configuration and auditor code missing a sanity check, see manual)";
    break;
  default:
    GNUNET_assert (0); /* should be impossible */
  }
  a2s = TALER_amount_to_string (a2);
  fprintf (stderr,
           "Aborting audit due to fatal error in function %s at %s:%d trying to add %s to %s: %s\n",
           functionname,
           filename,
           line,
           TALER_amount2s (a1),
           a2s,
           msg);
  GNUNET_free (a2s);
  exit (42);
}


void
TALER_ARL_amount_subtract_ (struct TALER_Amount *diff,
                            const struct TALER_Amount *a1,
                            const struct TALER_Amount *a2,
                            const char *filename,
                            const char *functionname,
                            unsigned int line)
{
  enum TALER_AmountArithmeticResult aar;
  const char *msg;
  char *a2s;

  aar = TALER_amount_subtract (diff,
                               a1,
                               a2);
  if (aar >= 0)
    return;
  switch (aar)
  {
  case TALER_AAR_INVALID_NEGATIVE_RESULT:
    msg =
      "negative result in amount subtraction (likely the database is corrupt, see manual)";
    break;
  case TALER_AAR_INVALID_NORMALIZATION_FAILED:
    msg =
      "normalization failed in amount subtraction (likely the database is corrupt, see manual)";
    break;
  case TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE:
    msg =
      "currencies incompatible in amount subtraction (likely bad configuration and auditor code missing a sanity check, see manual)";
    break;
  default:
    GNUNET_assert (0); /* should be impossible */
  }
  a2s = TALER_amount_to_string (a2);
  fprintf (stderr,
           "Aborting audit due to fatal error in function %s at %s:%d trying to subtract %s from %s: %s\n",
           functionname,
           filename,
           line,
           a2s,
           TALER_amount2s (a1),
           msg);
  GNUNET_free (a2s);
  exit (42);
}


enum TALER_ARL_SubtractionResult
TALER_ARL_amount_subtract_neg_ (struct TALER_Amount *diff,
                                const struct TALER_Amount *a1,
                                const struct TALER_Amount *a2,
                                const char *filename,
                                const char *functionname,
                                unsigned int line)
{
  enum TALER_AmountArithmeticResult aar;
  const char *msg;
  char *a2s;

  aar = TALER_amount_subtract (diff,
                               a1,
                               a2);
  switch (aar)
  {
  case TALER_AAR_RESULT_POSITIVE:
    return TALER_ARL_SR_POSITIVE;
  case TALER_AAR_RESULT_ZERO:
    return TALER_ARL_SR_ZERO;
  case TALER_AAR_INVALID_NEGATIVE_RESULT:
    return TALER_ARL_SR_INVALID_NEGATIVE;
  case TALER_AAR_INVALID_NORMALIZATION_FAILED:
    msg =
      "normalization failed in amount subtraction (likely the database is corrupt, see manual)";
    break;
  case TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE:
    msg =
      "currencies incompatible in amount subtraction (likely bad configuration and auditor code missing a sanity check, see manual)";
    break;
  default:
    GNUNET_assert (0); /* should be impossible */
  }
  a2s = TALER_amount_to_string (a2);
  fprintf (stderr,
           "Aborting audit due to fatal error in function %s at %s:%d trying to subtract %s from %s: %s\n",
           functionname,
           filename,
           line,
           a2s,
           TALER_amount2s (a1),
           msg);
  GNUNET_free (a2s);
  exit (42);
}


enum GNUNET_GenericReturnValue
TALER_ARL_init (const struct GNUNET_CONFIGURATION_Handle *c)
{
  TALER_ARL_cfg = c;
  start_time = GNUNET_TIME_absolute_get ();

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (TALER_ARL_cfg,
                                             "auditor",
                                             "BASE_URL",
                                             &TALER_ARL_auditor_url))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "auditor",
                               "BASE_URL");
    return GNUNET_SYSERR;
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (TALER_ARL_cfg,
                                             "exchange",
                                             "BASE_URL",
                                             &TALER_ARL_exchange_url))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "exchange",
                               "BASE_URL");
    return GNUNET_SYSERR;
  }

  if (GNUNET_is_zero (&TALER_ARL_master_pub))
  {
    /* -m option not given, try configuration */
    char *master_public_key_str;

    if (GNUNET_OK !=
        GNUNET_CONFIGURATION_get_value_string (TALER_ARL_cfg,
                                               "exchange",
                                               "MASTER_PUBLIC_KEY",
                                               &master_public_key_str))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Pass option -m or set MASTER_PUBLIC_KEY in the configuration!\n");
      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                                 "exchange",
                                 "MASTER_PUBLIC_KEY");
      return GNUNET_SYSERR;
    }
    if (GNUNET_OK !=
        GNUNET_CRYPTO_eddsa_public_key_from_string (
          master_public_key_str,
          strlen (master_public_key_str),
          &TALER_ARL_master_pub.eddsa_pub))
    {
      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                 "exchange",
                                 "MASTER_PUBLIC_KEY",
                                 "invalid key");
      GNUNET_free (master_public_key_str);
      return GNUNET_SYSERR;
    }
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Running auditor against exchange master public key `%s'\n",
                master_public_key_str);
    GNUNET_free (master_public_key_str);
  } /* end of -m not given */

  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Taler auditor running for exchange master public key %s\n",
              TALER_B2S (&TALER_ARL_master_pub));

  if (GNUNET_is_zero (&TALER_ARL_auditor_pub))
  {
    char *auditor_public_key_str;

    if (GNUNET_OK ==
        GNUNET_CONFIGURATION_get_value_string (c,
                                               "auditor",
                                               "PUBLIC_KEY",
                                               &auditor_public_key_str))
    {
      if (GNUNET_OK !=
          GNUNET_CRYPTO_eddsa_public_key_from_string (
            auditor_public_key_str,
            strlen (auditor_public_key_str),
            &TALER_ARL_auditor_pub.eddsa_pub))
      {
        GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                   "auditor",
                                   "PUBLIC_KEY",
                                   "invalid key");
        GNUNET_free (auditor_public_key_str);
        return GNUNET_SYSERR;
      }
      GNUNET_free (auditor_public_key_str);
    }
  }

  if (GNUNET_is_zero (&TALER_ARL_auditor_pub))
  {
    /* public key not configured */
    /* try loading private key and deriving public key */
    char *fn;

    if (GNUNET_OK ==
        GNUNET_CONFIGURATION_get_value_filename (c,
                                                 "auditor",
                                                 "AUDITOR_PRIV_FILE",
                                                 &fn))
    {
      struct TALER_AuditorPrivateKeyP auditor_priv;

      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Loading offline private key from `%s' to get auditor public key\n",
                  fn);
      if (GNUNET_OK ==
          GNUNET_CRYPTO_eddsa_key_from_file (fn,
                                             GNUNET_NO, /* do NOT create it! */
                                             &auditor_priv.eddsa_priv))
      {
        GNUNET_CRYPTO_eddsa_key_get_public (&auditor_priv.eddsa_priv,
                                            &TALER_ARL_auditor_pub.eddsa_pub);
      }
      GNUNET_free (fn);
    }
  }

  if (GNUNET_is_zero (&TALER_ARL_auditor_pub))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO,
                               "auditor",
                               "PUBLIC_KEY/AUDITOR_PRIV_FILE");
    return GNUNET_SYSERR;
  }

  if (GNUNET_OK !=
      TALER_config_get_currency (TALER_ARL_cfg,
                                 "exchange",
                                 &TALER_ARL_currency))
  {
    return GNUNET_SYSERR;
  }
  {
    if ( (GNUNET_OK !=
          TALER_config_get_amount (TALER_ARL_cfg,
                                   "exchange",
                                   "CURRENCY_ROUND_UNIT",
                                   &TALER_ARL_currency_round_unit)) ||
         ( (0 != TALER_ARL_currency_round_unit.fraction) &&
           (0 != TALER_ARL_currency_round_unit.value) ) )
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Need non-zero value in section `exchange' under `CURRENCY_ROUND_UNIT'\n");
      return GNUNET_SYSERR;
    }
  }
  if (NULL ==
      (TALER_ARL_edb = TALER_EXCHANGEDB_plugin_load (TALER_ARL_cfg,
                                                     false)))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to initialize exchange database plugin.\n");
    TALER_ARL_done ();
    return GNUNET_SYSERR;
  }
  if (NULL ==
      (TALER_ARL_adb = TALER_AUDITORDB_plugin_load (TALER_ARL_cfg,
                                                    false)))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to initialize auditor database plugin.\n");
    TALER_ARL_done ();
    return GNUNET_SYSERR;
  }
  if (GNUNET_SYSERR ==
      TALER_ARL_adb->preflight (TALER_ARL_adb->cls))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to start session with auditor database.\n");
    TALER_ARL_done ();
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


void
TALER_ARL_done ()
{
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Audit complete\n");
  if (NULL != TALER_ARL_adb)
  {
    TALER_AUDITORDB_plugin_unload (TALER_ARL_adb);
    TALER_ARL_adb = NULL;
  }
  if (NULL != TALER_ARL_edb)
  {
    TALER_EXCHANGEDB_plugin_unload (TALER_ARL_edb);
    TALER_ARL_edb = NULL;
  }
  GNUNET_free (TALER_ARL_exchange_url);
  GNUNET_free (TALER_ARL_auditor_url);
}


/* end of report-lib.c */
