/*
  This file is part of TALER
  Copyright (C) 2022-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 util/age_restriction.c
 * @brief Functions that are used for age restriction
 * @author Özgür Kesim
 */
#include "platform.h"
#include "taler_util.h"
#include "taler_signatures.h"
#include <gnunet/gnunet_json_lib.h>
#include <gcrypt.h>
#include <stdint.h>

struct
#ifndef AGE_RESTRICTION_WITH_ECDSA
GNUNET_CRYPTO_Edx25519PublicKey
#else
GNUNET_CRYPTO_EcdsaPublicKey
#endif
TALER_age_commitment_base_public_key = {
  .q_y = { 0x64, 0x41, 0xb9, 0xbd, 0xbf, 0x14, 0x39, 0x8e,
           0x46, 0xeb, 0x5c, 0x1d, 0x34, 0xd3, 0x9b, 0x2f,
           0x9b, 0x7d, 0xc8, 0x18, 0xeb, 0x9c, 0x09, 0xfb,
           0x43, 0xad, 0x16, 0x64, 0xbc, 0x18, 0x49, 0xb5},
};

void
TALER_age_commitment_hash (
  const struct TALER_AgeCommitment *commitment,
  struct TALER_AgeCommitmentHash *ahash)
{
  struct GNUNET_HashContext *hash_context;
  struct GNUNET_HashCode hash;

  GNUNET_assert (NULL != ahash);
  if (NULL == commitment)
  {
    memset (ahash, 0, sizeof(struct TALER_AgeCommitmentHash));
    return;
  }

  GNUNET_assert (__builtin_popcount (commitment->mask.bits) - 1 ==
                 (int) commitment->num);

  hash_context = GNUNET_CRYPTO_hash_context_start ();

  for (size_t i = 0; i < commitment->num; i++)
  {
    GNUNET_CRYPTO_hash_context_read (hash_context,
                                     &commitment->pubs[i],
                                     sizeof(commitment->pubs[i]));
  }

  GNUNET_CRYPTO_hash_context_finish (hash_context,
                                     &hash);
  GNUNET_memcpy (&ahash->shash.bits,
                 &hash.bits,
                 sizeof(ahash->shash.bits));
}


/* To a given age value between 0 and 31, returns the index of the age group
 * defined by the given mask.
 */
uint8_t
TALER_get_age_group (
  const struct TALER_AgeMask *mask,
  uint8_t age)
{
  uint32_t m = mask->bits;
  uint8_t i = 0;

  while (m > 0)
  {
    if (0 >= age)
      break;
    m = m >> 1;
    i += m & 1;
    age--;
  }
  return i;
}


uint8_t
TALER_get_lowest_age (
  const struct TALER_AgeMask *mask,
  uint8_t age)
{
  uint32_t m = mask->bits;
  uint8_t group = TALER_get_age_group (mask, age);
  uint8_t lowest = 0;

  while (group > 0)
  {
    m = m >> 1;
    if (m & 1)
      group--;
    lowest++;
  }

  return lowest;
}


#ifdef AGE_RESTRICTION_WITH_ECDSA
/**
 * @brief Helper function to generate a ECDSA private key
 *
 * @param seed Input seed
 * @param size Size of the seed in bytes
 * @param[out] pkey ECDSA private key
 */
static void
ecdsa_create_from_seed (
  const void *seed,
  size_t seed_size,
  struct GNUNET_CRYPTO_EcdsaPrivateKey *key)
{
  enum GNUNET_GenericReturnValue ret;

  GNUNET_assert (
    GNUNET_OK ==
    GNUNET_CRYPTO_kdf (key,
                       sizeof (*key),
                       &seed,
                       seed_size,
                       "age commitment",
                       sizeof ("age commitment") - 1,
                       NULL, 0));
  /* See GNUNET_CRYPTO_ecdsa_key_create */
  key->d[0] &= 248;
  key->d[31] &= 127;
  key->d[31] |= 64;
}


#endif


void
TALER_age_restriction_commit (
  const struct TALER_AgeMask *mask,
  uint8_t age,
  const struct GNUNET_HashCode *seed,
  struct TALER_AgeCommitmentProof *ncp)
{
  struct GNUNET_HashCode seed_i;
  uint8_t num_pub;
  uint8_t num_priv;
  size_t i;

