contract_choice_parse.c (14630B)
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 contract_choice_output_free ( 40 struct TALER_MERCHANT_ContractOutput *output) 41 { 42 43 switch (output->type) 44 { 45 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: 46 GNUNET_break (0); 47 break; 48 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: 49 break; 50 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: 51 for (unsigned int j = 0; 52 j<output->details.donation_receipt.donau_urls_len; 53 j++) 54 GNUNET_free (output->details.donation_receipt.donau_urls[j]); 55 GNUNET_array_grow (output->details.donation_receipt.donau_urls, 56 output->details.donation_receipt.donau_urls_len, 57 0); 58 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 } 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 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 TALER_MERCHANT_json_spec_cit ("type", 86 &input->type), 87 GNUNET_JSON_spec_end () 88 }; 89 90 if (GNUNET_OK != 91 GNUNET_JSON_parse (root, 92 ispec, 93 &ename, 94 &eline)) 95 { 96 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 97 "Failed to parse %s at %u: %s\n", 98 ispec[eline].field, 99 eline, 100 ename); 101 GNUNET_break_op (0); 102 return GNUNET_SYSERR; 103 } 104 105 switch (input->type) 106 { 107 case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: 108 GNUNET_break (0); 109 break; 110 case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: 111 { 112 struct GNUNET_JSON_Specification spec[] = { 113 GNUNET_JSON_spec_string ("token_family_slug", 114 &input->details.token.token_family_slug), 115 GNUNET_JSON_spec_mark_optional ( 116 GNUNET_JSON_spec_uint ("count", 117 &input->details.token.count), 118 NULL), 119 GNUNET_JSON_spec_end () 120 }; 121 122 if (GNUNET_OK != 123 GNUNET_JSON_parse (root, 124 spec, 125 &ename, 126 &eline)) 127 { 128 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 129 "Failed to parse %s at %u: %s\n", 130 spec[eline].field, 131 eline, 132 ename); 133 GNUNET_break_op (0); 134 return GNUNET_SYSERR; 135 } 136 137 return GNUNET_OK; 138 } 139 } 140 141 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 142 "Field 'type' invalid in input #%u\n", 143 (unsigned int) index); 144 GNUNET_break_op (0); 145 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 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 TALER_MERCHANT_json_spec_cot ("type", 167 &output->type), 168 GNUNET_JSON_spec_end () 169 }; 170 171 if (GNUNET_OK != 172 GNUNET_JSON_parse (root, 173 ispec, 174 &ename, 175 &eline)) 176 { 177 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 178 "Failed to parse %s at %u: %s\n", 179 ispec[eline].field, 180 eline, 181 ename); 182 GNUNET_break_op (0); 183 return GNUNET_SYSERR; 184 } 185 186 switch (output->type) 187 { 188 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: 189 GNUNET_break (0); 190 break; 191 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: 192 { 193 struct GNUNET_JSON_Specification spec[] = { 194 GNUNET_JSON_spec_string ("token_family_slug", 195 &output->details.token.token_family_slug), 196 GNUNET_JSON_spec_mark_optional ( 197 GNUNET_JSON_spec_uint ("count", 198 &output->details.token.count), 199 NULL), 200 GNUNET_JSON_spec_mark_optional ( 201 GNUNET_JSON_spec_timestamp ("valid_at", 202 &output->details.token.valid_at), 203 NULL), 204 GNUNET_JSON_spec_uint ("key_index", 205 &output->details.token.key_index), 206 GNUNET_JSON_spec_end () 207 }; 208 209 if (GNUNET_OK != 210 GNUNET_JSON_parse (root, 211 spec, 212 &ename, 213 &eline)) 214 { 215 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 216 "Failed to parse %s at %u: %s\n", 217 spec[eline].field, 218 eline, 219 ename); 220 GNUNET_break_op (0); 221 return GNUNET_SYSERR; 222 } 223 224 return GNUNET_OK; 225 } 226 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: 227 { 228 const json_t *donau_urls = NULL; 229 struct GNUNET_JSON_Specification spec[] = { 230 TALER_JSON_spec_amount_any ( 231 "amount", 232 &output->details.donation_receipt.amount), 233 GNUNET_JSON_spec_array_const ("donau_urls", 234 &donau_urls), 235 GNUNET_JSON_spec_end () 236 }; 237 238 if (GNUNET_OK != 239 GNUNET_JSON_parse (root, 240 spec, 241 &ename, 242 &eline)) 243 { 244 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 245 "Failed to parse %s at %u: %s\n", 246 spec[eline].field, 247 eline, 248 ename); 249 GNUNET_break_op (0); 250 return GNUNET_SYSERR; 251 } 252 253 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 for (unsigned int i = 0; 258 i < output->details.donation_receipt.donau_urls_len; 259 i++) 260 { 261 const json_t *jurl; 262 263 jurl = json_array_get (donau_urls, 264 i); 265 if (! json_is_string (jurl)) 266 { 267 GNUNET_break_op (0); 268 return GNUNET_SYSERR; 269 } 270 output->details.donation_receipt.donau_urls[i] = 271 GNUNET_strdup (json_string_value (jurl)); 272 } 273 274 return GNUNET_OK; 275 } 276 } 277 278 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 279 "Field 'type' invalid in output #%u\n", 280 (unsigned int) index); 281 GNUNET_break_op (0); 282 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 parse_contract_choices ( 296 void *cls, 297 json_t *root, 298 struct GNUNET_JSON_Specification *ospec) 299 { 300 struct TALER_MERCHANT_ContractChoice **choices = ospec->ptr; 301 unsigned int *choices_len = cls; 302 303 if (! json_is_array (root)) 304 { 305 GNUNET_break_op (0); 306 return GNUNET_SYSERR; 307 } 308 if (0 == json_array_size (root)) 309 { 310 /* empty list of choices is not allowed */ 311 GNUNET_break_op (0); 312 return GNUNET_SYSERR; 313 } 314 *choices = NULL; 315 *choices_len = 0; 316 GNUNET_array_grow (*choices, 317 *choices_len, 318 json_array_size (root)); 319 320 for (unsigned int i = 0; i < *choices_len; i++) 321 { 322 struct TALER_MERCHANT_ContractChoice *choice = &(*choices)[i]; 323 const json_t *jinputs = NULL; 324 const json_t *joutputs = NULL; 325 struct GNUNET_JSON_Specification spec[] = { 326 TALER_JSON_spec_amount_any ("amount", 327 &choice->amount), 328 GNUNET_JSON_spec_mark_optional ( 329 TALER_JSON_spec_amount_any ("tip", 330 &choice->tip), 331 &choice->no_tip), 332 GNUNET_JSON_spec_mark_optional ( 333 GNUNET_JSON_spec_string_copy ("description", 334 &choice->description), 335 NULL), 336 GNUNET_JSON_spec_mark_optional ( 337 GNUNET_JSON_spec_object_copy ("description_i18n", 338 &choice->description_i18n), 339 NULL), 340 TALER_JSON_spec_amount_any ("max_fee", 341 &choice->max_fee), 342 GNUNET_JSON_spec_mark_optional ( 343 GNUNET_JSON_spec_array_const ("inputs", 344 &jinputs), 345 NULL), 346 GNUNET_JSON_spec_mark_optional ( 347 GNUNET_JSON_spec_array_const ("outputs", 348 &joutputs), 349 NULL), 350 GNUNET_JSON_spec_end () 351 }; 352 const char *ename; 353 unsigned int eline; 354 355 if (GNUNET_OK != 356 GNUNET_JSON_parse (json_array_get (root, i), 357 spec, 358 &ename, 359 &eline)) 360 { 361 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 362 "Failed to parse %s at %u: %s\n", 363 spec[eline].field, 364 eline, 365 ename); 366 GNUNET_break_op (0); 367 return GNUNET_SYSERR; 368 } 369 if ( (! choice->no_tip) && 370 (GNUNET_OK != 371 TALER_amount_cmp_currency (&choice->amount, 372 &choice->tip)) ) 373 { 374 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 375 "Tip currency does not match amount currency in choice #%u\n", 376 i); 377 GNUNET_break_op (0); 378 return GNUNET_SYSERR; 379 } 380 381 if (NULL != jinputs) 382 { 383 const json_t *jinput; 384 size_t idx; 385 386 json_array_foreach ((json_t *) jinputs, idx, jinput) 387 { 388 struct TALER_MERCHANT_ContractInput input = { 389 .details.token.count = 1 390 }; 391 392 if (GNUNET_OK != 393 parse_contract_choice_input ((json_t *) jinput, 394 &input, 395 idx)) 396 { 397 GNUNET_break (0); 398 return GNUNET_SYSERR; 399 } 400 switch (input.type) 401 { 402 case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: 403 GNUNET_break_op (0); 404 return GNUNET_SYSERR; 405 case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: 406 /* Ignore inputs tokens with 'count' field set to 0 */ 407 if (0 == input.details.token.count) 408 continue; 409 break; 410 } 411 GNUNET_array_append (choice->inputs, 412 choice->inputs_len, 413 input); 414 } 415 } 416 417 if (NULL != joutputs) 418 { 419 const json_t *joutput; 420 size_t idx; 421 json_array_foreach ((json_t *) joutputs, idx, joutput) 422 { 423 struct TALER_MERCHANT_ContractOutput output = { 424 .details.token.count = 1 425 }; 426 427 if (GNUNET_OK != 428 parse_contract_choice_output ((json_t *) joutput, 429 &output, 430 idx)) 431 { 432 GNUNET_break (0); 433 contract_choice_output_free (&output); 434 return GNUNET_SYSERR; 435 } 436 switch (output.type) 437 { 438 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: 439 GNUNET_break_op (0); 440 contract_choice_output_free (&output); 441 return GNUNET_SYSERR; 442 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: 443 /* Ignore output tokens with 'count' field set to 0 */ 444 if (0 == output.details.token.count) 445 { 446 contract_choice_output_free (&output); 447 continue; 448 } 449 break; 450 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: 451 break; 452 } 453 GNUNET_array_append (choice->outputs, 454 choice->outputs_len, 455 output); 456 } 457 } 458 } 459 460 return GNUNET_OK; 461 } 462 463 464 struct GNUNET_JSON_Specification 465 TALER_MERCHANT_spec_contract_choices ( 466 const char *name, 467 struct TALER_MERCHANT_ContractChoice **choices, 468 unsigned int *choices_len) 469 { 470 struct GNUNET_JSON_Specification ret = { 471 .cls = (void *) choices_len, 472 .parser = &parse_contract_choices, 473 .field = name, 474 .ptr = choices, 475 }; 476 477 return ret; 478 } 479 480 481 void 482 TALER_MERCHANT_contract_choice_free ( 483 struct TALER_MERCHANT_ContractChoice *choice) 484 { 485 for (unsigned int i = 0; i < choice->outputs_len; i++) 486 { 487 contract_choice_output_free (&choice->outputs[i]); 488 } 489 GNUNET_free (choice->description); 490 if (NULL != choice->description_i18n) 491 { 492 json_decref (choice->description_i18n); 493 choice->description_i18n = NULL; 494 } 495 GNUNET_free (choice->inputs); 496 GNUNET_free (choice->outputs); 497 }