LCOV - code coverage report
Current view: top level - util - contract_choice_parse.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 66.7 % 180 120
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_choice_parse.c
      18              :  * @brief shared logic for contract choice 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              :  * Free contract choice output details in @a output, but not @a output itself
      35              :  *
      36              :  * @param[in,out] output contract output details to clean up
      37              :  */
      38              : static void
      39           21 : contract_choice_output_free (
      40              :   struct TALER_MERCHANT_ContractOutput *output)
      41              : {
      42              : 
      43           21 :   switch (output->type)
      44              :   {
      45            0 :   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
      46            0 :     GNUNET_break (0);
      47            0 :     break;
      48           16 :   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
      49           16 :     break;
      50            5 :   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
      51            5 :     for (unsigned int j = 0;
      52           14 :          j<output->details.donation_receipt.donau_urls_len;
      53            9 :          j++)
      54            9 :       GNUNET_free (output->details.donation_receipt.donau_urls[j]);
      55            5 :     GNUNET_array_grow (output->details.donation_receipt.donau_urls,
      56              :                        output->details.donation_receipt.donau_urls_len,
      57              :                        0);
      58            5 :     break;
      59              : #if FUTURE
      60              :   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_COIN:
      61              :     GNUNET_free (output->details.coin.exchange_url);
      62              :     break;
      63              : #endif
      64              :   }
      65           21 : }
      66              : 
      67              : 
      68              : /**
      69              :  * Parse JSON contract terms choice input.
      70              :  *
      71              :  * @param[in] root JSON object containing choice input
      72              :  * @param[out] input parsed choice input, NULL if @a input is malformed
      73              :  * @param index index of choice input in inputs array
      74              :  * @return #GNUNET_SYSERR if @a input is malformed; #GNUNET_OK otherwise
      75              :  */
      76              : static enum GNUNET_GenericReturnValue
      77            7 : parse_contract_choice_input (
      78              :   json_t *root,
      79              :   struct TALER_MERCHANT_ContractInput *input,
      80              :   size_t index)
      81              : {
      82              :   const char *ename;
      83              :   unsigned int eline;
      84              :   struct GNUNET_JSON_Specification ispec[] = {
      85            7 :     TALER_MERCHANT_json_spec_cit ("type",
      86              :                                   &input->type),
      87            7 :     GNUNET_JSON_spec_end ()
      88              :   };
      89              : 
      90            7 :   if (GNUNET_OK !=
      91            7 :       GNUNET_JSON_parse (root,
      92              :                          ispec,
      93              :                          &ename,
      94              :                          &eline))
      95              :   {
      96            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
      97              :                 "Failed to parse %s at %u: %s\n",
      98              :                 ispec[eline].field,
      99              :                 eline,
     100              :                 ename);
     101            0 :     GNUNET_break_op (0);
     102            0 :     return GNUNET_SYSERR;
     103              :   }
     104              : 
     105            7 :   switch (input->type)
     106              :   {
     107            0 :   case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
     108            0 :     GNUNET_break (0);
     109            0 :     break;
     110            7 :   case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
     111              :     {
     112              :       struct GNUNET_JSON_Specification spec[] = {
     113            7 :         TALER_JSON_spec_slug ("token_family_slug",
     114              :                               &input->details.token.token_family_slug),
     115            7 :         GNUNET_JSON_spec_mark_optional (
     116              :           GNUNET_JSON_spec_uint ("count",
     117              :                                  &input->details.token.count),
     118              :           NULL),
     119            7 :         GNUNET_JSON_spec_end ()
     120              :       };
     121              : 
     122            7 :       if (GNUNET_OK !=
     123            7 :           GNUNET_JSON_parse (root,
     124              :                              spec,
     125              :                              &ename,
     126              :                              &eline))
     127              :       {
     128            0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     129              :                     "Failed to parse %s at %u: %s\n",
     130              :                     spec[eline].field,
     131              :                     eline,
     132              :                     ename);
     133            0 :         GNUNET_break_op (0);
     134            0 :         return GNUNET_SYSERR;
     135              :       }
     136              : 
     137            7 :       return GNUNET_OK;
     138              :     }
     139              :   }
     140              : 
     141            0 :   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     142              :               "Field 'type' invalid in input #%u\n",
     143              :               (unsigned int) index);
     144            0 :   GNUNET_break_op (0);
     145            0 :   return GNUNET_SYSERR;
     146              : }
     147              : 
     148              : 
     149              : /**
     150              :  * Parse JSON contract terms choice output.
     151              :  *
     152              :  * @param[in] root JSON object containing choice output
     153              :  * @param[out] output parsed choice output, NULL if @a output is malformed
     154              :  * @param index index of choice output in outputs array
     155              :  * @return #GNUNET_SYSERR if @a output is malformed; #GNUNET_OK otherwise
     156              :  */
     157              : static enum GNUNET_GenericReturnValue
     158           11 : parse_contract_choice_output (
     159              :   json_t *root,
     160              :   struct TALER_MERCHANT_ContractOutput *output,
     161              :   size_t index)
     162              : {
     163              :   const char *ename;
     164              :   unsigned int eline;
     165              :   struct GNUNET_JSON_Specification ispec[] = {
     166           11 :     TALER_MERCHANT_json_spec_cot ("type",
     167              :                                   &output->type),
     168           11 :     GNUNET_JSON_spec_end ()
     169              :   };
     170              : 
     171           11 :   if (GNUNET_OK !=
     172           11 :       GNUNET_JSON_parse (root,
     173              :                          ispec,
     174              :                          &ename,
     175              :                          &eline))
     176              :   {
     177            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     178              :                 "Failed to parse %s at %u: %s\n",
     179              :                 ispec[eline].field,
     180              :                 eline,
     181              :                 ename);
     182            0 :     GNUNET_break_op (0);
     183            0 :     return GNUNET_SYSERR;
     184              :   }
     185              : 
     186           11 :   switch (output->type)
     187              :   {
     188            0 :   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
     189            0 :     GNUNET_break (0);
     190            0 :     break;
     191            8 :   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
     192              :     {
     193              :       struct GNUNET_JSON_Specification spec[] = {
     194            8 :         TALER_JSON_spec_slug ("token_family_slug",
     195              :                               &output->details.token.token_family_slug),
     196            8 :         GNUNET_JSON_spec_mark_optional (
     197              :           GNUNET_JSON_spec_uint ("count",
     198              :                                  &output->details.token.count),
     199              :           NULL),
     200            8 :         GNUNET_JSON_spec_mark_optional (
     201              :           GNUNET_JSON_spec_timestamp ("valid_at",
     202              :                                       &output->details.token.valid_at),
     203              :           NULL),
     204            8 :         GNUNET_JSON_spec_uint ("key_index",
     205              :                                &output->details.token.key_index),
     206            8 :         GNUNET_JSON_spec_end ()
     207              :       };
     208              : 
     209            8 :       if (GNUNET_OK !=
     210            8 :           GNUNET_JSON_parse (root,
     211              :                              spec,
     212              :                              &ename,
     213              :                              &eline))
     214              :       {
     215            0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     216              :                     "Failed to parse %s at %u: %s\n",
     217              :                     spec[eline].field,
     218              :                     eline,
     219              :                     ename);
     220            0 :         GNUNET_break_op (0);
     221            0 :         return GNUNET_SYSERR;
     222              :       }
     223              : 
     224            8 :       return GNUNET_OK;
     225              :     }
     226            3 :   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
     227              :     {
     228            3 :       const json_t *donau_urls = NULL;
     229              :       struct GNUNET_JSON_Specification spec[] = {
     230            3 :         TALER_JSON_spec_amount_any (
     231              :           "amount",
     232              :           &output->details.donation_receipt.amount),
     233            3 :         GNUNET_JSON_spec_array_const ("donau_urls",
     234              :                                       &donau_urls),
     235            3 :         GNUNET_JSON_spec_end ()
     236              :       };
     237              : 
     238            3 :       if (GNUNET_OK !=
     239            3 :           GNUNET_JSON_parse (root,
     240              :                              spec,
     241              :                              &ename,
     242              :                              &eline))
     243              :       {
     244            0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     245              :                     "Failed to parse %s at %u: %s\n",
     246              :                     spec[eline].field,
     247              :                     eline,
     248              :                     ename);
     249            0 :         GNUNET_break_op (0);
     250            0 :         return GNUNET_SYSERR;
     251              :       }
     252              : 
     253            3 :       GNUNET_array_grow (output->details.donation_receipt.donau_urls,
     254              :                          output->details.donation_receipt.donau_urls_len,
     255              :                          json_array_size (donau_urls));
     256              : 
     257            3 :       for (unsigned int i = 0;
     258           10 :            i < output->details.donation_receipt.donau_urls_len;
     259            7 :            i++)
     260              :       {
     261              :         const json_t *jurl;
     262              : 
     263            7 :         jurl = json_array_get (donau_urls,
     264              :                                i);
     265            7 :         if (! json_is_string (jurl))
     266              :         {
     267            0 :           GNUNET_break_op (0);
     268            0 :           return GNUNET_SYSERR;
     269              :         }
     270            7 :         output->details.donation_receipt.donau_urls[i] =
     271            7 :           GNUNET_strdup (json_string_value (jurl));
     272              :       }
     273              : 
     274            3 :       return GNUNET_OK;
     275              :     }
     276              :   }
     277              : 
     278            0 :   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     279              :               "Field 'type' invalid in output #%u\n",
     280              :               (unsigned int) index);
     281            0 :   GNUNET_break_op (0);
     282            0 :   return GNUNET_SYSERR;
     283              : }
     284              : 
     285              : 
     286              : /**
     287              :  * Parse given JSON object to choices array.
     288              :  *
     289              :  * @param cls closure, pointer to array length
     290              :  * @param root the json array representing the choices
     291              :  * @param[out] ospec where to write the data
     292              :  * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
     293              :  */
     294              : static enum GNUNET_GenericReturnValue
     295           27 : parse_contract_choices (
     296              :   void *cls,
     297              :   json_t *root,
     298              :   struct GNUNET_JSON_Specification *ospec)
     299              : {
     300           27 :   struct TALER_MERCHANT_ContractChoice **choices = ospec->ptr;
     301           27 :   unsigned int *choices_len = cls;
     302              : 
     303           27 :   if (! json_is_array (root))
     304              :   {
     305            0 :     GNUNET_break_op (0);
     306            0 :     return GNUNET_SYSERR;
     307              :   }
     308           27 :   if (0 == json_array_size (root))
     309              :   {
     310              :     /* empty list of choices is not allowed */
     311            0 :     GNUNET_break_op (0);
     312            0 :     return GNUNET_SYSERR;
     313              :   }
     314           27 :   *choices = NULL;
     315           27 :   *choices_len = 0;
     316           27 :   GNUNET_array_grow (*choices,
     317              :                      *choices_len,
     318              :                      json_array_size (root));
     319              : 
     320           65 :   for (unsigned int i = 0; i < *choices_len; i++)
     321              :   {
     322           38 :     struct TALER_MERCHANT_ContractChoice *choice = &(*choices)[i];
     323           38 :     const json_t *jinputs = NULL;
     324           38 :     const json_t *joutputs = NULL;
     325              :     struct GNUNET_JSON_Specification spec[] = {
     326           38 :       TALER_JSON_spec_amount_any ("amount",
     327              :                                   &choice->amount),
     328           38 :       GNUNET_JSON_spec_mark_optional (
     329              :         TALER_JSON_spec_amount_any ("tip",
     330              :                                     &choice->tip),
     331              :         &choice->no_tip),
     332           38 :       GNUNET_JSON_spec_mark_optional (
     333              :         GNUNET_JSON_spec_string_copy ("description",
     334              :                                       &choice->description),
     335              :         NULL),
     336           38 :       GNUNET_JSON_spec_mark_optional (
     337              :         GNUNET_JSON_spec_object_copy ("description_i18n",
     338              :                                       &choice->description_i18n),
     339              :         NULL),
     340           38 :       TALER_JSON_spec_amount_any ("max_fee",
     341              :                                   &choice->max_fee),
     342           38 :       GNUNET_JSON_spec_mark_optional (
     343              :         GNUNET_JSON_spec_array_const ("inputs",
     344              :                                       &jinputs),
     345              :         NULL),
     346           38 :       GNUNET_JSON_spec_mark_optional (
     347              :         GNUNET_JSON_spec_array_const ("outputs",
     348              :                                       &joutputs),
     349              :         NULL),
     350           38 :       GNUNET_JSON_spec_end ()
     351              :     };
     352              :     const char *ename;
     353              :     unsigned int eline;
     354              : 
     355           38 :     if (GNUNET_OK !=
     356           38 :         GNUNET_JSON_parse (json_array_get (root, i),
     357              :                            spec,
     358              :                            &ename,
     359              :                            &eline))
     360              :     {
     361            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     362              :                   "Failed to parse %s at %u: %s\n",
     363              :                   spec[eline].field,
     364              :                   eline,
     365              :                   ename);
     366            0 :       GNUNET_break_op (0);
     367            0 :       return GNUNET_SYSERR;
     368              :     }
     369           42 :     if ( (! choice->no_tip) &&
     370              :          (GNUNET_OK !=
     371            4 :           TALER_amount_cmp_currency (&choice->amount,
     372            4 :                                      &choice->tip)) )
     373              :     {
     374            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     375              :                   "Tip currency does not match amount currency in choice #%u\n",
     376              :                   i);
     377            0 :       GNUNET_break_op (0);
     378            0 :       return GNUNET_SYSERR;
     379              :     }
     380           38 :     if (GNUNET_OK !=
     381           38 :         TALER_amount_cmp_currency (&choice->amount,
     382           38 :                                    &choice->max_fee))
     383              :     {
     384            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     385              :                   "Fee currency does not match amount currency in choice #%u\n",
     386              :                   i);
     387            0 :       GNUNET_break_op (0);
     388            0 :       return GNUNET_SYSERR;
     389              :     }
     390              : 
     391           38 :     if (NULL != jinputs)
     392              :     {
     393              :       const json_t *jinput;
     394              :       size_t idx;
     395              : 
     396           45 :       json_array_foreach ((json_t *) jinputs, idx, jinput)
     397              :       {
     398            7 :         struct TALER_MERCHANT_ContractInput input = {
     399              :           .details.token.count = 1
     400              :         };
     401              : 
     402            7 :         if (GNUNET_OK !=
     403            7 :             parse_contract_choice_input ((json_t *) jinput,
     404              :                                          &input,
     405              :                                          idx))
     406              :         {
     407            0 :           GNUNET_break (0);
     408            0 :           return GNUNET_SYSERR;
     409              :         }
     410            7 :         switch (input.type)
     411              :         {
     412            0 :         case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
     413            0 :           GNUNET_break_op (0);
     414            0 :           return GNUNET_SYSERR;
     415            7 :         case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
     416              :           /* Ignore inputs tokens with 'count' field set to 0 */
     417            7 :           if (0 == input.details.token.count)
     418            0 :             continue;
     419            7 :           break;
     420              :         }
     421            7 :         GNUNET_array_append (choice->inputs,
     422              :                              choice->inputs_len,
     423              :                              input);
     424              :       }
     425              :     }
     426              : 
     427           38 :     if (NULL != joutputs)
     428              :     {
     429              :       const json_t *joutput;
     430              :       size_t idx;
     431           49 :       json_array_foreach ((json_t *) joutputs, idx, joutput)
     432              :       {
     433           11 :         struct TALER_MERCHANT_ContractOutput output = {
     434              :           .details.token.count = 1
     435              :         };
     436              : 
     437           11 :         if (GNUNET_OK !=
     438           11 :             parse_contract_choice_output ((json_t *) joutput,
     439              :                                           &output,
     440              :                                           idx))
     441              :         {
     442            0 :           GNUNET_break (0);
     443            0 :           contract_choice_output_free (&output);
     444            0 :           return GNUNET_SYSERR;
     445              :         }
     446           11 :         switch (output.type)
     447              :         {
     448            0 :         case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
     449            0 :           GNUNET_break_op (0);
     450            0 :           contract_choice_output_free (&output);
     451            0 :           return GNUNET_SYSERR;
     452            8 :         case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
     453              :           /* Ignore output tokens with 'count' field set to 0 */
     454            8 :           if (0 == output.details.token.count)
     455              :           {
     456            0 :             contract_choice_output_free (&output);
     457            0 :             continue;
     458              :           }
     459            8 :           break;
     460            3 :         case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
     461            3 :           break;
     462              :         }
     463           11 :         GNUNET_array_append (choice->outputs,
     464              :                              choice->outputs_len,
     465              :                              output);
     466              :       }
     467              :     }
     468              :   }
     469              : 
     470           27 :   return GNUNET_OK;
     471              : }
     472              : 
     473              : 
     474              : struct GNUNET_JSON_Specification
     475           27 : TALER_MERCHANT_spec_contract_choices (
     476              :   const char *name,
     477              :   struct TALER_MERCHANT_ContractChoice **choices,
     478              :   unsigned int *choices_len)
     479              : {
     480           27 :   struct GNUNET_JSON_Specification ret = {
     481              :     .cls = (void *) choices_len,
     482              :     .parser = &parse_contract_choices,
     483              :     .field = name,
     484              :     .ptr = choices,
     485              :   };
     486              : 
     487           27 :   return ret;
     488              : }
     489              : 
     490              : 
     491              : void
     492           69 : TALER_MERCHANT_contract_choice_free (
     493              :   struct TALER_MERCHANT_ContractChoice *choice)
     494              : {
     495           90 :   for (unsigned int i = 0; i < choice->outputs_len; i++)
     496              :   {
     497           21 :     contract_choice_output_free (&choice->outputs[i]);
     498              :   }
     499           69 :   GNUNET_free (choice->description);
     500           69 :   if (NULL != choice->description_i18n)
     501              :   {
     502            2 :     json_decref (choice->description_i18n);
     503            2 :     choice->description_i18n = NULL;
     504              :   }
     505           69 :   GNUNET_free (choice->inputs);
     506           69 :   GNUNET_free (choice->outputs);
     507           69 : }
        

Generated by: LCOV version 2.0-1