  GNUNET_assert (NULL != mask);
  GNUNET_assert (NULL != seed);
  GNUNET_assert (NULL != ncp);
  GNUNET_assert (mask->bits & 1); /* first bit must have been set */

  num_pub = __builtin_popcount (mask->bits) - 1;
  num_priv = TALER_get_age_group (mask, age);

  GNUNET_assert (31 > num_priv);
  GNUNET_assert (num_priv <= num_pub);

  seed_i = *seed;
  ncp->commitment.mask.bits = mask->bits;
  ncp->commitment.num = num_pub;
  ncp->proof.num = num_priv;
  ncp->proof.privs = NULL;

  ncp->commitment.pubs = GNUNET_new_array (
    num_pub,
    struct TALER_AgeCommitmentPublicKeyP);

  if (0 < num_priv)
    ncp->proof.privs = GNUNET_new_array (
      num_priv,
      struct TALER_AgeCommitmentPrivateKeyP);

  /* Create as many private keys as we need and fill the rest of the
   * public keys with valid curve points.
   * We need to make sure that the public keys are proper points on the
   * elliptic curve, so we can't simply fill the struct with random values. */
  for (i = 0; i < num_pub; i++)
  {
    struct TALER_AgeCommitmentPrivateKeyP key = {0};
    struct TALER_AgeCommitmentPrivateKeyP *pkey = &key;

    /* Only save the private keys for age groups less than num_priv */
    if (i < num_priv)
      pkey = &ncp->proof.privs[i];

#ifndef AGE_RESTRICTION_WITH_ECDSA
    GNUNET_CRYPTO_edx25519_key_create_from_seed (&seed_i,
                                                 sizeof(seed_i),
                                                 &pkey->priv);
    GNUNET_CRYPTO_edx25519_key_get_public (&pkey->priv,
                                           &ncp->commitment.pubs[i].pub);
#else
    ecdsa_create_from_seed (&seed_i,
                            sizeof(seed_i),
                            &pkey->priv);
    GNUNET_CRYPTO_ecdsa_key_get_public (&pkey->priv,
                                        &ncp->commitment.pubs[i].pub);
#endif

    seed_i.bits[0] += 1;
  }
}


enum GNUNET_GenericReturnValue
TALER_age_commitment_derive (
  const struct TALER_AgeCommitment *orig,
  const struct GNUNET_HashCode *salt,
  struct TALER_AgeCommitment *newac)
{
  GNUNET_assert (NULL != newac);
  GNUNET_assert (((int) orig->num) ==
                 __builtin_popcount (orig->mask.bits) - 1);

  newac->mask = orig->mask;
  newac->num = orig->num;
  newac->pubs = GNUNET_new_array (
    newac->num,
    struct TALER_AgeCommitmentPublicKeyP);

#ifndef AGE_RESTRICTION_WITH_ECDSA
  /* Derive the public keys */
  for (size_t i = 0; i < orig->num; i++)
  {
    GNUNET_CRYPTO_edx25519_public_key_derive (
      &orig->pubs[i].pub,
      salt,
      sizeof(*salt),
      &newac->pubs[i].pub);
  }
#else
  {
    const char *label = GNUNET_h2s (salt);

    /* Derive the public keys */
    for (size_t i = 0; i < orig->num; i++)
    {
      GNUNET_CRYPTO_ecdsa_public_key_derive (
        &orig->pubs[i].pub,
        label,
        "age commitment derive",
        &newac->pubs[i].pub);
    }
  }
#endif

  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
TALER_age_commitment_derive_from_secret (
  const struct TALER_AgeCommitment *orig,
  const struct TALER_PlanchetMasterSecretP *secret,
  struct TALER_AgeCommitment *newac)
{
  struct GNUNET_HashCode salt;
  enum GNUNET_GenericReturnValue ret;

  ret =      GNUNET_CRYPTO_kdf (&salt,
                                sizeof (salt),
                                "age commitment",
                                strlen ("age commitment"),
                                secret,
                                sizeof(*secret),
                                NULL, 0);
  if (GNUNET_OK != ret)
  {
    GNUNET_break (0);
    return ret;
  }

