Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero 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 Affero General Public License for more details.
12 :
13 : You should have received a copy of the GNU Affero General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-exchange-httpd_purses_get.c
18 : * @brief Handle GET /purses/$PID/$TARGET requests
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include <gnunet/gnunet_util_lib.h>
23 : #include <jansson.h>
24 : #include <microhttpd.h>
25 : #include "taler_mhd_lib.h"
26 : #include "taler_dbevents.h"
27 : #include "taler-exchange-httpd_keys.h"
28 : #include "taler-exchange-httpd_purses_get.h"
29 : #include "taler-exchange-httpd_mhd.h"
30 : #include "taler-exchange-httpd_responses.h"
31 :
32 :
33 : /**
34 : * Information about an ongoing /purses GET operation.
35 : */
36 : struct GetContext
37 : {
38 : /**
39 : * Kept in a DLL.
40 : */
41 : struct GetContext *next;
42 :
43 : /**
44 : * Kept in a DLL.
45 : */
46 : struct GetContext *prev;
47 :
48 : /**
49 : * Connection we are handling.
50 : */
51 : struct MHD_Connection *connection;
52 :
53 : /**
54 : * Subscription for the database event we are
55 : * waiting for.
56 : */
57 : struct GNUNET_DB_EventHandler *eh;
58 :
59 : /**
60 : * Public key of our purse.
61 : */
62 : struct TALER_PurseContractPublicKeyP purse_pub;
63 :
64 : /**
65 : * When does this purse expire?
66 : */
67 : struct GNUNET_TIME_Timestamp purse_expiration;
68 :
69 : /**
70 : * When was this purse merged?
71 : */
72 : struct GNUNET_TIME_Timestamp merge_timestamp;
73 :
74 : /**
75 : * How much is the purse (supposed) to be worth?
76 : */
77 : struct TALER_Amount amount;
78 :
79 : /**
80 : * How much was deposited into the purse so far?
81 : */
82 : struct TALER_Amount deposited;
83 :
84 : /**
85 : * Hash over the contract of the purse.
86 : */
87 : struct TALER_PrivateContractHashP h_contract;
88 :
89 : /**
90 : * When will this request time out?
91 : */
92 : struct GNUNET_TIME_Absolute timeout;
93 :
94 : /**
95 : * true to wait for merge, false to wait for deposit.
96 : */
97 : bool wait_for_merge;
98 :
99 : /**
100 : * True if we are still suspended.
101 : */
102 : bool suspended;
103 : };
104 :
105 :
106 : /**
107 : * Head of DLL of suspended GET requests.
108 : */
109 : static struct GetContext *gc_head;
110 :
111 : /**
112 : * Tail of DLL of suspended GET requests.
113 : */
114 : static struct GetContext *gc_tail;
115 :
116 :
117 : void
118 0 : TEH_purses_get_cleanup ()
119 : {
120 : struct GetContext *gc;
121 :
122 0 : while (NULL != (gc = gc_head))
123 : {
124 0 : GNUNET_CONTAINER_DLL_remove (gc_head,
125 : gc_tail,
126 : gc);
127 0 : if (gc->suspended)
128 : {
129 0 : gc->suspended = false;
130 0 : MHD_resume_connection (gc->connection);
131 : }
132 : }
133 0 : }
134 :
135 :
136 : /**
137 : * Function called once a connection is done to
138 : * clean up the `struct GetContext` state.
139 : *
140 : * @param rc context to clean up for
141 : */
142 : static void
143 0 : gc_cleanup (struct TEH_RequestContext *rc)
144 : {
145 0 : struct GetContext *gc = rc->rh_ctx;
146 :
147 0 : GNUNET_assert (! gc->suspended);
148 0 : if (NULL != gc->eh)
149 : {
150 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
151 : "Cancelling DB event listening\n");
152 0 : TEH_plugin->event_listen_cancel (TEH_plugin->cls,
153 : gc->eh);
154 0 : gc->eh = NULL;
155 : }
156 0 : GNUNET_free (gc);
157 0 : }
158 :
159 :
160 : /**
161 : * Function called on events received from Postgres.
162 : * Wakes up long pollers.
163 : *
164 : * @param cls the `struct TEH_RequestContext *`
165 : * @param extra additional event data provided
166 : * @param extra_size number of bytes in @a extra
167 : */
168 : static void
169 0 : db_event_cb (void *cls,
170 : const void *extra,
171 : size_t extra_size)
172 : {
173 0 : struct TEH_RequestContext *rc = cls;
174 0 : struct GetContext *gc = rc->rh_ctx;
175 : struct GNUNET_AsyncScopeSave old_scope;
176 :
177 : (void) extra;
178 : (void) extra_size;
179 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
180 : "Waking up on %p - %p - %s\n",
181 : rc,
182 : gc,
183 : gc->suspended ? "suspended" : "active");
184 0 : if (NULL == gc)
185 0 : return; /* event triggered while main transaction
186 : was still running */
187 0 : if (! gc->suspended)
188 0 : return; /* might get multiple wake-up events */
189 0 : gc->suspended = false;
190 0 : GNUNET_async_scope_enter (&rc->async_scope_id,
191 : &old_scope);
192 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
193 : "Resuming from long-polling on purse\n");
194 0 : TEH_check_invariants ();
195 0 : GNUNET_CONTAINER_DLL_remove (gc_head,
196 : gc_tail,
197 : gc);
198 0 : MHD_resume_connection (gc->connection);
199 0 : TALER_MHD_daemon_trigger ();
200 0 : TEH_check_invariants ();
201 0 : GNUNET_async_scope_restore (&old_scope);
202 : }
203 :
204 :
205 : MHD_RESULT
206 0 : TEH_handler_purses_get (struct TEH_RequestContext *rc,
207 : const char *const args[2])
208 : {
209 0 : struct GetContext *gc = rc->rh_ctx;
210 : MHD_RESULT res;
211 :
212 0 : if (NULL == gc)
213 : {
214 0 : gc = GNUNET_new (struct GetContext);
215 0 : rc->rh_ctx = gc;
216 0 : rc->rh_cleaner = &gc_cleanup;
217 0 : gc->connection = rc->connection;
218 0 : if (GNUNET_OK !=
219 0 : GNUNET_STRINGS_string_to_data (args[0],
220 : strlen (args[0]),
221 0 : &gc->purse_pub,
222 : sizeof (gc->purse_pub)))
223 : {
224 0 : GNUNET_break_op (0);
225 0 : return TALER_MHD_reply_with_error (rc->connection,
226 : MHD_HTTP_BAD_REQUEST,
227 : TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED,
228 : args[0]);
229 : }
230 0 : if (0 == strcmp (args[1],
231 : "merge"))
232 0 : gc->wait_for_merge = true;
233 0 : else if (0 == strcmp (args[1],
234 : "deposit"))
235 0 : gc->wait_for_merge = false;
236 : else
237 : {
238 0 : GNUNET_break_op (0);
239 0 : return TALER_MHD_reply_with_error (rc->connection,
240 : MHD_HTTP_BAD_REQUEST,
241 : TALER_EC_EXCHANGE_PURSES_INVALID_WAIT_TARGET,
242 0 : args[1]);
243 : }
244 :
245 : {
246 : const char *long_poll_timeout_ms;
247 :
248 : long_poll_timeout_ms
249 0 : = MHD_lookup_connection_value (rc->connection,
250 : MHD_GET_ARGUMENT_KIND,
251 : "timeout_ms");
252 0 : if (NULL != long_poll_timeout_ms)
253 : {
254 : unsigned int timeout_ms;
255 : char dummy;
256 : struct GNUNET_TIME_Relative timeout;
257 :
258 0 : if (1 != sscanf (long_poll_timeout_ms,
259 : "%u%c",
260 : &timeout_ms,
261 : &dummy))
262 : {
263 0 : GNUNET_break_op (0);
264 0 : return TALER_MHD_reply_with_error (rc->connection,
265 : MHD_HTTP_BAD_REQUEST,
266 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
267 : "timeout_ms (must be non-negative number)");
268 : }
269 0 : timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
270 : timeout_ms);
271 0 : gc->timeout = GNUNET_TIME_relative_to_absolute (timeout);
272 : }
273 : }
274 :
275 0 : if ( (GNUNET_TIME_absolute_is_future (gc->timeout)) &&
276 0 : (NULL == gc->eh) )
277 : {
278 0 : struct TALER_PurseEventP rep = {
279 0 : .header.size = htons (sizeof (rep)),
280 0 : .header.type = htons (
281 0 : gc->wait_for_merge
282 : ? TALER_DBEVENT_EXCHANGE_PURSE_MERGED
283 : : TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
284 : .purse_pub = gc->purse_pub
285 : };
286 :
287 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
288 : "Starting DB event listening on purse %s\n",
289 : TALER_B2S (&gc->purse_pub));
290 0 : gc->eh = TEH_plugin->event_listen (
291 0 : TEH_plugin->cls,
292 : GNUNET_TIME_absolute_get_remaining (gc->timeout),
293 : &rep.header,
294 : &db_event_cb,
295 : rc);
296 0 : if (NULL == gc->eh)
297 : {
298 0 : GNUNET_break (0);
299 0 : gc->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
300 : }
301 : }
302 : } /* end first-time initialization */
303 :
304 : {
305 : enum GNUNET_DB_QueryStatus qs;
306 :
307 0 : qs = TEH_plugin->select_purse (TEH_plugin->cls,
308 0 : &gc->purse_pub,
309 : &gc->purse_expiration,
310 : &gc->amount,
311 : &gc->deposited,
312 : &gc->h_contract,
313 : &gc->merge_timestamp);
314 0 : switch (qs)
315 : {
316 0 : case GNUNET_DB_STATUS_HARD_ERROR:
317 0 : GNUNET_break (0);
318 0 : return TALER_MHD_reply_with_error (rc->connection,
319 : MHD_HTTP_INTERNAL_SERVER_ERROR,
320 : TALER_EC_GENERIC_DB_FETCH_FAILED,
321 : "select_purse");
322 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
323 0 : GNUNET_break (0);
324 0 : return TALER_MHD_reply_with_error (rc->connection,
325 : MHD_HTTP_INTERNAL_SERVER_ERROR,
326 : TALER_EC_GENERIC_DB_FETCH_FAILED,
327 : "select_purse");
328 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
329 0 : return TALER_MHD_reply_with_error (rc->connection,
330 : MHD_HTTP_NOT_FOUND,
331 : TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
332 : NULL);
333 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
334 0 : break; /* handled below */
335 : }
336 0 : if (GNUNET_TIME_absolute_cmp (gc->timeout,
337 : >,
338 : gc->purse_expiration.abs_time))
339 : {
340 : /* Timeout too high, need to replace event handler */
341 0 : struct TALER_PurseEventP rep = {
342 0 : .header.size = htons (sizeof (rep)),
343 0 : .header.type = htons (
344 0 : gc->wait_for_merge
345 : ? TALER_DBEVENT_EXCHANGE_PURSE_MERGED
346 : : TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
347 : .purse_pub = gc->purse_pub
348 : };
349 : struct GNUNET_DB_EventHandler *eh2;
350 :
351 0 : gc->timeout = gc->purse_expiration.abs_time;
352 0 : eh2 = TEH_plugin->event_listen (
353 0 : TEH_plugin->cls,
354 : GNUNET_TIME_absolute_get_remaining (gc->timeout),
355 : &rep.header,
356 : &db_event_cb,
357 : rc);
358 0 : if (NULL == eh2)
359 : {
360 0 : GNUNET_break (0);
361 0 : gc->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
362 : }
363 0 : TEH_plugin->event_listen_cancel (TEH_plugin->cls,
364 : gc->eh);
365 0 : gc->eh = eh2;
366 : }
367 : }
368 0 : if (GNUNET_TIME_absolute_is_past (gc->purse_expiration.abs_time))
369 : {
370 0 : return TALER_MHD_reply_with_error (rc->connection,
371 : MHD_HTTP_GONE,
372 : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
373 : GNUNET_TIME_timestamp2s (
374 : gc->purse_expiration));
375 : }
376 :
377 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
378 : "Deposited amount is %s\n",
379 : TALER_amount2s (&gc->deposited));
380 0 : if (GNUNET_TIME_absolute_is_future (gc->timeout) &&
381 0 : ( ((gc->wait_for_merge) &&
382 0 : GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time)) ||
383 0 : ((! gc->wait_for_merge) &&
384 : (0 <
385 0 : TALER_amount_cmp (&gc->amount,
386 0 : &gc->deposited))) ) )
387 : {
388 0 : gc->suspended = true;
389 0 : GNUNET_CONTAINER_DLL_insert (gc_head,
390 : gc_tail,
391 : gc);
392 0 : MHD_suspend_connection (gc->connection);
393 0 : return MHD_YES;
394 : }
395 :
396 : {
397 0 : struct GNUNET_TIME_Timestamp dt = GNUNET_TIME_timestamp_get ();
398 : struct TALER_ExchangePublicKeyP exchange_pub;
399 : struct TALER_ExchangeSignatureP exchange_sig;
400 : enum TALER_ErrorCode ec;
401 :
402 0 : if (GNUNET_TIME_timestamp_cmp (dt,
403 : >,
404 : gc->purse_expiration))
405 0 : dt = gc->purse_expiration;
406 0 : if (0 <
407 0 : TALER_amount_cmp (&gc->amount,
408 0 : &gc->deposited))
409 0 : dt = GNUNET_TIME_UNIT_ZERO_TS;
410 0 : if (TALER_EC_NONE !=
411 0 : (ec = TALER_exchange_online_purse_status_sign (
412 : &TEH_keys_exchange_sign_,
413 : gc->merge_timestamp,
414 : dt,
415 0 : &gc->deposited,
416 : &exchange_pub,
417 : &exchange_sig)))
418 0 : res = TALER_MHD_reply_with_ec (rc->connection,
419 : ec,
420 : NULL);
421 : else
422 0 : res = TALER_MHD_REPLY_JSON_PACK (
423 : rc->connection,
424 : MHD_HTTP_OK,
425 : TALER_JSON_pack_amount ("balance",
426 : &gc->deposited),
427 : GNUNET_JSON_pack_data_auto ("exchange_sig",
428 : &exchange_sig),
429 : GNUNET_JSON_pack_data_auto ("exchange_pub",
430 : &exchange_pub),
431 : GNUNET_JSON_pack_allow_null (
432 : GNUNET_JSON_pack_timestamp ("merge_timestamp",
433 : gc->merge_timestamp)),
434 : GNUNET_JSON_pack_allow_null (
435 : GNUNET_JSON_pack_timestamp ("deposit_timestamp",
436 : dt))
437 : );
438 : }
439 0 : return res;
440 : }
441 :
442 :
443 : /* end of taler-exchange-httpd_purses_get.c */
|