/*
This file is part of TALER
(C) 2024 Taler Systems SA
TALER 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.
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
*/
/**
* @file taler-merchant-httpd_contract.c
* @brief shared logic for contract terms handling
* @author Christian Blättler
*/
#include "platform.h"
#include
#include
#include
#include "taler-merchant-httpd_contract.h"
enum TALER_MerchantContractInputType
TMH_contract_input_type_from_string (const char *str)
{
/* For now, only 'token' is the only supported option. */
if (0 == strcmp("token", str))
{
return TALER_MCIT_TOKEN;
}
return TALER_MCIT_INVALID;
}
enum TALER_MerchantContractOutputType
TMH_contract_output_type_from_string (const char *str)
{
/* For now, only 'token' is the only supported option. */
if (0 == strcmp("token", str))
{
return TALER_MCOT_TOKEN;
}
return TALER_MCOT_INVALID;
}
const char *
TMH_string_from_contract_input_type (enum TALER_MerchantContractInputType t)
{
switch (t) {
case TALER_MCIT_TOKEN:
return "token";
case TALER_MCIT_COIN:
return "coin";
default:
return "invalid";
}
}
const char *
TMH_string_from_contract_output_type (enum TALER_MerchantContractOutputType t)
{
switch (t) {
case TALER_MCOT_TOKEN:
return "token";
case TALER_MCOT_COIN:
return "coin";
case TALER_MCOT_TAX_RECEIPT:
return "tax_receipt";
default:
return "invalid";
}
}
/**
* Parse given JSON object to choices array.
*
* @param cls closure, pointer to array length
* @param root the json array representing the choices
* @param[out] spec where to write the data
* @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
*/
static enum GNUNET_GenericReturnValue
parse_choices (void *cls,
json_t *root,
struct GNUNET_JSON_Specification *spec)
{
struct TALER_MerchantContractChoice **choices = spec->ptr;
unsigned int *choices_len = cls;
if (!json_is_array (root))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
GNUNET_array_grow (*choices,
*choices_len,
json_array_size (root));
for (unsigned int i = 0; i<*choices_len; i++)
{
const json_t *jinputs;
const json_t *joutputs;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_array_const ("inputs",
&jinputs),
GNUNET_JSON_spec_array_const ("outputs",
&joutputs),
GNUNET_JSON_spec_end ()
};
const char *error_name;
unsigned int error_line;
struct TALER_MerchantContractChoice *choice = &(*choices)[i];
if (GNUNET_OK !=
GNUNET_JSON_parse (json_array_get (root, i),
spec,
&error_name,
&error_line))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to parse %s at %u: %s\n",
spec[error_line].field,
error_line,
error_name);
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
{
const json_t *jinput;
size_t idx;
json_array_foreach ((json_t *) jinputs, idx, jinput)
{
struct TALER_MerchantContractInput input = {.details.token.count = 1};
const char *kind;
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_string ("kind",
&kind),
GNUNET_JSON_spec_string ("token_family_slug",
&input.details.token.token_family_slug),
GNUNET_JSON_spec_timestamp ("valid_after",
&input.details.token.valid_after),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_uint32 ("count",
&input.details.token.count),
NULL),
GNUNET_JSON_spec_end()
};
const char *ierror_name;
unsigned int ierror_line;
if (GNUNET_OK !=
GNUNET_JSON_parse (jinput,
ispec,
&ierror_name,
&ierror_line))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to parse %s at %u: %s\n",
spec[ierror_line].field,
ierror_line,
ierror_name);
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
input.type = TMH_contract_input_type_from_string (kind);
if (TALER_MCIT_INVALID == input.type)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Field 'kind' invalid in input #%u\n",
(unsigned int) idx);
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (0 == input.details.token.count)
{
/* Ignore inputs with 'number' field set to 0 */
continue;
}
GNUNET_array_append (choice->inputs,
choice->inputs_len,
input);
}
}
{
const json_t *joutput;
size_t idx;
json_array_foreach ((json_t *) joutputs, idx, joutput)
{
struct TALER_MerchantContractOutput output = {.details.token.count = 1};
const char *kind;
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_string ("kind",
&kind),
GNUNET_JSON_spec_string ("token_family_slug",
&output.details.token.token_family_slug),
GNUNET_JSON_spec_timestamp ("valid_after",
&output.details.token.valid_after),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_uint32 ("count",
&output.details.token.count),
NULL),
GNUNET_JSON_spec_end()
};
const char *ierror_name;
unsigned int ierror_line;
if (GNUNET_OK !=
GNUNET_JSON_parse (joutput,
ispec,
&ierror_name,
&ierror_line))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to parse %s at %u: %s\n",
spec[ierror_line].field,
ierror_line,
ierror_name);
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
output.type = TMH_contract_output_type_from_string (kind);
if (TALER_MCOT_INVALID == output.type)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Field 'kind' invalid in output #%u\n",
(unsigned int) idx);
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (0 == output.details.token.count)
{
/* Ignore outputs with 'number' field set to 0 */
continue;
}
GNUNET_array_append (choice->outputs,
choice->outputs_len,
output);
}
}
}
return GNUNET_OK;
}
struct GNUNET_JSON_Specification
TALER_JSON_spec_choices (const char *name,
struct TALER_MerchantContractChoice **choices,
unsigned int *choices_len)
{
struct GNUNET_JSON_Specification ret = {
.cls = (void *) choices_len,
.parser = &parse_choices,
.field = name,
.ptr = choices,
};
return ret;
}
/**
* Parse given JSON object to token families array.
*
* @param cls closure, pointer to array length
* @param root the json object representing the token families. The keys are
* the token family slugs.
* @param[out] spec where to write the data
* @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
*/
static enum GNUNET_GenericReturnValue
parse_token_families (void *cls,
json_t *root,
struct GNUNET_JSON_Specification *spec)
{
struct TALER_MerchantContractTokenFamily **families = spec->ptr;
unsigned int *families_len = cls;
json_t *jfamily;
const char *slug;
if (!json_is_object (root))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
json_object_foreach(root, slug, jfamily)
{
const json_t *keys;
struct TALER_MerchantContractTokenFamily family = {
.slug = slug
};
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_array_const ("keys",
&keys),
GNUNET_JSON_spec_bool ("critical",
&family.critical),
GNUNET_JSON_spec_end ()
/* TODO: Figure out if these fields should be 'const' */
// GNUNET_JSON_spec_string ("description",
// &family.description),
// GNUNET_JSON_spec_object_const ("description_i18n",
// &family.description_i18n),
};
const char *error_name;
unsigned int error_line;
if (GNUNET_OK !=
GNUNET_JSON_parse (jfamily,
spec,
&error_name,
&error_line))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to parse %s at %u: %s\n",
spec[error_line].field,
error_line,
error_name);
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
GNUNET_array_grow (family.keys,
family.keys_len,
json_array_size (keys));
for (unsigned int i = 0; ipub.public_key = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey);
struct GNUNET_JSON_Specification key_spec[] = {
GNUNET_JSON_spec_fixed_auto ("h_pub",
&key->pub.public_key->pub_key_hash),
GNUNET_JSON_spec_rsa_public_key ("rsa_pub",
&key->pub.public_key->details.rsa_public_key),
// GNUNET_JSON_spec_fixed_auto ("cs_pub",
// &key.pub.public_key->details.cs_public_key)),
GNUNET_JSON_spec_int64 ("cipher",
&cipher),
GNUNET_JSON_spec_timestamp ("valid_after",
&key->valid_after),
GNUNET_JSON_spec_timestamp ("valid_before",
&key->valid_before),
GNUNET_JSON_spec_end()
};
const char *ierror_name;
unsigned int ierror_line;
if (GNUNET_OK !=
GNUNET_JSON_parse (json_array_get (keys, i),
key_spec,
&ierror_name,
&ierror_line))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to parse %s at %u: %s\n",
key_spec[ierror_line].field,
ierror_line,
ierror_name);
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
switch (cipher) {
case GNUNET_CRYPTO_BSA_RSA:
key->pub.public_key->cipher = GNUNET_CRYPTO_BSA_RSA;
break;
case GNUNET_CRYPTO_BSA_CS:
key->pub.public_key->cipher = GNUNET_CRYPTO_BSA_CS;
break;
case GNUNET_CRYPTO_BSA_INVALID:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Field 'cipher' invalid in key #%u\n",
i);
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
}
GNUNET_array_append (*families, *families_len, family);
}
return GNUNET_OK;
}
struct GNUNET_JSON_Specification
TALER_JSON_spec_token_families (const char *name,
struct TALER_MerchantContractTokenFamily **families,
unsigned int *families_len)
{
struct GNUNET_JSON_Specification ret = {
.cls = (void *) families_len,
.parser = &parse_token_families,
.field = name,
.ptr = families,
};
return ret;
}
enum GNUNET_GenericReturnValue
TMH_find_token_family_key (const char *slug,
struct GNUNET_TIME_Timestamp valid_after,
struct TALER_MerchantContractTokenFamily *families,
unsigned int families_len,
struct TALER_MerchantContractTokenFamily *family,
struct TALER_MerchantContractTokenFamilyKey *key)
{
for (unsigned int i = 0; i < families_len; i++)
{
if (0 != strcmp (families[i].slug, slug))
{
continue;
}
if (NULL != family)
{
*family = families[i];
}
for (unsigned int k = 0; k < family->keys_len; k++)
{
if (GNUNET_TIME_timestamp_cmp(family->keys[k].valid_after,
==,
valid_after))
{
if (NULL != key)
{
*key = family->keys[k];
}
return GNUNET_OK;
}
}
/* matching family found, but no key. */
return GNUNET_NO;
}
/* no matching family found */
return GNUNET_NO;
}