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