  return TALER_age_commitment_derive (
    orig,
    &salt,
    newac);
}


enum GNUNET_GenericReturnValue
TALER_age_commitment_proof_derive (
  const struct TALER_AgeCommitmentProof *orig,
  const struct GNUNET_HashCode *salt,
  struct TALER_AgeCommitmentProof *newacp)
{
  enum GNUNET_GenericReturnValue ret;
  GNUNET_assert (NULL != newacp);
  GNUNET_assert (orig->proof.num <=
                 orig->commitment.num);
  GNUNET_assert (((int) orig->commitment.num) ==
                 __builtin_popcount (orig->commitment.mask.bits) - 1);

  ret = TALER_age_commitment_derive (
    &orig->commitment,
    salt,
    &newacp->commitment);
  if (GNUNET_OK != ret)
  {
    GNUNET_break (0);
    return ret;
  }

  newacp->proof.num = orig->proof.num;
  newacp->proof.privs = NULL;
  if (0 != newacp->proof.num)
    newacp->proof.privs = GNUNET_new_array (
      newacp->proof.num,
      struct TALER_AgeCommitmentPrivateKeyP);

#ifndef AGE_RESTRICTION_WITH_ECDSA
  /* Derive the private keys */
  for (size_t i = 0; i < orig->proof.num; i++)
  {
    GNUNET_CRYPTO_edx25519_private_key_derive (
      &orig->proof.privs[i].priv,
      salt,
      sizeof(*salt),
      &newacp->proof.privs[i].priv);
  }
#else
  {
    const char *label = GNUNET_h2s (salt);

    /* Derive the private keys */
    for (size_t i = 0; i < orig->proof.num; i++)
    {
      struct GNUNET_CRYPTO_EcdsaPrivateKey *priv;
      priv = GNUNET_CRYPTO_ecdsa_private_key_derive (
        &orig->proof.privs[i].priv,
        label,
        "age commitment derive");
      newacp->proof.privs[i].priv = *priv;
      GNUNET_free (priv);
    }
  }
#endif

  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
TALER_age_commitment_proof_derive_from_secret (
  const struct TALER_AgeCommitmentProof *orig,
  const struct TALER_PlanchetMasterSecretP *secret,
  struct TALER_AgeCommitmentProof *newacp)
{
  struct GNUNET_HashCode salt;
  enum GNUNET_GenericReturnValue ret;

  ret =      GNUNET_CRYPTO_kdf (&salt,
                                sizeof (salt),
                                "age commitment",
                                strlen ("age commitment"),
                                secret,
                                sizeof(*secret),
                                NULL, 0);
  if (GNUNET_OK != ret)
  {
    GNUNET_break (0);
    return ret;
  }

  return TALER_age_commitment_proof_derive (
    orig,
    &salt,
    newacp);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * Age group mask in network byte order.
 */
struct TALER_AgeMaskNBO
{
  uint32_t bits_nbo;
};

/**
 * Used for attestation of a particular age
 */
struct TALER_AgeAttestationPS
{
  /**
   * Purpose must be #TALER_SIGNATURE_WALLET_AGE_ATTESTATION.
   * (no GNUNET_PACKED here because the struct is already packed)
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Age mask that defines the underlying age groups
   */
  struct TALER_AgeMaskNBO mask GNUNET_PACKED;

  /**
   * The particular age that this attestation is for.
   * We use uint32_t here for alignment.
   */
  uint32_t age GNUNET_PACKED;
};

GNUNET_NETWORK_STRUCT_END


enum GNUNET_GenericReturnValue
TALER_age_commitment_attest (
  const struct TALER_AgeCommitmentProof *cp,
  uint8_t age,
  struct TALER_AgeAttestation *attest)
{
  uint8_t group;

  GNUNET_assert (NULL != attest);
  GNUNET_assert (NULL != cp);

  group = TALER_get_age_group (&cp->commitment.mask,
                               age);

  GNUNET_assert (group < 32);

  if (0 == group)
  {
    /* Age group 0 means: no attestation necessary.
     * We set the signature to zero and communicate success. */
    memset (attest,
            0,
            sizeof(struct TALER_AgeAttestation));
    return GNUNET_OK;
  }

  if (group > cp->proof.num)
    return GNUNET_NO;

