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 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_reserves_get.c
18 : * @brief Handle /reserves/$RESERVE_PUB GET requests
19 : * @author Florian Dold
20 : * @author Benedikt Mueller
21 : * @author Christian Grothoff
22 : */
23 : #include "platform.h"
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <jansson.h>
26 : #include "taler_mhd_lib.h"
27 : #include "taler_json_lib.h"
28 : #include "taler_dbevents.h"
29 : #include "taler-exchange-httpd_keys.h"
30 : #include "taler-exchange-httpd_reserves_get.h"
31 : #include "taler-exchange-httpd_responses.h"
32 :
33 :
34 : /**
35 : * Reserve GET request that is long-polling.
36 : */
37 : struct ReservePoller
38 : {
39 : /**
40 : * Kept in a DLL.
41 : */
42 : struct ReservePoller *next;
43 :
44 : /**
45 : * Kept in a DLL.
46 : */
47 : struct ReservePoller *prev;
48 :
49 : /**
50 : * Connection we are handling.
51 : */
52 : struct MHD_Connection *connection;
53 :
54 : /**
55 : * Subscription for the database event we are
56 : * waiting for.
57 : */
58 : struct GNUNET_DB_EventHandler *eh;
59 :
60 : /**
61 : * When will this request time out?
62 : */
63 : struct GNUNET_TIME_Absolute timeout;
64 :
65 : /**
66 : * True if we are still suspended.
67 : */
68 : bool suspended;
69 :
70 : };
71 :
72 :
73 : /**
74 : * Head of list of requests in long polling.
75 : */
76 : static struct ReservePoller *rp_head;
77 :
78 : /**
79 : * Tail of list of requests in long polling.
80 : */
81 : static struct ReservePoller *rp_tail;
82 :
83 :
84 : void
85 0 : TEH_reserves_get_cleanup ()
86 : {
87 : struct ReservePoller *rp;
88 :
89 0 : while (NULL != (rp = rp_head))
90 : {
91 0 : GNUNET_CONTAINER_DLL_remove (rp_head,
92 : rp_tail,
93 : rp);
94 0 : if (rp->suspended)
95 : {
96 0 : rp->suspended = false;
97 0 : MHD_resume_connection (rp->connection);
98 : }
99 : }
100 0 : }
101 :
102 :
103 : /**
104 : * Function called once a connection is done to
105 : * clean up the `struct ReservePoller` state.
106 : *
107 : * @param rc context to clean up for
108 : */
109 : static void
110 0 : rp_cleanup (struct TEH_RequestContext *rc)
111 : {
112 0 : struct ReservePoller *rp = rc->rh_ctx;
113 :
114 0 : GNUNET_assert (! rp->suspended);
115 0 : if (NULL != rp->eh)
116 : {
117 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
118 : "Cancelling DB event listening\n");
119 0 : TEH_plugin->event_listen_cancel (TEH_plugin->cls,
120 : rp->eh);
121 0 : rp->eh = NULL;
122 : }
123 0 : GNUNET_free (rp);
124 0 : }
125 :
126 :
127 : /**
128 : * Function called on events received from Postgres.
129 : * Wakes up long pollers.
130 : *
131 : * @param cls the `struct TEH_RequestContext *`
132 : * @param extra additional event data provided
133 : * @param extra_size number of bytes in @a extra
134 : */
135 : static void
136 0 : db_event_cb (void *cls,
137 : const void *extra,
138 : size_t extra_size)
139 : {
140 0 : struct TEH_RequestContext *rc = cls;
141 0 : struct ReservePoller *rp = rc->rh_ctx;
142 : struct GNUNET_AsyncScopeSave old_scope;
143 :
144 : (void) extra;
145 : (void) extra_size;
146 0 : if (NULL == rp)
147 0 : return; /* event triggered while main transaction
148 : was still running */
149 0 : if (! rp->suspended)
150 0 : return; /* might get multiple wake-up events */
151 0 : rp->suspended = false;
152 0 : GNUNET_async_scope_enter (&rc->async_scope_id,
153 : &old_scope);
154 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
155 : "Resuming from long-polling on reserve\n");
156 0 : TEH_check_invariants ();
157 0 : GNUNET_CONTAINER_DLL_remove (rp_head,
158 : rp_tail,
159 : rp);
160 0 : MHD_resume_connection (rp->connection);
161 0 : TALER_MHD_daemon_trigger ();
162 0 : TEH_check_invariants ();
163 0 : GNUNET_async_scope_restore (&old_scope);
164 : }
165 :
166 :
167 : /**
168 : * Closure for #reserve_history_transaction.
169 : */
170 : struct ReserveHistoryContext
171 : {
172 : /**
173 : * Public key of the reserve the inquiry is about.
174 : */
175 : struct TALER_ReservePublicKeyP reserve_pub;
176 :
177 : /**
178 : * Balance of the reserve, set in the callback.
179 : */
180 : struct TALER_Amount balance;
181 :
182 : /**
183 : * Set to true if we did not find the reserve.
184 : */
185 : bool not_found;
186 : };
187 :
188 :
189 : /**
190 : * Function implementing /reserves/ GET transaction.
191 : * Execute a /reserves/ GET. Given the public key of a reserve,
192 : * return the associated transaction history. Runs the
193 : * transaction logic; IF it returns a non-error code, the transaction
194 : * logic MUST NOT queue a MHD response. IF it returns an hard error,
195 : * the transaction logic MUST queue a MHD response and set @a mhd_ret.
196 : * IF it returns the soft error code, the function MAY be called again
197 : * to retry and MUST not queue a MHD response.
198 : *
199 : * @param cls a `struct ReserveHistoryContext *`
200 : * @param connection MHD request which triggered the transaction
201 : * @param[out] mhd_ret set to MHD response status for @a connection,
202 : * if transaction failed (!)
203 : * @return transaction status
204 : */
205 : static enum GNUNET_DB_QueryStatus
206 0 : reserve_balance_transaction (void *cls,
207 : struct MHD_Connection *connection,
208 : MHD_RESULT *mhd_ret)
209 : {
210 0 : struct ReserveHistoryContext *rsc = cls;
211 : enum GNUNET_DB_QueryStatus qs;
212 :
213 0 : qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
214 0 : &rsc->reserve_pub,
215 : &rsc->balance);
216 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
217 : {
218 0 : GNUNET_break (0);
219 : *mhd_ret
220 0 : = TALER_MHD_reply_with_error (connection,
221 : MHD_HTTP_INTERNAL_SERVER_ERROR,
222 : TALER_EC_GENERIC_DB_FETCH_FAILED,
223 : "get_reserve_balance");
224 : }
225 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
226 0 : rsc->not_found = true;
227 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
228 0 : rsc->not_found = false;
229 0 : return qs;
230 : }
231 :
232 :
233 : MHD_RESULT
234 0 : TEH_handler_reserves_get (struct TEH_RequestContext *rc,
235 : const char *const args[1])
236 : {
237 : struct ReserveHistoryContext rsc;
238 0 : struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
239 0 : struct GNUNET_DB_EventHandler *eh = NULL;
240 :
241 0 : if (GNUNET_OK !=
242 0 : GNUNET_STRINGS_string_to_data (args[0],
243 : strlen (args[0]),
244 : &rsc.reserve_pub,
245 : sizeof (rsc.reserve_pub)))
246 : {
247 0 : GNUNET_break_op (0);
248 0 : return TALER_MHD_reply_with_error (rc->connection,
249 : MHD_HTTP_BAD_REQUEST,
250 : TALER_EC_MERCHANT_GENERIC_RESERVE_PUB_MALFORMED,
251 : args[0]);
252 : }
253 : {
254 : const char *long_poll_timeout_ms;
255 :
256 : long_poll_timeout_ms
257 0 : = MHD_lookup_connection_value (rc->connection,
258 : MHD_GET_ARGUMENT_KIND,
259 : "timeout_ms");
260 0 : if (NULL != long_poll_timeout_ms)
261 : {
262 : unsigned int timeout_ms;
263 : char dummy;
264 :
265 0 : if (1 != sscanf (long_poll_timeout_ms,
266 : "%u%c",
267 : &timeout_ms,
268 : &dummy))
269 : {
270 0 : GNUNET_break_op (0);
271 0 : return TALER_MHD_reply_with_error (rc->connection,
272 : MHD_HTTP_BAD_REQUEST,
273 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
274 : "timeout_ms (must be non-negative number)");
275 : }
276 0 : timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
277 : timeout_ms);
278 : }
279 : }
280 0 : if ( (! GNUNET_TIME_relative_is_zero (timeout)) &&
281 0 : (NULL == rc->rh_ctx) )
282 : {
283 0 : struct TALER_ReserveEventP rep = {
284 0 : .header.size = htons (sizeof (rep)),
285 0 : .header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
286 : .reserve_pub = rsc.reserve_pub
287 : };
288 :
289 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
290 : "Starting DB event listening\n");
291 0 : eh = TEH_plugin->event_listen (TEH_plugin->cls,
292 : timeout,
293 : &rep.header,
294 : &db_event_cb,
295 : rc);
296 : }
297 : {
298 : MHD_RESULT mhd_ret;
299 :
300 0 : if (GNUNET_OK !=
301 0 : TEH_DB_run_transaction (rc->connection,
302 : "get reserve balance",
303 : TEH_MT_REQUEST_OTHER,
304 : &mhd_ret,
305 : &reserve_balance_transaction,
306 : &rsc))
307 : {
308 0 : if (NULL != eh)
309 0 : TEH_plugin->event_listen_cancel (TEH_plugin->cls,
310 : eh);
311 0 : return mhd_ret;
312 : }
313 : }
314 : /* generate proper response */
315 0 : if (rsc.not_found)
316 : {
317 0 : struct ReservePoller *rp = rc->rh_ctx;
318 :
319 0 : if ( (NULL != rp) ||
320 0 : (GNUNET_TIME_relative_is_zero (timeout)) )
321 : {
322 0 : return TALER_MHD_reply_with_error (rc->connection,
323 : MHD_HTTP_NOT_FOUND,
324 : TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN,
325 : args[0]);
326 : }
327 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
328 : "Long-polling on reserve for %s\n",
329 : GNUNET_STRINGS_relative_time_to_string (timeout,
330 : GNUNET_YES));
331 0 : rp = GNUNET_new (struct ReservePoller);
332 0 : rp->connection = rc->connection;
333 0 : rp->timeout = GNUNET_TIME_relative_to_absolute (timeout);
334 0 : rp->eh = eh;
335 0 : rc->rh_ctx = rp;
336 0 : rc->rh_cleaner = &rp_cleanup;
337 0 : rp->suspended = true;
338 0 : GNUNET_CONTAINER_DLL_insert (rp_head,
339 : rp_tail,
340 : rp);
341 0 : MHD_suspend_connection (rc->connection);
342 0 : return MHD_YES;
343 : }
344 0 : if (NULL != eh)
345 0 : TEH_plugin->event_listen_cancel (TEH_plugin->cls,
346 : eh);
347 0 : return TALER_MHD_REPLY_JSON_PACK (
348 : rc->connection,
349 : MHD_HTTP_OK,
350 : TALER_JSON_pack_amount ("balance",
351 : &rsc.balance));
352 : }
353 :
354 :
355 : /* end of taler-exchange-httpd_reserves_get.c */
|