LCOV - code coverage report
Current view: top level - util - contract_parse.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 74.9 % 183 137
Test Date: 2026-09-11 17:45:24 Functions: 100.0 % 6 6

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   (C) 2024, 2025, 2026 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU Lesser General Public License as published by the Free Software
       7              :   Foundation; either version 3, or (at your option) any later version.
       8              : 
       9              :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10              :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11              :   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU General Public License along with
      14              :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15              : */
      16              : /**
      17              :  * @file src/util/contract_parse.c
      18              :  * @brief shared logic for contract terms parsing
      19              :  * @author Iván Ávalos
      20              :  * @author Christian Grothoff
      21              :  */
      22              : #include "platform.h"
      23              : #include <gnunet/gnunet_common.h>
      24              : #include <gnunet/gnunet_json_lib.h>
      25              : #include <jansson.h>
      26              : #include <stdbool.h>
      27              : #include <stdint.h>
      28              : #include <taler/taler_json_lib.h>
      29              : #include <taler/taler_util.h>
      30              : #include "taler/taler_merchant_util.h"
      31              : 
      32              : 
      33              : /**
      34              :  * Parse v0-specific fields of @a input JSON into @a contract.
      35              :  *
      36              :  * @param[in] input the JSON contract terms
      37              :  * @param[out] contract where to write the data
      38              :  * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
      39              :  */
      40              : static enum GNUNET_GenericReturnValue
      41          178 : parse_contract_v0 (
      42              :   json_t *input,
      43              :   struct TALER_MERCHANT_ProtoContract *contract)
      44              : {
      45              :   struct GNUNET_JSON_Specification espec[] = {
      46          178 :     TALER_JSON_spec_amount_any ("amount",
      47              :                                 &contract->details.v0.brutto),
      48          178 :     GNUNET_JSON_spec_mark_optional (
      49              :       TALER_JSON_spec_amount_any ("tip",
      50              :                                   &contract->details.v0.tip),
      51              :       &contract->details.v0.no_tip),
      52          178 :     TALER_JSON_spec_amount_any ("max_fee",
      53              :                                 &contract->details.v0.max_fee),
      54          178 :     GNUNET_JSON_spec_end ()
      55              :   };
      56              :   enum GNUNET_GenericReturnValue res;
      57              :   const char *ename;
      58              :   unsigned int eline;
      59              : 
      60          178 :   res = GNUNET_JSON_parse (input,
      61              :                            espec,
      62              :                            &ename,
      63              :                            &eline);
      64          178 :   if (GNUNET_OK != res)
      65              :   {
      66            0 :     GNUNET_break (0);
      67            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
      68              :                 "Failed to parse contract v0 at field %s\n",
      69              :                 ename);
      70            0 :     return GNUNET_SYSERR;
      71              :   }
      72              : 
      73          178 :   if (GNUNET_OK !=
      74          178 :       TALER_amount_cmp_currency (&contract->details.v0.max_fee,
      75          178 :                                  &contract->details.v0.brutto))
      76              :   {
      77            0 :     GNUNET_break (0);
      78            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
      79              :                 "'max_fee' does not match currency of contract price");
      80            0 :     return GNUNET_SYSERR;
      81              :   }
      82          178 :   if ( (! contract->details.v0.no_tip) &&
      83              :        (GNUNET_OK !=
      84            0 :         TALER_amount_cmp_currency (&contract->details.v0.tip,
      85            0 :                                    &contract->details.v0.brutto)) )
      86              :   {
      87            0 :     GNUNET_break (0);
      88            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
      89              :                 "'tip' does not match currency of contract price");
      90            0 :     return GNUNET_SYSERR;
      91              :   }
      92          178 :   if (NULL != contract->base->amount_external)
      93              :   {
      94            7 :     if (! TALER_MERCHANT_amount_external_valid (
      95            7 :           contract->base->amount_external))
      96              :     {
      97            0 :       GNUNET_break_op (0);
      98            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
      99              :                   "'amount_external' is not a valid set of external payments");
     100            0 :       return GNUNET_SYSERR;
     101              :     }
     102            7 :     if (! TALER_MERCHANT_amount_external_currency_valid (
     103            7 :           contract->base->amount_external,
     104            7 :           &contract->details.v0.brutto))
     105              :     {
     106            0 :       GNUNET_break_op (0);
     107            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     108              :                   "'amount_external' does not match currency of contract price");
     109            0 :       return GNUNET_SYSERR;
     110              :     }
     111            7 :     if (! TALER_MERCHANT_amount_external_total_valid (
     112            7 :           contract->base->amount_external,
     113            7 :           &contract->details.v0.brutto))
     114              :     {
     115            0 :       GNUNET_break_op (0);
     116            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     117              :                   "total of 'amount_external' and contract price is too large");
     118            0 :       return GNUNET_SYSERR;
     119              :     }
     120              :   }
     121              : 
     122          178 :   return res;
     123              : }
     124              : 
     125              : 
     126              : /**
     127              :  * Parse v1-specific fields of @a input JSON into @a pc.
     128              :  *
     129              :  * @param[in] input the JSON contract terms
     130              :  * @param[out] pc where to write the data
     131              :  * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
     132              :  */
     133              : static enum GNUNET_GenericReturnValue
     134           27 : parse_contract_v1 (
     135              :   json_t *input,
     136              :   struct TALER_MERCHANT_ProtoContract *pc)
     137              : {
     138              :   struct GNUNET_JSON_Specification espec[] = {
     139           27 :     TALER_MERCHANT_spec_contract_choices (
     140              :       "choices",
     141              :       &pc->details.v1.choices,
     142              :       &pc->details.v1.choices_len),
     143           27 :     TALER_MERCHANT_spec_token_families (
     144              :       "token_families",
     145              :       &pc->details.v1.token_authorities,
     146              :       &pc->details.v1.token_authorities_len),
     147           27 :     GNUNET_JSON_spec_end ()
     148              :   };
     149              : 
     150              :   enum GNUNET_GenericReturnValue res;
     151              :   const char *ename;
     152              :   unsigned int eline;
     153              : 
     154           27 :   res = GNUNET_JSON_parse (input,
     155              :                            espec,
     156              :                            &ename,
     157              :                            &eline);
     158           27 :   if (GNUNET_OK != res)
     159              :   {
     160            0 :     GNUNET_break (0);
     161            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     162              :                 "Failed to parse contract v1 at field %s\n",
     163              :                 ename);
     164            0 :     return GNUNET_SYSERR;
     165              :   }
     166           27 :   if (NULL != pc->base->amount_external)
     167              :   {
     168            2 :     if (! TALER_MERCHANT_amount_external_valid (pc->base->amount_external))
     169              :     {
     170            0 :       GNUNET_break_op (0);
     171            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     172              :                   "'amount_external' is not a valid set of external payments\n");
     173            0 :       return GNUNET_SYSERR;
     174              :     }
     175            3 :     for (unsigned int i = 0; i<pc->details.v1.choices_len; i++)
     176              :     {
     177            2 :       if (! TALER_MERCHANT_amount_external_currency_valid (
     178            2 :             pc->base->amount_external,
     179            2 :             &pc->details.v1.choices[i].amount))
     180              :       {
     181            1 :         GNUNET_break_op (0);
     182            1 :         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     183              :                     "'amount_external' does not match currency of choice #%u\n",
     184              :                     i);
     185            1 :         return GNUNET_SYSERR;
     186              :       }
     187            1 :       if (! TALER_MERCHANT_amount_external_total_valid (
     188            1 :             pc->base->amount_external,
     189            1 :             &pc->details.v1.choices[i].amount))
     190              :       {
     191            0 :         GNUNET_break_op (0);
     192            0 :         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     193              :                     "total of 'amount_external' and choice #%u is too large\n",
     194              :                     i);
     195            0 :         return GNUNET_SYSERR;
     196              :       }
     197              :     }
     198              :   }
     199              : 
     200           26 :   return res;
     201              : }
     202              : 
     203              : 
     204              : struct TALER_MERCHANT_ProtoContract *
     205          205 : TALER_MERCHANT_proto_contract_parse (
     206              :   json_t *input)
     207              : {
     208              :   struct TALER_MERCHANT_ContractBaseTerms *base;
     209              :   struct TALER_MERCHANT_ProtoContract *pc;
     210              :   enum GNUNET_GenericReturnValue res;
     211              : 
     212          205 :   base = TALER_MERCHANT_base_terms_parse (input);
     213          205 :   if (NULL == base)
     214              :   {
     215            0 :     GNUNET_break_op (0);
     216            0 :     return NULL;
     217              :   }
     218          205 :   pc = GNUNET_new (struct TALER_MERCHANT_ProtoContract);
     219          205 :   pc->base = base;
     220              :   {
     221          205 :     const json_t *products = NULL;
     222              :     struct GNUNET_JSON_Specification espec[] = {
     223          205 :       TALER_JSON_spec_slug_copy ("order_id",
     224              :                                  &pc->order_id),
     225          205 :       GNUNET_JSON_spec_mark_optional (
     226              :         GNUNET_JSON_spec_array_const ("products",
     227              :                                       &products),
     228              :         NULL),
     229          205 :       GNUNET_JSON_spec_timestamp ("timestamp",
     230              :                                   &pc->timestamp),
     231          205 :       GNUNET_JSON_spec_timestamp ("refund_deadline",
     232              :                                   &pc->refund_deadline),
     233          205 :       GNUNET_JSON_spec_timestamp ("pay_deadline",
     234              :                                   &pc->pay_deadline),
     235          205 :       GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
     236              :                                   &pc->wire_deadline),
     237          205 :       GNUNET_JSON_spec_fixed_auto ("merchant_pub",
     238              :                                    &pc->merchant_pub),
     239          205 :       GNUNET_JSON_spec_fixed_auto ("h_wire",
     240              :                                    &pc->h_wire),
     241          205 :       TALER_JSON_spec_web_url_copy ("merchant_base_url",
     242              :                                     &pc->merchant_base_url),
     243          205 :       TALER_MERCHANT_spec_merchant_details ("merchant",
     244              :                                             &pc->merchant),
     245          205 :       GNUNET_JSON_spec_string_copy ("wire_method",
     246              :                                     &pc->wire_method),
     247          205 :       GNUNET_JSON_spec_array_copy ("exchanges",
     248              :                                    &pc->exchanges),
     249              : 
     250          205 :       GNUNET_JSON_spec_end ()
     251              :     };
     252              :     const char *ename;
     253              :     unsigned int eline;
     254              : 
     255          205 :     res = GNUNET_JSON_parse (input,
     256              :                              espec,
     257              :                              &ename,
     258              :                              &eline);
     259          205 :     if (GNUNET_OK != res)
     260              :     {
     261            0 :       GNUNET_break_op (0);
     262            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     263              :                   "Failed to parse proto contract at field %s\n",
     264              :                   ename);
     265            0 :       goto cleanup;
     266              :     }
     267          205 :     if (NULL != products)
     268              :     {
     269          205 :       pc->products_len = json_array_size (products);
     270          205 :       if (0 != pc->products_len)
     271              :       {
     272              :         size_t i;
     273              :         json_t *p;
     274              : 
     275           17 :         pc->products = GNUNET_new_array (
     276              :           pc->products_len,
     277              :           struct TALER_MERCHANT_ProductSold);
     278           36 :         json_array_foreach (products, i, p)
     279              :         {
     280           19 :           if (GNUNET_OK !=
     281           19 :               TALER_MERCHANT_parse_product_sold (p,
     282           19 :                                                  &pc->products[i],
     283              :                                                  false))
     284              :           {
     285            0 :             GNUNET_break (0);
     286            0 :             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     287              :                         "Failed to parse product at offset %u\n",
     288              :                         (unsigned int) i);
     289            0 :             goto cleanup;
     290              :           }
     291              :         }
     292              :       }
     293              :     }
     294              :   }
     295          205 :   switch (base->version)
     296              :   {
     297          178 :   case TALER_MERCHANT_CONTRACT_VERSION_0:
     298          178 :     if (GNUNET_OK ==
     299          178 :         parse_contract_v0 (input,
     300              :                            pc))
     301          178 :       return pc;
     302            0 :     GNUNET_break_op (0);
     303            0 :     break;
     304           27 :   case TALER_MERCHANT_CONTRACT_VERSION_1:
     305           27 :     if (GNUNET_OK ==
     306           27 :         parse_contract_v1 (input,
     307              :                            pc))
     308           26 :       return pc;
     309            1 :     GNUNET_break_op (0);
     310            1 :     break;
     311              :   }
     312            1 : cleanup:
     313            1 :   TALER_MERCHANT_proto_contract_free (pc);
     314            1 :   return NULL;
     315              : }
     316              : 
     317              : 
     318              : /**
     319              :  * Free the proto-contract at @a pc
     320              :  *
     321              :  * @param[in] pc proto-contract to free
     322              :  */
     323              : void
     324          204 : TALER_MERCHANT_proto_contract_free (
     325              :   struct TALER_MERCHANT_ProtoContract *pc)
     326              : {
     327          204 :   if (NULL != pc->products)
     328              :   {
     329           36 :     for (size_t i = 0; i<pc->products_len; i++)
     330           19 :       TALER_MERCHANT_product_sold_free (&pc->products[i]);
     331           17 :     GNUNET_free (pc->products);
     332              :   }
     333          204 :   if (NULL != pc->base)
     334              :   {
     335          204 :     switch (pc->base->version)
     336              :     {
     337          177 :     case TALER_MERCHANT_CONTRACT_VERSION_0:
     338          177 :       break;
     339           27 :     case TALER_MERCHANT_CONTRACT_VERSION_1:
     340           27 :       for (unsigned int i = 0;
     341           65 :            i < pc->details.v1.choices_len;
     342           38 :            i++)
     343           38 :         TALER_MERCHANT_contract_choice_free (
     344           38 :           &pc->details.v1.choices[i]);
     345           27 :       GNUNET_free (pc->details.v1.choices);
     346           27 :       for (unsigned int i = 0;
     347           38 :            i < pc->details.v1.token_authorities_len;
     348           11 :            i++)
     349           11 :         TALER_MERCHANT_contract_token_family_free (
     350           11 :           &pc->details.v1.token_authorities[i]);
     351           27 :       GNUNET_free (pc->details.v1.token_authorities);
     352           27 :       break;
     353              :     }
     354          204 :     TALER_MERCHANT_base_terms_free (pc->base);
     355          204 :     pc->base = NULL;
     356              :   }
     357          204 :   GNUNET_free (pc->wire_method);
     358          204 :   GNUNET_free (pc->merchant_base_url);
     359          204 :   TALER_MERCHANT_metadata_free (&pc->merchant);
     360          204 :   if (NULL != pc->exchanges)
     361              :   {
     362          204 :     json_decref (pc->exchanges);
     363          204 :     pc->exchanges = NULL;
     364              :   }
     365          204 :   GNUNET_free (pc->order_id);
     366          204 :   GNUNET_free (pc);
     367          204 : }
     368              : 
     369              : 
     370              : struct TALER_MERCHANT_Contract *
     371          188 : TALER_MERCHANT_contract_parse (json_t *input)
     372              : {
     373              :   struct TALER_MERCHANT_Contract *contract
     374          188 :     = GNUNET_new (struct TALER_MERCHANT_Contract);
     375              :   struct GNUNET_JSON_Specification espec[] = {
     376          188 :     GNUNET_JSON_spec_string_copy ("nonce",
     377              :                                   &contract->nonce),
     378          188 :     GNUNET_JSON_spec_end ()
     379              :   };
     380              :   enum GNUNET_GenericReturnValue res;
     381              :   const char *ename;
     382              :   unsigned int eline;
     383              : 
     384          188 :   GNUNET_assert (NULL != input);
     385          188 :   contract->pc = TALER_MERCHANT_proto_contract_parse (input);
     386          188 :   if (NULL == contract->pc)
     387              :   {
     388            1 :     GNUNET_break_op (0);
     389            1 :     GNUNET_free (contract);
     390            1 :     return NULL;
     391              :   }
     392          187 :   res = GNUNET_JSON_parse (input,
     393              :                            espec,
     394              :                            &ename,
     395              :                            &eline);
     396          187 :   if (GNUNET_OK != res)
     397              :   {
     398            0 :     GNUNET_break_op (0);
     399            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     400              :                 "Failed to parse contract at field %s\n",
     401              :                 ename);
     402            0 :     goto cleanup;
     403              :   }
     404          187 :   return contract;
     405              : 
     406            0 : cleanup:
     407            0 :   TALER_MERCHANT_contract_free (contract);
     408            0 :   return NULL;
     409              : }
     410              : 
     411              : 
     412              : void
     413          187 : TALER_MERCHANT_contract_free (
     414              :   struct TALER_MERCHANT_Contract *contract)
     415              : {
     416          187 :   if (NULL == contract)
     417            0 :     return;
     418          187 :   if (NULL != contract->pc)
     419              :   {
     420          187 :     TALER_MERCHANT_proto_contract_free (contract->pc);
     421          187 :     contract->pc = NULL;
     422              :   }
     423          187 :   GNUNET_free (contract->nonce);
     424          187 :   GNUNET_free (contract);
     425              : }
        

Generated by: LCOV version 2.0-1