  {
    struct TALER_AgeAttestationPS at = {
      .purpose.size = htonl (sizeof(at)),
      .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_AGE_ATTESTATION),
      .mask.bits_nbo = htonl (cp->commitment.mask.bits),
      .age = htonl (age),
    };

#ifndef AGE_RESTRICTION_WITH_ECDSA
  #define sign(a,b,c)  GNUNET_CRYPTO_edx25519_sign (a,b,c)
#else
  #define sign(a,b,c)  GNUNET_CRYPTO_ecdsa_sign (a,b,c)
#endif
    sign (&cp->proof.privs[group - 1].priv,
          &at,
          &attest->signature);
  }
#undef sign

  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
TALER_age_commitment_verify (
  const struct TALER_AgeCommitment *comm,
  uint8_t age,
  const struct TALER_AgeAttestation *attest)
{
  uint8_t group;

  GNUNET_assert (NULL != attest);
  GNUNET_assert (NULL != comm);

  group = TALER_get_age_group (&comm->mask,
                               age);

  GNUNET_assert (group < 32);

  /* Age group 0 means: no attestation necessary. */
  if (0 == group)
    return GNUNET_OK;

  if (group > comm->num)
  {
    GNUNET_break_op (0);
    return GNUNET_NO;
  }

  {
    struct TALER_AgeAttestationPS at = {
      .purpose.size = htonl (sizeof(at)),
      .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_AGE_ATTESTATION),
      .mask.bits_nbo = htonl (comm->mask.bits),
      .age = htonl (age),
    };

#ifndef AGE_RESTRICTION_WITH_ECDSA
  #define verify(a,b,c,d)      GNUNET_CRYPTO_edx25519_verify ((a),(b),(c),(d))
#else
  #define verify(a,b,c,d)      GNUNET_CRYPTO_ecdsa_verify ((a),(b),(c),(d))
#endif
    return verify (TALER_SIGNATURE_WALLET_AGE_ATTESTATION,
                   &at,
                   &attest->signature,
                   &comm->pubs[group - 1].pub);
  }
#undef verify
}


void
TALER_age_commitment_free (
  struct TALER_AgeCommitment *commitment)
{
  if (NULL == commitment)
    return;

  if (NULL != commitment->pubs)
  {
    GNUNET_free (commitment->pubs);
    commitment->pubs = NULL;
  }
}


void
TALER_age_proof_free (
  struct TALER_AgeProof *proof)
{
  if (NULL == proof)
    return;

  if (NULL != proof->privs)
  {
    GNUNET_CRYPTO_zero_keys (
      proof->privs,
      sizeof(*proof->privs) * proof->num);

    GNUNET_free (proof->privs);
    proof->privs = NULL;
  }
}


void
TALER_age_commitment_proof_free (
  struct TALER_AgeCommitmentProof *acp)
{
  if (NULL == acp)
    return;

  if (NULL != acp->proof.privs)
  {
    GNUNET_CRYPTO_zero_keys (
      acp->proof.privs,
      sizeof(*acp->proof.privs) * acp->proof.num);

    GNUNET_free (acp->proof.privs);
    acp->proof.privs = NULL;
  }

