Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2022 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU General Public License as
7 : published by the Free Software Foundation; either version 3, or
8 : (at your 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
13 : GNU 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_history.c
21 : * @brief Implement the /reserve/history test command.
22 : * @author Marcello Stanisci
23 : */
24 : #include "platform.h"
25 : #include "taler_json_lib.h"
26 : #include <gnunet/gnunet_curl_lib.h>
27 : #include "taler_testing_lib.h"
28 :
29 :
30 : /**
31 : * State for a "history" CMD.
32 : */
33 : struct HistoryState
34 : {
35 :
36 : /**
37 : * Public key of the reserve being analyzed.
38 : */
39 : struct TALER_ReservePublicKeyP reserve_pub;
40 :
41 : /**
42 : * Label to the command which created the reserve to check,
43 : * needed to resort the reserve key.
44 : */
45 : const char *reserve_reference;
46 :
47 : /**
48 : * Handle to the "reserve history" operation.
49 : */
50 : struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
51 :
52 : /**
53 : * Expected reserve balance.
54 : */
55 : const char *expected_balance;
56 :
57 : /**
58 : * Private key of the reserve being analyzed.
59 : */
60 : const struct TALER_ReservePrivateKeyP *reserve_priv;
61 :
62 : /**
63 : * Interpreter state.
64 : */
65 : struct TALER_TESTING_Interpreter *is;
66 :
67 : /**
68 : * Reserve history entry that corresponds to this operation.
69 : * Will be of type #TALER_EXCHANGE_RTT_HISTORY.
70 : */
71 : struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
72 :
73 : /**
74 : * Expected HTTP response code.
75 : */
76 : unsigned int expected_response_code;
77 :
78 : };
79 :
80 :
81 : /**
82 : * Check if @a cmd changed the reserve, if so, find the
83 : * entry in @a history and set the respective index in @a found
84 : * to #GNUNET_YES. If the entry is not found, return #GNUNET_SYSERR.
85 : *
86 : * @param reserve_pub public key of the reserve for which we have the @a history
87 : * @param cmd command to analyze for impact on history
88 : * @param history_length number of entries in @a history and @a found
89 : * @param history history to check
90 : * @param[in,out] found array to update
91 : * @return #GNUNET_OK if @a cmd action on reserve was found in @a history
92 : */
93 : static enum GNUNET_GenericReturnValue
94 0 : analyze_command (const struct TALER_ReservePublicKeyP *reserve_pub,
95 : const struct TALER_TESTING_Command *cmd,
96 : unsigned int history_length,
97 : const struct TALER_EXCHANGE_ReserveHistoryEntry *history,
98 : bool *found)
99 : {
100 0 : if (TALER_TESTING_cmd_is_batch (cmd))
101 : {
102 : struct TALER_TESTING_Command *cur;
103 : struct TALER_TESTING_Command **bcmd;
104 :
105 0 : cur = TALER_TESTING_cmd_batch_get_current (cmd);
106 0 : if (GNUNET_OK !=
107 0 : TALER_TESTING_get_trait_batch_cmds (cmd,
108 : &bcmd))
109 : {
110 0 : GNUNET_break (0);
111 0 : return GNUNET_SYSERR;
112 : }
113 0 : for (unsigned int i = 0; NULL != (*bcmd)[i].label; i++)
114 : {
115 0 : struct TALER_TESTING_Command *step = &(*bcmd)[i];
116 :
117 0 : if (GNUNET_OK !=
118 0 : analyze_command (reserve_pub,
119 : step,
120 : history_length,
121 : history,
122 : found))
123 : {
124 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
125 : "Entry for batch step `%s' missing in history\n",
126 : step->label);
127 0 : return GNUNET_SYSERR;
128 : }
129 0 : if (step == cur)
130 0 : break; /* if *we* are in a batch, make sure not to analyze commands past 'now' */
131 : }
132 0 : return GNUNET_OK;
133 : }
134 :
135 : {
136 : const struct TALER_ReservePublicKeyP *rp;
137 :
138 0 : if (GNUNET_OK !=
139 0 : TALER_TESTING_get_trait_reserve_pub (cmd,
140 : &rp))
141 0 : return GNUNET_OK; /* command does nothing for reserves */
142 0 : if (0 !=
143 0 : GNUNET_memcmp (rp,
144 : reserve_pub))
145 0 : return GNUNET_OK; /* command affects some _other_ reserve */
146 0 : for (unsigned int j = 0; true; j++)
147 0 : {
148 : const struct TALER_EXCHANGE_ReserveHistoryEntry *he;
149 0 : bool matched = false;
150 :
151 0 : if (GNUNET_OK !=
152 0 : TALER_TESTING_get_trait_reserve_history (cmd,
153 : j,
154 : &he))
155 : {
156 : /* NOTE: only for debugging... */
157 0 : if (0 == j)
158 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
159 : "Command `%s' has the reserve_pub trait, but does not reserve history trait\n",
160 : cmd->label);
161 0 : return GNUNET_OK; /* command does nothing for reserves */
162 : }
163 0 : for (unsigned int i = 0; i<history_length; i++)
164 : {
165 0 : if (found[i])
166 0 : continue; /* already found, skip */
167 0 : if (0 ==
168 0 : TALER_TESTING_history_entry_cmp (he,
169 0 : &history[i]))
170 : {
171 0 : found[i] = true;
172 0 : matched = true;
173 0 : break;
174 : }
175 : }
176 0 : if (! matched)
177 : {
178 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
179 : "Command `%s' reserve history entry #%u not found\n",
180 : cmd->label,
181 : j);
182 0 : return GNUNET_SYSERR;
183 : }
184 : }
185 : }
186 : }
187 :
188 :
189 : /**
190 : * Check that the reserve balance and HTTP response code are
191 : * both acceptable.
192 : *
193 : * @param cls closure.
194 : * @param rs HTTP response details
195 : */
196 : static void
197 0 : reserve_history_cb (void *cls,
198 : const struct TALER_EXCHANGE_ReserveHistory *rs)
199 : {
200 0 : struct HistoryState *ss = cls;
201 0 : struct TALER_TESTING_Interpreter *is = ss->is;
202 : struct TALER_Amount eb;
203 :
204 0 : ss->rsh = NULL;
205 0 : if (MHD_HTTP_OK == rs->hr.http_status)
206 : {
207 : const struct TALER_EXCHANGE_Keys *keys;
208 : const struct TALER_EXCHANGE_GlobalFee *gf;
209 :
210 0 : ss->reserve_history.type = TALER_EXCHANGE_RTT_HISTORY;
211 0 : keys = TALER_EXCHANGE_get_keys (ss->is->exchange);
212 0 : GNUNET_assert (NULL != keys);
213 0 : gf = TALER_EXCHANGE_get_global_fee (keys,
214 : rs->ts);
215 0 : GNUNET_assert (NULL != gf);
216 0 : ss->reserve_history.amount = gf->fees.history;
217 0 : ss->reserve_history.details.history_details.request_timestamp = rs->ts;
218 0 : ss->reserve_history.details.history_details.reserve_sig = *rs->reserve_sig;
219 : }
220 0 : if (ss->expected_response_code != rs->hr.http_status)
221 : {
222 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
223 : "Unexpected HTTP response code: %d in %s:%u\n",
224 : rs->hr.http_status,
225 : __FILE__,
226 : __LINE__);
227 0 : json_dumpf (rs->hr.reply,
228 : stderr,
229 : 0);
230 0 : TALER_TESTING_interpreter_fail (ss->is);
231 0 : return;
232 : }
233 0 : if (MHD_HTTP_OK != rs->hr.http_status)
234 : {
235 0 : TALER_TESTING_interpreter_next (is);
236 0 : return;
237 : }
238 0 : GNUNET_assert (GNUNET_OK ==
239 : TALER_string_to_amount (ss->expected_balance,
240 : &eb));
241 :
242 0 : if (0 != TALER_amount_cmp (&eb,
243 : &rs->details.ok.balance))
244 : {
245 0 : GNUNET_break (0);
246 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
247 : "Unexpected amount in reserve: %s\n",
248 : TALER_amount_to_string (&rs->details.ok.balance));
249 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
250 : "Expected balance of: %s\n",
251 : TALER_amount_to_string (&eb));
252 0 : TALER_TESTING_interpreter_fail (ss->is);
253 0 : return;
254 : }
255 0 : {
256 0 : bool found[rs->details.ok.history_len];
257 :
258 0 : memset (found,
259 : 0,
260 0 : sizeof (found));
261 0 : for (unsigned int i = 0; i<= (unsigned int) is->ip; i++)
262 : {
263 0 : struct TALER_TESTING_Command *cmd = &is->commands[i];
264 :
265 0 : if (GNUNET_OK !=
266 0 : analyze_command (&ss->reserve_pub,
267 : cmd,
268 : rs->details.ok.history_len,
269 : rs->details.ok.history,
270 : found))
271 : {
272 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
273 : "Entry for command `%s' missing in history\n",
274 : cmd->label);
275 0 : json_dumpf (rs->hr.reply,
276 : stderr,
277 : JSON_INDENT (2));
278 0 : TALER_TESTING_interpreter_fail (ss->is);
279 0 : return;
280 : }
281 : }
282 0 : for (unsigned int i = 0; i<rs->details.ok.history_len; i++)
283 0 : if (! found[i])
284 : {
285 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
286 : "History entry at index %u of type %d not justified by command history\n",
287 : i,
288 : rs->details.ok.history[i].type);
289 0 : json_dumpf (rs->hr.reply,
290 : stderr,
291 : JSON_INDENT (2));
292 0 : TALER_TESTING_interpreter_fail (ss->is);
293 0 : return;
294 : }
295 : }
296 0 : TALER_TESTING_interpreter_next (is);
297 : }
298 :
299 :
300 : /**
301 : * Run the command.
302 : *
303 : * @param cls closure.
304 : * @param cmd the command being executed.
305 : * @param is the interpreter state.
306 : */
307 : static void
308 0 : history_run (void *cls,
309 : const struct TALER_TESTING_Command *cmd,
310 : struct TALER_TESTING_Interpreter *is)
311 : {
312 0 : struct HistoryState *ss = cls;
313 : const struct TALER_TESTING_Command *create_reserve;
314 :
315 0 : ss->is = is;
316 : create_reserve
317 0 : = TALER_TESTING_interpreter_lookup_command (is,
318 : ss->reserve_reference);
319 0 : if (NULL == create_reserve)
320 : {
321 0 : GNUNET_break (0);
322 0 : TALER_TESTING_interpreter_fail (is);
323 0 : return;
324 : }
325 0 : if (GNUNET_OK !=
326 0 : TALER_TESTING_get_trait_reserve_priv (create_reserve,
327 : &ss->reserve_priv))
328 : {
329 0 : GNUNET_break (0);
330 0 : TALER_LOG_ERROR ("Failed to find reserve_priv for history query\n");
331 0 : TALER_TESTING_interpreter_fail (is);
332 0 : return;
333 : }
334 0 : GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
335 : &ss->reserve_pub.eddsa_pub);
336 0 : ss->rsh = TALER_EXCHANGE_reserves_history (is->exchange,
337 : ss->reserve_priv,
338 : &reserve_history_cb,
339 : ss);
340 : }
341 :
342 :
343 : /**
344 : * Offer internal data from a "history" CMD, to other commands.
345 : *
346 : * @param cls closure.
347 : * @param[out] ret result.
348 : * @param trait name of the trait.
349 : * @param index index number of the object to offer.
350 : * @return #GNUNET_OK on success.
351 : */
352 : static enum GNUNET_GenericReturnValue
353 0 : history_traits (void *cls,
354 : const void **ret,
355 : const char *trait,
356 : unsigned int index)
357 : {
358 0 : struct HistoryState *hs = cls;
359 : struct TALER_TESTING_Trait traits[] = {
360 : /* history entry MUST be first due to response code logic below! */
361 0 : TALER_TESTING_make_trait_reserve_history (0,
362 0 : &hs->reserve_history),
363 0 : TALER_TESTING_make_trait_reserve_pub (&hs->reserve_pub),
364 0 : TALER_TESTING_trait_end ()
365 : };
366 :
367 0 : return TALER_TESTING_get_trait ((hs->expected_response_code == MHD_HTTP_OK)
368 : ? &traits[0] /* we have reserve history */
369 : : &traits[1], /* skip reserve history */
370 : ret,
371 : trait,
372 : index);
373 : }
374 :
375 :
376 : /**
377 : * Cleanup the state from a "reserve history" CMD, and possibly
378 : * cancel a pending operation thereof.
379 : *
380 : * @param cls closure.
381 : * @param cmd the command which is being cleaned up.
382 : */
383 : static void
384 0 : history_cleanup (void *cls,
385 : const struct TALER_TESTING_Command *cmd)
386 : {
387 0 : struct HistoryState *ss = cls;
388 :
389 0 : if (NULL != ss->rsh)
390 : {
391 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
392 : "Command %u (%s) did not complete\n",
393 : ss->is->ip,
394 : cmd->label);
395 0 : TALER_EXCHANGE_reserves_history_cancel (ss->rsh);
396 0 : ss->rsh = NULL;
397 : }
398 0 : GNUNET_free (ss);
399 0 : }
400 :
401 :
402 : struct TALER_TESTING_Command
403 0 : TALER_TESTING_cmd_reserve_history (const char *label,
404 : const char *reserve_reference,
405 : const char *expected_balance,
406 : unsigned int expected_response_code)
407 : {
408 : struct HistoryState *ss;
409 :
410 0 : GNUNET_assert (NULL != reserve_reference);
411 0 : ss = GNUNET_new (struct HistoryState);
412 0 : ss->reserve_reference = reserve_reference;
413 0 : ss->expected_balance = expected_balance;
414 0 : ss->expected_response_code = expected_response_code;
415 : {
416 0 : struct TALER_TESTING_Command cmd = {
417 : .cls = ss,
418 : .label = label,
419 : .run = &history_run,
420 : .cleanup = &history_cleanup,
421 : .traits = &history_traits
422 : };
423 :
424 0 : return cmd;
425 : }
426 : }
|