contract_choice_parse.c (14970B)
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 if (GNUNET_OK != 381 TALER_amount_cmp_currency (&choice->amount, 382 &choice->max_fee)) 383 { 384 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 385 "Fee currency does not match amount currency in choice #%u\n", 386 i); 387 GNUNET_break_op (0); 388 return GNUNET_SYSERR; 389 } 390 391 if (NULL != jinputs) 392 { 393 const json_t *jinput; 394 size_t idx; 395 396 json_array_foreach ((json_t *) jinputs, idx, jinput) 397 { 398 struct TALER_MERCHANT_ContractInput input = { 399 .details.token.count = 1 400 }; 401 402 if (GNUNET_OK != 403 parse_contract_choice_input ((json_t *) jinput, 404 &input, 405 idx)) 406 { 407 GNUNET_break (0); 408 return GNUNET_SYSERR; 409 } 410 switch (input.type) 411 { 412 case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: 413 GNUNET_break_op (0); 414 return GNUNET_SYSERR; 415 case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: 416 /* Ignore inputs tokens with 'count' field set to 0 */ 417 if (0 == input.details.token.count) 418 continue; 419 break; 420 } 421 GNUNET_array_append (choice->inputs, 422 choice->inputs_len, 423 input); 424 } 425 } 426 427 if (NULL != joutputs) 428 { 429 const json_t *joutput; 430 size_t idx; 431 json_array_foreach ((json_t *) joutputs, idx, joutput) 432 { 433 struct TALER_MERCHANT_ContractOutput output = { 434 .details.token.count = 1 435 }; 436 437 if (GNUNET_OK != 438 parse_contract_choice_output ((json_t *) joutput, 439 &output, 440 idx)) 441 { 442 GNUNET_break (0); 443 contract_choice_output_free (&output); 444 return GNUNET_SYSERR; 445 } 446 switch (output.type) 447 { 448 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: 449 GNUNET_break_op (0); 450 contract_choice_output_free (&output); 451 return GNUNET_SYSERR; 452 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: 453 /* Ignore output tokens with 'count' field set to 0 */ 454 if (0 == output.details.token.count) 455 { 456 contract_choice_output_free (&output); 457 continue; 458 } 459 break; 460 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: 461 break; 462 } 463 GNUNET_array_append (choice->outputs, 464 choice->outputs_len, 465 output); 466 } 467 } 468 } 469 470 return GNUNET_OK; 471 } 472 473 474 struct GNUNET_JSON_Specification 475 TALER_MERCHANT_spec_contract_choices ( 476 const char *name, 477 struct TALER_MERCHANT_ContractChoice **choices, 478 unsigned int *choices_len) 479 { 480 struct GNUNET_JSON_Specification ret = { 481 .cls = (void *) choices_len, 482 .parser = &parse_contract_choices, 483 .field = name, 484 .ptr = choices, 485 }; 486 487 return ret; 488 } 489 490 491 void 492 TALER_MERCHANT_contract_choice_free ( 493 struct TALER_MERCHANT_ContractChoice *choice) 494 { 495 for (unsigned int i = 0; i < choice->outputs_len; i++) 496 { 497 contract_choice_output_free (&choice->outputs[i]); 498 } 499 GNUNET_free (choice->description); 500 if (NULL != choice->description_i18n) 501 { 502 json_decref (choice->description_i18n); 503 choice->description_i18n = NULL; 504 } 505 GNUNET_free (choice->inputs); 506 GNUNET_free (choice->outputs); 507 }