  if (NULL != acp->commitment.pubs)
  {
    GNUNET_free (acp->commitment.pubs);
    acp->commitment.pubs = NULL;
  }
}


struct TALER_AgeCommitmentProof *
TALER_age_commitment_proof_duplicate (
  const struct TALER_AgeCommitmentProof *acp)
{
  struct TALER_AgeCommitmentProof *nacp;

  GNUNET_assert (NULL != acp);
  GNUNET_assert (__builtin_popcount (acp->commitment.mask.bits) - 1 ==
                 (int) acp->commitment.num);

  nacp = GNUNET_new (struct TALER_AgeCommitmentProof);

  TALER_age_commitment_proof_deep_copy (nacp, acp);
  return nacp;
}


struct TALER_AgeCommitment *
TALER_age_commitment_duplicate (
  const struct TALER_AgeCommitment *ac)
{
  struct TALER_AgeCommitment *nac;

  GNUNET_assert (NULL != ac);
  GNUNET_assert (__builtin_popcount (ac->mask.bits) - 1 ==
                 (int) ac->num);

  nac = GNUNET_new (struct TALER_AgeCommitment);
  TALER_age_commitment_deep_copy (nac, ac);
  return nac;
}


void
TALER_age_commitment_proof_deep_copy (
  struct TALER_AgeCommitmentProof *nacp,
  const struct TALER_AgeCommitmentProof *acp)
{
  GNUNET_assert (NULL != acp);
  GNUNET_assert (__builtin_popcount (acp->commitment.mask.bits) - 1 ==
                 (int) acp->commitment.num);

  *nacp = *acp;
  nacp->commitment.pubs =
    GNUNET_new_array (acp->commitment.num,
                      struct TALER_AgeCommitmentPublicKeyP);
  nacp->proof.privs =
    GNUNET_new_array (acp->proof.num,
                      struct TALER_AgeCommitmentPrivateKeyP);

  for (size_t i = 0; i < acp->commitment.num; i++)
    nacp->commitment.pubs[i] = acp->commitment.pubs[i];

  for (size_t i = 0; i < acp->proof.num; i++)
    nacp->proof.privs[i] = acp->proof.privs[i];
}


void
TALER_age_commitment_deep_copy (
  struct TALER_AgeCommitment *nac,
  const struct TALER_AgeCommitment *ac)
{
  GNUNET_assert (NULL != ac);
  GNUNET_assert (__builtin_popcount (ac->mask.bits) - 1 ==
                 (int) ac->num);

  *nac = *ac;
  nac->pubs =
    GNUNET_new_array (ac->num,
                      struct TALER_AgeCommitmentPublicKeyP);

  for (size_t i = 0; i < ac->num; i++)
    nac->pubs[i] = ac->pubs[i];

}


enum GNUNET_GenericReturnValue
TALER_JSON_parse_age_groups (const json_t *root,
                             struct TALER_AgeMask *mask)
{
  enum GNUNET_GenericReturnValue ret;
  const char *str;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_string ("age_groups",
                             &str),
    GNUNET_JSON_spec_end ()
  };

  ret = GNUNET_JSON_parse (root,
                           spec,
                           NULL,
                           NULL);
  if (GNUNET_OK == ret)
    TALER_parse_age_group_string (str, mask);

  GNUNET_JSON_parse_free (spec);

  return ret;
}


enum GNUNET_GenericReturnValue
TALER_parse_age_group_string (
  const char *groups,
  struct TALER_AgeMask *mask)
{

  const char *pos = groups;
  unsigned int prev = 0;
  unsigned int val = 0;
  char c;

  /* reset mask */
  mask->bits = 0;

  while (*pos)
  {
    c = *pos++;
    if (':' == c)
    {
      if (prev >= val)
        return GNUNET_SYSERR;

      mask->bits |= 1 << val;
      prev = val;
      val = 0;
      continue;
    }

    if ('0'>c || '9'<c)
      return GNUNET_SYSERR;

    val = 10 * val + c - '0';

    if (0>=val || 32<=val)
      return GNUNET_SYSERR;
  }

  if (32<=val || prev>=val)
    return GNUNET_SYSERR;

  mask->bits |= (1 << val);
  mask->bits |= 1; // mark zeroth group, too

  return GNUNET_OK;
}


const char *
TALER_age_mask_to_string (
  const struct TALER_AgeMask *mask)
{
  static char buf[256] = {0};
  uint32_t bits = mask->bits;
  unsigned int n = 0;
  char *pos = buf;

  memset (buf, 0, sizeof(buf));

  while (bits != 0)
  {
    bits >>= 1;
    n++;
    if (0 == (bits & 1))
    {
      continue;
    }

    if (n > 9)
    {
      *(pos++) = '0' + n / 10;
    }
    *(pos++) = '0' + n % 10;

    if (0 != (bits >> 1))
    {
      *(pos++) = ':';
    }
  }
  return buf;
}


void
TALER_age_restriction_from_secret (
  const struct TALER_PlanchetMasterSecretP *secret,
  const struct TALER_AgeMask *mask,
  const uint8_t max_age,
  struct TALER_AgeCommitmentProof *ncp)
{
  struct GNUNET_HashCode seed_i = {0};
  uint8_t num_pub;
  uint8_t num_priv;

  GNUNET_assert (NULL != mask);
  GNUNET_assert (NULL != secret);
  GNUNET_assert (NULL != ncp);
  GNUNET_assert (mask->bits & 1); /* fist bit must have been set */

  num_pub = __builtin_popcount (mask->bits) - 1;
  num_priv = TALER_get_age_group (mask, max_age);

  GNUNET_assert (31 > num_priv);
  GNUNET_assert (num_priv <= num_pub);

