Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022, 2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it
6 : under the terms of the GNU General Public License as published by
7 : the Free Software Foundation; either version 3, or (at your
8 : option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not, see
17 : <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file testing/testing_api_cmd_reserve_purse.c
21 : * @brief command for testing /reserves/$PID/purse
22 : * @author Christian Grothoff
23 : */
24 : #include "taler/taler_json_lib.h"
25 : #include <gnunet/gnunet_curl_lib.h>
26 : #include "taler/taler_testing_lib.h"
27 : #include "taler/taler_signatures.h"
28 :
29 :
30 : /**
31 : * State for a "purse create with merge" CMD.
32 : */
33 : struct ReservePurseState
34 : {
35 :
36 : /**
37 : * Merge time (local time when the command was
38 : * executed).
39 : */
40 : struct GNUNET_TIME_Timestamp merge_timestamp;
41 :
42 : /**
43 : * Account (reserve) private key.
44 : */
45 : union TALER_AccountPrivateKeyP account_priv;
46 :
47 : /**
48 : * Account (reserve) public key.
49 : */
50 : union TALER_AccountPublicKeyP account_pub;
51 :
52 : /**
53 : * Reserve signature generated for the request
54 : * (client-side).
55 : */
56 : struct TALER_ReserveSignatureP reserve_sig;
57 :
58 : /**
59 : * Private key of the purse.
60 : */
61 : struct TALER_PurseContractPrivateKeyP purse_priv;
62 :
63 : /**
64 : * Public key of the purse.
65 : */
66 : struct TALER_PurseContractPublicKeyP purse_pub;
67 :
68 : /**
69 : * Private key with the merge capability.
70 : */
71 : struct TALER_PurseMergePrivateKeyP merge_priv;
72 :
73 : /**
74 : * Public key of the merge capability.
75 : */
76 : struct TALER_PurseMergePublicKeyP merge_pub;
77 :
78 : /**
79 : * Private key to decrypt the contract.
80 : */
81 : struct TALER_ContractDiffiePrivateP contract_priv;
82 :
83 : /**
84 : * Handle while operation is running.
85 : */
86 : struct TALER_EXCHANGE_PostReservesPurseHandle *dh;
87 :
88 : /**
89 : * When will the purse expire?
90 : */
91 : struct GNUNET_TIME_Relative expiration_rel;
92 :
93 : /**
94 : * When will the purse expire?
95 : */
96 : struct GNUNET_TIME_Timestamp purse_expiration;
97 :
98 : /**
99 : * Hash of the payto://-URI for the reserve we are
100 : * merging into.
101 : */
102 : struct TALER_NormalizedPaytoHashP h_payto;
103 :
104 : /**
105 : * Set to the KYC requirement row *if* the exchange replied with
106 : * a request for KYC.
107 : */
108 : uint64_t requirement_row;
109 :
110 : /**
111 : * Contract terms for the purse.
112 : */
113 : json_t *contract_terms;
114 :
115 : /**
116 : * Reference to the reserve, or NULL (!).
117 : */
118 : const char *reserve_ref;
119 :
120 : /**
121 : * Interpreter state.
122 : */
123 : struct TALER_TESTING_Interpreter *is;
124 :
125 : /**
126 : * Expected HTTP response code.
127 : */
128 : unsigned int expected_response_code;
129 :
130 : /**
131 : * True to pay the purse fee.
132 : */
133 : bool pay_purse_fee;
134 : };
135 :
136 :
137 : /**
138 : * Callback to analyze the /reserves/$PID/purse response, just used to check if
139 : * the response code is acceptable.
140 : *
141 : * @param cls closure.
142 : * @param dr purse response details
143 : */
144 : static void
145 14 : purse_cb (void *cls,
146 : const struct TALER_EXCHANGE_PostReservesPurseResponse *dr)
147 : {
148 14 : struct ReservePurseState *ds = cls;
149 :
150 14 : ds->dh = NULL;
151 14 : ds->reserve_sig = *dr->reserve_sig;
152 14 : if (ds->expected_response_code != dr->hr.http_status)
153 : {
154 0 : TALER_TESTING_unexpected_status (ds->is,
155 : dr->hr.http_status,
156 : ds->expected_response_code);
157 0 : return;
158 : }
159 14 : switch (dr->hr.http_status)
160 : {
161 1 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
162 : /* KYC required */
163 1 : ds->requirement_row =
164 1 : dr->details.unavailable_for_legal_reasons.requirement_row;
165 1 : GNUNET_break (0 ==
166 : GNUNET_memcmp (
167 : &ds->h_payto,
168 : &dr->details.unavailable_for_legal_reasons.h_payto));
169 1 : break;
170 : }
171 14 : TALER_TESTING_interpreter_next (ds->is);
172 : }
173 :
174 :
175 : /**
176 : * Run the command.
177 : *
178 : * @param cls closure.
179 : * @param cmd the command to execute.
180 : * @param is the interpreter state.
181 : */
182 : static void
183 14 : purse_run (void *cls,
184 : const struct TALER_TESTING_Command *cmd,
185 : struct TALER_TESTING_Interpreter *is)
186 : {
187 14 : struct ReservePurseState *ds = cls;
188 : const struct TALER_ReservePrivateKeyP *reserve_priv;
189 : const struct TALER_TESTING_Command *ref;
190 :
191 : (void) cmd;
192 14 : ds->is = is;
193 14 : ref = TALER_TESTING_interpreter_lookup_command (ds->is,
194 : ds->reserve_ref);
195 14 : GNUNET_assert (NULL != ref);
196 14 : if (GNUNET_OK !=
197 14 : TALER_TESTING_get_trait_reserve_priv (ref,
198 : &reserve_priv))
199 : {
200 0 : GNUNET_break (0);
201 0 : TALER_TESTING_interpreter_fail (ds->is);
202 0 : return;
203 : }
204 14 : ds->account_priv.reserve_priv = *reserve_priv;
205 14 : GNUNET_CRYPTO_eddsa_key_create (
206 : &ds->purse_priv.eddsa_priv);
207 14 : GNUNET_CRYPTO_eddsa_key_get_public (
208 14 : &ds->purse_priv.eddsa_priv,
209 : &ds->purse_pub.eddsa_pub);
210 14 : GNUNET_CRYPTO_eddsa_key_get_public (
211 14 : &ds->account_priv.reserve_priv.eddsa_priv,
212 : &ds->account_pub.reserve_pub.eddsa_pub);
213 14 : GNUNET_CRYPTO_eddsa_key_create (
214 : &ds->merge_priv.eddsa_priv);
215 14 : GNUNET_CRYPTO_eddsa_key_get_public (
216 14 : &ds->merge_priv.eddsa_priv,
217 : &ds->merge_pub.eddsa_pub);
218 14 : GNUNET_CRYPTO_ecdhe_key_create (
219 : &ds->contract_priv.ecdhe_priv);
220 : ds->purse_expiration
221 14 : = GNUNET_TIME_absolute_to_timestamp (
222 : GNUNET_TIME_relative_to_absolute (
223 : ds->expiration_rel));
224 :
225 : {
226 : struct TALER_NormalizedPayto payto_uri;
227 : const char *exchange_url;
228 : const struct TALER_TESTING_Command *exchange_cmd;
229 :
230 14 : exchange_cmd = TALER_TESTING_interpreter_get_command (is,
231 : "exchange");
232 14 : if (NULL == exchange_cmd)
233 : {
234 0 : GNUNET_break (0);
235 0 : TALER_TESTING_interpreter_fail (is);
236 0 : return;
237 : }
238 14 : GNUNET_assert (
239 : GNUNET_OK ==
240 : TALER_TESTING_get_trait_exchange_url (
241 : exchange_cmd,
242 : &exchange_url));
243 : payto_uri
244 14 : = TALER_reserve_make_payto (
245 : exchange_url,
246 14 : &ds->account_pub.reserve_pub);
247 14 : TALER_normalized_payto_hash (payto_uri,
248 : &ds->h_payto);
249 14 : GNUNET_free (payto_uri.normalized_payto);
250 : }
251 :
252 14 : GNUNET_assert (0 ==
253 : json_object_set_new (
254 : ds->contract_terms,
255 : "pay_deadline",
256 : GNUNET_JSON_from_timestamp (ds->purse_expiration)));
257 14 : ds->merge_timestamp = GNUNET_TIME_timestamp_get ();
258 14 : ds->dh = TALER_EXCHANGE_post_reserves_purse_create (
259 : TALER_TESTING_interpreter_get_context (is),
260 : TALER_TESTING_get_exchange_url (is),
261 : TALER_TESTING_get_keys (is),
262 14 : &ds->account_priv.reserve_priv,
263 14 : &ds->purse_priv,
264 14 : &ds->merge_priv,
265 14 : &ds->contract_priv,
266 14 : ds->contract_terms,
267 14 : ds->pay_purse_fee,
268 : ds->merge_timestamp);
269 14 : if (NULL == ds->dh)
270 : {
271 0 : GNUNET_break (0);
272 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
273 : "Could not purse reserve\n");
274 0 : TALER_TESTING_interpreter_fail (is);
275 0 : return;
276 : }
277 14 : GNUNET_assert (GNUNET_OK ==
278 : TALER_EXCHANGE_post_reserves_purse_set_options (
279 : ds->dh,
280 : TALER_EXCHANGE_post_reserves_purse_option_upload_contract ())
281 : );
282 14 : GNUNET_assert (TALER_EC_NONE ==
283 : TALER_EXCHANGE_post_reserves_purse_start (ds->dh,
284 : &purse_cb,
285 : ds));
286 : }
287 :
288 :
289 : /**
290 : * Free the state of a "purse" CMD, and possibly cancel a
291 : * pending operation thereof.
292 : *
293 : * @param cls closure, must be a `struct ReservePurseState`.
294 : * @param cmd the command which is being cleaned up.
295 : */
296 : static void
297 14 : purse_cleanup (void *cls,
298 : const struct TALER_TESTING_Command *cmd)
299 : {
300 14 : struct ReservePurseState *ds = cls;
301 :
302 14 : if (NULL != ds->dh)
303 : {
304 0 : TALER_TESTING_command_incomplete (ds->is,
305 : cmd->label);
306 0 : TALER_EXCHANGE_post_reserves_purse_cancel (ds->dh);
307 0 : ds->dh = NULL;
308 : }
309 14 : json_decref (ds->contract_terms);
310 14 : GNUNET_free (ds);
311 14 : }
312 :
313 :
314 : /**
315 : * Offer internal data from a "purse" CMD, to other commands.
316 : *
317 : * @param cls closure.
318 : * @param[out] ret result.
319 : * @param trait name of the trait.
320 : * @param index index number of the object to offer.
321 : * @return #GNUNET_OK on success.
322 : */
323 : static enum GNUNET_GenericReturnValue
324 51 : purse_traits (void *cls,
325 : const void **ret,
326 : const char *trait,
327 : unsigned int index)
328 : {
329 51 : struct ReservePurseState *ds = cls;
330 : struct TALER_TESTING_Trait traits[] = {
331 51 : TALER_TESTING_make_trait_timestamp (
332 : 0,
333 51 : &ds->merge_timestamp),
334 51 : TALER_TESTING_make_trait_contract_terms (
335 51 : ds->contract_terms),
336 51 : TALER_TESTING_make_trait_purse_priv (
337 51 : &ds->purse_priv),
338 51 : TALER_TESTING_make_trait_purse_pub (
339 51 : &ds->purse_pub),
340 51 : TALER_TESTING_make_trait_merge_priv (
341 51 : &ds->merge_priv),
342 51 : TALER_TESTING_make_trait_merge_pub (
343 51 : &ds->merge_pub),
344 51 : TALER_TESTING_make_trait_contract_priv (
345 51 : &ds->contract_priv),
346 51 : TALER_TESTING_make_trait_account_priv (
347 51 : &ds->account_priv),
348 51 : TALER_TESTING_make_trait_account_pub (
349 51 : &ds->account_pub),
350 51 : TALER_TESTING_make_trait_reserve_priv (
351 51 : &ds->account_priv.reserve_priv),
352 51 : TALER_TESTING_make_trait_reserve_pub (
353 51 : &ds->account_pub.reserve_pub),
354 51 : TALER_TESTING_make_trait_reserve_sig (
355 51 : &ds->reserve_sig),
356 51 : TALER_TESTING_make_trait_legi_requirement_row (
357 51 : &ds->requirement_row),
358 51 : TALER_TESTING_make_trait_h_normalized_payto (
359 51 : &ds->h_payto),
360 51 : TALER_TESTING_trait_end ()
361 : };
362 :
363 51 : return TALER_TESTING_get_trait (traits,
364 : ret,
365 : trait,
366 : index);
367 : }
368 :
369 :
370 : struct TALER_TESTING_Command
371 14 : TALER_TESTING_cmd_purse_create_with_reserve (
372 : const char *label,
373 : unsigned int expected_http_status,
374 : const char *contract_terms,
375 : bool upload_contract,
376 : bool pay_purse_fee,
377 : struct GNUNET_TIME_Relative expiration,
378 : const char *reserve_ref)
379 : {
380 : struct ReservePurseState *ds;
381 : json_error_t err;
382 :
383 14 : ds = GNUNET_new (struct ReservePurseState);
384 14 : ds->expiration_rel = expiration;
385 14 : ds->contract_terms = json_loads (contract_terms,
386 : 0 /* flags */,
387 : &err);
388 14 : GNUNET_assert (NULL != ds->contract_terms);
389 14 : ds->pay_purse_fee = pay_purse_fee;
390 14 : ds->reserve_ref = reserve_ref;
391 14 : ds->expected_response_code = expected_http_status;
392 :
393 : {
394 14 : struct TALER_TESTING_Command cmd = {
395 : .cls = ds,
396 : .label = label,
397 : .run = &purse_run,
398 : .cleanup = &purse_cleanup,
399 : .traits = &purse_traits
400 : };
401 :
402 14 : return cmd;
403 : }
404 : }
405 :
406 :
407 : /* end of testing_api_cmd_reserve_purse.c */
|