Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2015, 2021 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU 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 : /**
18 : * @file src/util/test_contract.c
19 : * @brief Tests for contract parsing/serializing
20 : * @author Iván Ávalos (ivan@avalos.me)
21 : */
22 :
23 : #include "platform.h"
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <gnunet/gnunet_common.h>
26 : #include <gnunet/gnunet_json_lib.h>
27 : #include "taler/taler_merchant_util.h"
28 : #include <jansson.h>
29 :
30 : const char *contract_common = "{\n"
31 : " \"summary\": \"Test order\",\n"
32 : " \"summary_i18n\": {\n"
33 : " \"en\": \"Test order\",\n"
34 : " \"es\": \"Orden de prueba\"\n"
35 : " },\n"
36 : " \"order_id\": \"test\",\n"
37 : " \"public_reorder_url\": \"https://test.org/reorder\",\n"
38 : " \"fulfillment_url\": \"https://test.org/fulfillment\",\n"
39 : " \"fulfillment_message\": \"Thank you for your purchase!\",\n"
40 : " \"fulfillment_message_i18n\": {\n"
41 : " \"en\": \"Thank you for your purchase!\",\n"
42 : " \"es\": \"¡Gracias por su compra!\"\n"
43 : " },\n"
44 : " \"products\": [],\n"
45 : " \"timestamp\": {\"t_s\": 1736174497},\n"
46 : " \"refund_deadline\": {\"t_s\": 1736174497},\n"
47 : " \"pay_deadline\": {\"t_s\": 1736174497},\n"
48 : " \"wire_transfer_deadline\": {\"t_s\": 1736174497},\n"
49 : " \"merchant_pub\": \"F80MFRG8HVH6R9CQ47KRFQSJP3T6DBJ4K1D9B703RJY3Z39TBMJ0\",\n"
50 : " \"merchant_base_url\": \"https://test.org/merchant\",\n"
51 : " \"merchant\": {\n"
52 : " \"name\": \"Test merchant\",\n"
53 : " \"email\": \"test@test.org\",\n"
54 : " \"website\": \"https://test.org/merchant\",\n"
55 : " \"logo\": \"data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAADRUlEQVRYhe2WXUgUURiG39l1Vt021C2piJAQyhXEXVNKysoSd8nywgLNn1CIMCLposCLxOii8KasC2+CFB1WRVASMpMUKiyIaLXyBwmhoh9Kzc1W929murAZ9+znZl2EXey5OvOc93zfO+ec+eZwZmuLjFVsmtVMHjYQNvBfGIhYSSAD6LcXYJ1RrzLO1AhzAg9oNHD0FGPBLSJ2bwuSjRE4U2rCydIdmJpxIae4C9wK8blQdUCWgaH7pepzZXUf3rx34eqFXcgwbwIANLUP40bjKzzuLIRBzwMATFY7ojgJL3pL1eRmqwAuhJNlDSiTTQebEcUv7pKjd9GMxSaouj9hHp+M0f4yeH0Sdh6xEwPkDOTu2QzuV4CVkltsAkYnptTxQI3CInkOFpsAHa9Byjbj7w2Ikoy6i9lMIh+nDfmWAFBS1UuYonX6OIY13zwEOWi9GQMv+8pwS3Awgtf3jjPJg5ME9wPZZH8Jw7r7JphzRQwAQIMwovZHZ/wkcF11Jj5/cRHumvfjVFES4Z/mRLVfe+0ZGVcNLHglMjg3eALff3gZlrs/ETnFnUSbdtiO0+XpDHN7RIz1FBKtKC3tg2rAtDUG35xuRqjjNdhd2EUC8BH0m9LraE2rrO5DXEwU4ZJ2qfyos6adXqw1RBKx0bBirQrZJj/MEyaKQKxeu4yBWTcitOybuT0i7t7OJ0G8flq7PD7KepryMb/AniOtFph1eqgB3TLLGpPVAsManmF3escx0HqUaB92HENH9wjDDHoeG7PpFxK4hWTjqspT1H6ykS7/pfrn2BCvJzzeGI0rDQ7CE2OXYtScTSfjjIFUq4CKolRGkJrXxhQZpQWyUOPbc9nSW5CXBLOVXRHGgIYDzl8eYAJqRD9JohSe+toswgK1eo3EsPJzPeSnRLag/8lHdYJy2ILru8L2ZSao/4Tg5ApzeaTFWD4Jw+MzwemWvw9YbAJkGRh5UAZg8ddssQmIitapCeZcXlhsAmqq0lX26Ok7bDlgR17Gesas2SaEvBeEvA8oTZaBgbYCGOP0aG534HrjCIam/XAPliFSp0VaXitkUcTQWx/ksQp13tdpF3JLaBH7awP/uq36nTBsIGxg1Q38BGj1Qe2GKmj3AAAAAElFTkSuQmCC\",\n"
56 : " \"address\": {},\n" // TODO
57 : " \"jurisdiction\": {}\n" // TODO
58 : " },\n"
59 : " \"h_wire\": \"WYEMPXPRA87Y1QBJAJSR5SMB8V3QN2MMHAKDAEN04XC8TQ6TBEJF5KAKT3Y8KKP9W0TW3A2PH1YB22EZJ9TA7HX1P5BFTJQ660GS1TG\",\n"
60 : " \"wire_method\": \"iban\",\n"
61 : " \"exchanges\": [],\n"
62 : " \"delivery_location\": {},\n" // TODO
63 : " \"delivery_date\": {\"t_s\": 1736174497},\n"
64 : " \"nonce\": \"test-nonce\",\n"
65 : " \"auto_refund\": {\"d_us\": \"forever\"},\n"
66 : " \"extra\": {\n"
67 : " \"key0\": \"value0\",\n"
68 : " \"key1\": \"value1\",\n"
69 : " \"key2\": 2\n"
70 : " },\n"
71 : " \"minimum_age\": 100\n"
72 : "}";
73 :
74 : const char *contract_v0 = "{\n"
75 : " \"version\": 0,\n"
76 : " \"amount\": \"KUDOS:10\",\n"
77 : " \"amount_external\": [\n"
78 : " {\n"
79 : " \"method\": \"cash\",\n"
80 : " \"id\": \"cash1\",\n"
81 : " \"amount\": \"KUDOS:30\",\n"
82 : " \"cashier_number\": \"7\"\n"
83 : " },\n"
84 : " {\n"
85 : " \"method\": \"card\",\n"
86 : " \"id\": \"sumup1\",\n"
87 : " \"amount\": \"KUDOS:5\",\n"
88 : " \"terminal_id\": 42,\n"
89 : " \"contactless\": true\n"
90 : " }\n"
91 : " ],\n"
92 : " \"max_fee\": \"KUDOS:0.2\"\n"
93 : "}";
94 :
95 : const char *contract_v1 = "{\n"
96 : " \"version\": 1,\n"
97 : " \"amount_external\": [\n"
98 : " {\n"
99 : " \"method\": \"cash\",\n"
100 : " \"id\": \"cash1\",\n"
101 : " \"amount\": \"KUDOS:3\"\n"
102 : " }\n"
103 : " ],\n"
104 : " \"choices\": [\n"
105 : " {\n"
106 : " \"amount\": \"KUDOS:1\",\n"
107 : " \"max_fee\": \"KUDOS:0.5\",\n"
108 : " \"description\": \"Subscription in, discount out\",\n"
109 : " \"description_i18n\": {\n"
110 : " \"en\": \"This is a test choice\",\n"
111 : " \"es\": \"Esta es una opción de prueba\"\n"
112 : " },\n"
113 : " \"inputs\": [\n"
114 : " {\n"
115 : " \"type\": \"token\",\n"
116 : " \"token_family_slug\": \"test-subscription\",\n"
117 : " \"count\": 1\n"
118 : " }\n"
119 : " ],\n"
120 : " \"outputs\": [\n"
121 : " {\n"
122 : " \"type\": \"token\",\n"
123 : " \"token_family_slug\": \"test-discount\",\n"
124 : " \"count\": 2,\n"
125 : " \"key_index\": 0\n" // TODO
126 : " },\n"
127 : " {\n"
128 : " \"type\": \"tax-receipt\",\n"
129 : " \"donau_urls\": [\"a\", \"b\", \"c\"],\n"
130 : " \"amount\": \"KUDOS:10\"\n"
131 : " }\n"
132 : " ]\n"
133 : " }\n"
134 : " ],\n"
135 : " \"token_families\": {\n"
136 : " \"test-subscription\": {\n"
137 : " \"name\": \"Test subscription\",\n"
138 : " \"description\": \"This is a test subscription\",\n"
139 : " \"description_i18n\": {\n"
140 : " \"en\": \"This is a test subscription\",\n"
141 : " \"es\": \"Esta es una subscripción de prueba\"\n"
142 : " },\n"
143 : " \"keys\": [\n"
144 : " {\n"
145 : " \"cipher\": \"CS\",\n"
146 : " \"cs_pub\": \"AMGKHAWCF3Y32E64G6JV7TPP7KHE2C3QFMNZ8N66Q744FV3TH1D0\",\n"
147 : " \"signature_validity_start\": {\"t_s\": 1736174497},\n"
148 : " \"signature_validity_end\": {\"t_s\": 1736174497}\n"
149 : " }\n"
150 : " ],\n"
151 : " \"details\": {\n"
152 : " \"class\": \"subscription\",\n"
153 : " \"trusted_domains\": [\"a\", \"b\", \"c\"]\n"
154 : " },\n"
155 : " \"critical\": true\n"
156 : " },\n"
157 : " \"test-discount\": {\n"
158 : " \"name\": \"Test discount\",\n"
159 : " \"description\": \"This is a test discount\",\n"
160 : " \"description_i18n\": {\n"
161 : " \"en\": \"This is a test discount\",\n"
162 : " \"es\": \"Este es un descuento de prueba\"\n"
163 : " },\n"
164 : " \"keys\": [\n"
165 : " {\n"
166 : " \"cipher\": \"RSA\",\n"
167 : " \"rsa_pub\": \"040000YGF5DK0PKCN99J0V814C20Q54C82S3RE3GBVC2T4QXEP7N05ABAN5DG8BC3FTN33BSG15VFX2N9X95HE7GBDAHSYHG4G00VHDCV4E0W4HVYTZGN6SGPBMTAE1XMYBH5DFWT4TXPSEQB96AG3G65X6BPQ0WXSARD5NP2YR1CQB6GB0W2BSKZK1AXZN67GHB3HHAPFV8V584QF1DGDXEWN875RN4HYNH3AW4XZ9SP5A7J5MED56P0TXX5D8C1HPWHFD89GE6Q7J0Q3QKM18WAVAZJTF6PR3Q5T2C71ST0VTP42F16ZZRWS4CHSXHM5RW0BGH383VX4100AD61X6QQ99K12Q17EQZK5MSE6AGNK24SCAH06XTXA7WFC78V0ARJKFDX1M483GE9SX20XXFKSTQ6B8104002\",\n"
168 : " \"signature_validity_start\": {\"t_s\": 1736174497},\n"
169 : " \"signature_validity_end\": {\"t_s\": 1736174497}\n"
170 : " }\n"
171 : " ],\n"
172 : " \"details\": {\n"
173 : " \"class\": \"discount\",\n"
174 : " \"expected_domains\": [\"a\", \"b\", \"c\"]\n"
175 : " },\n"
176 : " \"critical\": true\n"
177 : " }\n"
178 : " }\n"
179 : "}";
180 :
181 :
182 : int
183 1 : main (int argc,
184 : const char *const argv[])
185 : {
186 : (void) argc;
187 : (void) argv;
188 1 : GNUNET_log_setup ("test-contract",
189 : "WARNING",
190 : NULL);
191 :
192 : { // Contract v0
193 : json_t *common;
194 : json_t *v0;
195 : struct TALER_MERCHANT_Contract *v0_parsed;
196 : json_t *v0_serialized;
197 :
198 1 : common = json_loads (contract_common, 0, NULL);
199 1 : GNUNET_assert (NULL != common);
200 :
201 1 : v0 = json_loads (contract_v0, 0, NULL);
202 1 : GNUNET_assert (NULL != v0);
203 :
204 1 : GNUNET_assert (0 == json_object_update_new (v0, common));
205 :
206 1 : v0_parsed = TALER_MERCHANT_contract_parse (v0);
207 1 : GNUNET_assert (NULL != v0_parsed);
208 :
209 1 : v0_serialized = TALER_MERCHANT_contract_serialize (v0_parsed);
210 1 : GNUNET_assert (NULL != v0_serialized);
211 1 : GNUNET_assert (1 == json_equal (v0, v0_serialized));
212 :
213 1 : json_decref (v0_serialized);
214 1 : TALER_MERCHANT_contract_free (v0_parsed);
215 1 : json_decref (v0);
216 : }
217 :
218 : { // Contract v1
219 : json_t *common;
220 : json_t *v1;
221 : struct TALER_MERCHANT_Contract *v1_parsed;
222 : json_t *v1_serialized;
223 :
224 1 : common = json_loads (contract_common, 0, NULL);
225 1 : GNUNET_assert (NULL != common);
226 :
227 1 : v1 = json_loads (contract_v1, 0, NULL);
228 1 : GNUNET_assert (NULL != v1);
229 :
230 1 : GNUNET_assert (0 == json_object_update_new (v1, common));
231 :
232 1 : v1_parsed = TALER_MERCHANT_contract_parse (v1);
233 1 : GNUNET_assert (NULL != v1_parsed);
234 :
235 1 : v1_serialized = TALER_MERCHANT_contract_serialize (v1_parsed);
236 1 : GNUNET_assert (NULL != v1_serialized);
237 :
238 1 : GNUNET_assert (1 == json_equal (v1, v1_serialized));
239 :
240 1 : for (unsigned int i = 0;
241 3 : i < v1_parsed->pc->details.v1.token_authorities_len;
242 2 : i++)
243 : {
244 2 : struct TALER_MERCHANT_ContractTokenFamily *in =
245 2 : &v1_parsed->pc->details.v1.token_authorities[i];
246 4 : for (unsigned int j = 0; j < in->keys_len; j++)
247 : {
248 2 : struct TALER_MERCHANT_ContractTokenFamilyKey *inkey = &in->keys[j];
249 : struct TALER_MERCHANT_ContractTokenFamily out;
250 : struct TALER_MERCHANT_ContractTokenFamilyKey outkey;
251 2 : GNUNET_assert (GNUNET_OK == TALER_MERCHANT_find_token_family_key (
252 : in->slug,
253 : inkey->valid_after,
254 : v1_parsed->pc->details.v1.token_authorities,
255 : v1_parsed->pc->details.v1.token_authorities_len,
256 : &out,
257 : &outkey));
258 2 : GNUNET_assert (0 == GNUNET_memcmp (in, &out));
259 2 : GNUNET_assert (0 == GNUNET_memcmp (inkey, &outkey));
260 : }
261 : }
262 :
263 1 : json_decref (v1_serialized);
264 1 : TALER_MERCHANT_contract_free (v1_parsed);
265 1 : json_decref (v1);
266 : }
267 :
268 : { // Template pay duration validation
269 : json_t *finite;
270 : json_t *zero;
271 : json_t *omitted;
272 : json_t *forever;
273 1 : struct TALER_MERCHANT_TemplateContract parsed = { 0 };
274 :
275 1 : finite = json_pack ("{s:{s:I}}",
276 : "pay_duration",
277 : "d_us",
278 1 : (json_int_t) GNUNET_TIME_UNIT_MINUTES.rel_value_us);
279 1 : zero = json_pack ("{s:{s:I}}",
280 : "pay_duration",
281 : "d_us",
282 : (json_int_t) 0);
283 1 : omitted = json_object ();
284 1 : forever = json_pack ("{s:{s:s}}",
285 : "pay_duration",
286 : "d_us",
287 : "forever");
288 1 : GNUNET_assert (TALER_MERCHANT_template_contract_valid (finite));
289 1 : GNUNET_assert (TALER_MERCHANT_template_contract_valid (zero));
290 1 : GNUNET_assert (TALER_MERCHANT_template_contract_valid (omitted));
291 1 : GNUNET_assert (! TALER_MERCHANT_template_contract_valid (forever));
292 : /* Legacy templates must remain parseable so they can fall back to the
293 : instance default when instantiated. */
294 1 : GNUNET_assert (
295 : GNUNET_OK ==
296 : TALER_MERCHANT_template_contract_parse (forever,
297 : &parsed,
298 : NULL));
299 1 : GNUNET_assert (GNUNET_TIME_relative_is_forever (parsed.pay_duration));
300 1 : TALER_MERCHANT_template_contract_free (&parsed);
301 1 : json_decref (finite);
302 1 : json_decref (zero);
303 1 : json_decref (omitted);
304 1 : json_decref (forever);
305 : }
306 :
307 : { // amount_external currency must match all v1 choices
308 : json_t *common;
309 : json_t *v1;
310 : json_t *bad_external;
311 :
312 1 : common = json_loads (contract_common, 0, NULL);
313 1 : GNUNET_assert (NULL != common);
314 1 : v1 = json_loads (contract_v1, 0, NULL);
315 1 : GNUNET_assert (NULL != v1);
316 1 : GNUNET_assert (0 == json_object_update_new (v1, common));
317 1 : bad_external = json_loads (
318 : "[{\"method\":\"cash\",\"id\":\"cash1\",\"amount\":\"EUR:3\"}]",
319 : 0,
320 : NULL);
321 1 : GNUNET_assert (NULL != bad_external);
322 1 : GNUNET_assert (0 == json_object_set_new (v1,
323 : "amount_external",
324 : bad_external));
325 1 : GNUNET_assert (NULL == TALER_MERCHANT_contract_parse (v1));
326 1 : json_decref (v1);
327 : }
328 :
329 : { // amount_external validation
330 : struct TALER_Amount amount;
331 : static const char *invalid[] = {
332 : /* method "taler" is reserved */
333 : "[{\"method\":\"taler\",\"id\":\"t1\",\"amount\":\"KUDOS:1\"}]",
334 : /* entries disagreeing on a currency among themselves */
335 : "[{\"method\":\"cash\",\"id\":\"cash1\",\"amount\":\"KUDOS:1\"},"
336 : " {\"method\":\"card\",\"id\":\"card1\",\"amount\":\"EUR:1\"}]",
337 : /* missing id */
338 : "[{\"method\":\"cash\",\"amount\":\"KUDOS:1\"}]",
339 : /* missing amount */
340 : "[{\"method\":\"cash\",\"id\":\"cash1\"}]",
341 : /* empty method name */
342 : "[{\"method\":\"\",\"id\":\"cash1\",\"amount\":\"KUDOS:1\"}]",
343 : /* method name with blank */
344 : "[{\"method\":\"ca sh\",\"id\":\"cash1\",\"amount\":\"KUDOS:1\"}]",
345 : /* duplicate id */
346 : "[{\"method\":\"cash\",\"id\":\"cash1\",\"amount\":\"KUDOS:1\"},"
347 : " {\"method\":\"card\",\"id\":\"cash1\",\"amount\":\"KUDOS:1\"}]",
348 : /* nested method-specific field */
349 : "[{\"method\":\"cash\",\"id\":\"cash1\",\"amount\":\"KUDOS:1\","
350 : " \"details\":{\"register\":1}}]",
351 : NULL
352 : };
353 :
354 1 : GNUNET_assert (GNUNET_OK ==
355 : TALER_string_to_amount ("KUDOS:10",
356 : &amount));
357 : {
358 : json_t *valid;
359 :
360 1 : valid = json_loads (
361 : "[{\"method\":\"cash\",\"id\":\"cash1\",\"amount\":\"KUDOS:30\","
362 : " \"cashier_number\":\"7\"}]",
363 : 0,
364 : NULL);
365 1 : GNUNET_assert (NULL != valid);
366 1 : GNUNET_assert (TALER_MERCHANT_amount_external_valid (valid));
367 : /* valid entries in the currency of the order, and not in another */
368 1 : GNUNET_assert (TALER_MERCHANT_amount_external_currency_valid (valid,
369 : &amount));
370 : {
371 : struct TALER_Amount other;
372 :
373 1 : GNUNET_assert (GNUNET_OK ==
374 : TALER_string_to_amount ("EUR:10",
375 : &other));
376 1 : GNUNET_assert (! TALER_MERCHANT_amount_external_currency_valid (valid,
377 : &other));
378 : }
379 1 : GNUNET_assert (TALER_MERCHANT_amount_external_total_valid (valid,
380 : &amount));
381 1 : json_decref (valid);
382 : }
383 : { // the order total must stay representable
384 : json_t *huge;
385 : struct TALER_Amount big;
386 :
387 : /* Two entries just below the largest representable amount:
388 : each is fine on its own, their sum is not. */
389 1 : huge = json_loads (
390 : "[{\"method\":\"cash\",\"id\":\"cash1\","
391 : " \"amount\":\"KUDOS:4503599627370495\"},"
392 : " {\"method\":\"card\",\"id\":\"card1\","
393 : " \"amount\":\"KUDOS:4503599627370495\"}]",
394 : 0,
395 : NULL);
396 1 : GNUNET_assert (NULL != huge);
397 : /* the entries themselves are perfectly valid */
398 1 : GNUNET_assert (TALER_MERCHANT_amount_external_valid (huge));
399 1 : GNUNET_assert (TALER_MERCHANT_amount_external_currency_valid (huge,
400 : &amount));
401 : /* ... but adding them up overflows */
402 1 : GNUNET_assert (! TALER_MERCHANT_amount_external_total_valid (huge,
403 : &amount));
404 1 : json_decref (huge);
405 :
406 : /* A single entry that overflows only once the Taler amount of
407 : the order is added to it. */
408 1 : huge = json_loads (
409 : "[{\"method\":\"cash\",\"id\":\"cash1\","
410 : " \"amount\":\"KUDOS:4503599627370495\"}]",
411 : 0,
412 : NULL);
413 1 : GNUNET_assert (NULL != huge);
414 1 : GNUNET_assert (GNUNET_OK ==
415 : TALER_string_to_amount ("KUDOS:10",
416 : &big));
417 1 : GNUNET_assert (! TALER_MERCHANT_amount_external_total_valid (huge,
418 : &big));
419 1 : json_decref (huge);
420 : }
421 9 : for (unsigned int i = 0; NULL != invalid[i]; i++)
422 : {
423 : json_t *j;
424 :
425 8 : j = json_loads (invalid[i], 0, NULL);
426 8 : GNUNET_assert (NULL != j);
427 8 : GNUNET_assert (! TALER_MERCHANT_amount_external_valid (j));
428 8 : json_decref (j);
429 : }
430 : }
431 :
432 1 : return 0;
433 : }
|