  ncp->commitment.mask.bits = mask->bits;
  ncp->commitment.num = num_pub;
  ncp->proof.num = num_priv;
  ncp->proof.privs = NULL;
  ncp->commitment.pubs = GNUNET_new_array (
    num_pub,
    struct TALER_AgeCommitmentPublicKeyP);
  if (0 < num_priv)
    ncp->proof.privs = GNUNET_new_array (
      num_priv,
      struct TALER_AgeCommitmentPrivateKeyP);

  /* Create as many private keys as allow with max_age and derive the
   * corresponding public keys.  The rest of the needed public keys are created
   * by scalar multiplication with the TALER_age_commitment_base_public_key. */
  for (size_t i = 0; i < num_pub; i++)
  {
    enum GNUNET_GenericReturnValue ret;
    const char *label = i < num_priv ? "age-commitment" : "age-factor";

    ret = GNUNET_CRYPTO_kdf (&seed_i, sizeof(seed_i),
                             secret, sizeof(*secret),
                             label, strlen (label),
                             &i, sizeof(i),
                             NULL, 0);
    GNUNET_assert (GNUNET_OK == ret);

    /* Only generate and save the private keys and public keys for age groups
     * less than num_priv */
    if (i < num_priv)
    {
      struct TALER_AgeCommitmentPrivateKeyP *pkey = &ncp->proof.privs[i];

#ifndef AGE_RESTRICTION_WITH_ECDSA
      GNUNET_CRYPTO_edx25519_key_create_from_seed (&seed_i,
                                                   sizeof(seed_i),
                                                   &pkey->priv);
      GNUNET_CRYPTO_edx25519_key_get_public (&pkey->priv,
                                             &ncp->commitment.pubs[i].pub);
#else
      ecdsa_create_from_seed (&seed_i,
                              sizeof(seed_i),
                              &pkey->priv);
      GNUNET_CRYPTO_ecdsa_key_get_public (&pkey->priv,
                                          &ncp->commitment.pubs[i].pub);
#endif
    }
    else
    {
      /* For all indices larger than num_priv, derive a public key from
       * TALER_age_commitment_base_public_key by scalar multiplication */
#ifndef AGE_RESTRICTION_WITH_ECDSA
      GNUNET_CRYPTO_edx25519_public_key_derive (
        &TALER_age_commitment_base_public_key,
        &seed_i,
        sizeof(seed_i),
        &ncp->commitment.pubs[i].pub);
#else

      GNUNET_CRYPTO_ecdsa_public_key_derive (
        &TALER_age_commitment_base_public_key,
        GNUNET_h2s (&seed_i),
        "age withdraw",
        &ncp->commitment.pubs[i].pub);
#endif
    }
  }
}


enum GNUNET_GenericReturnValue
TALER_parse_coarse_date (
  const char *in,
  const struct TALER_AgeMask *mask,
  uint32_t *out)
{
  struct tm date = {0};
  struct tm limit = {0};
  time_t seconds;

  if (NULL == in)
  {
    /* FIXME[oec]: correct behaviour? */
    *out = 0;
    return GNUNET_OK;
  }

  GNUNET_assert (NULL !=mask);
  GNUNET_assert (NULL !=out);

  if (NULL == strptime (in, "%Y-%m-%d", &date))
  {
    if (NULL == strptime (in, "%Y-%m-00", &date))
      if (NULL == strptime (in, "%Y-00-00", &date))
        return GNUNET_SYSERR;
    /* turns out that the day is off by one in the last two cases */
    date.tm_mday += 1;
  }

  seconds = timegm (&date);
  if (-1 == seconds)
    return GNUNET_SYSERR;

  /* calculate the limit date for the largest age group */
  {
    time_t l = time (NULL);
    localtime_r (&l, &limit);
  }
  limit.tm_year -= TALER_adult_age (mask);
  GNUNET_assert (-1 != timegm (&limit));

  if ((limit.tm_year < date.tm_year)
      || ((limit.tm_year == date.tm_year)
          && (limit.tm_mon < date.tm_mon))
      || ((limit.tm_year == date.tm_year)
          && (limit.tm_mon == date.tm_mon)
          && (limit.tm_mday < date.tm_mday)))
    *out = seconds / 60 / 60 / 24;
  else
    *out = 0;

  return GNUNET_OK;
}


/* end util/age_restriction.c */
