Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2024 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 "taler/platform.h"
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <jansson.h>
26 : #include "taler/taler_mhd_lib.h"
27 : #include "taler/taler_json_lib.h"
28 : #include "taler/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 : * Our request context.
56 : */
57 : struct TEH_RequestContext *rc;
58 :
59 : /**
60 : * Subscription for the database event we are waiting for.
61 : */
62 : struct GNUNET_DB_EventHandler *eh;
63 :
64 : /**
65 : * When will this request time out?
66 : */
67 : struct GNUNET_TIME_Absolute timeout;
68 :
69 : /**
70 : * Public key of the reserve the inquiry is about.
71 : */
72 : struct TALER_ReservePublicKeyP reserve_pub;
73 :
74 : /**
75 : * Balance of the reserve, set in the callback.
76 : */
77 : struct TALER_Amount balance;
78 :
79 : /**
80 : * Last origin account of the reserve, NULL if only
81 : * P2P payments were made.
82 : */
83 : struct TALER_FullPayto origin_account;
84 :
85 : /**
86 : * True if we are still suspended.
87 : */
88 : bool suspended;
89 :
90 : };
91 :
92 :
93 : /**
94 : * Head of list of requests in long polling.
95 : */
96 : static struct ReservePoller *rp_head;
97 :
98 : /**
99 : * Tail of list of requests in long polling.
100 : */
101 : static struct ReservePoller *rp_tail;
102 :
103 :
104 : void
105 21 : TEH_reserves_get_cleanup ()
106 : {
107 21 : for (struct ReservePoller *rp = rp_head;
108 21 : NULL != rp;
109 0 : rp = rp->next)
110 : {
111 0 : if (rp->suspended)
112 : {
113 0 : rp->suspended = false;
114 0 : MHD_resume_connection (rp->connection);
115 : }
116 : }
117 21 : }
118 :
119 :
120 : /**
121 : * Function called once a connection is done to
122 : * clean up the `struct ReservePoller` state.
123 : *
124 : * @param rc context to clean up for
125 : */
126 : static void
127 34 : rp_cleanup (struct TEH_RequestContext *rc)
128 : {
129 34 : struct ReservePoller *rp = rc->rh_ctx;
130 :
131 34 : GNUNET_assert (! rp->suspended);
132 34 : if (NULL != rp->eh)
133 : {
134 11 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
135 : "Cancelling DB event listening on cleanup (odd unless during shutdown)\n");
136 11 : TEH_plugin->event_listen_cancel (TEH_plugin->cls,
137 : rp->eh);
138 11 : rp->eh = NULL;
139 : }
140 34 : GNUNET_CONTAINER_DLL_remove (rp_head,
141 : rp_tail,
142 : rp);
143 34 : GNUNET_free (rp->origin_account.full_payto);
144 34 : GNUNET_free (rp);
145 34 : }
146 :
147 :
148 : /**
149 : * Function called on events received from Postgres.
150 : * Wakes up long pollers.
151 : *
152 : * @param cls the `struct TEH_RequestContext *`
153 : * @param extra additional event data provided
154 : * @param extra_size number of bytes in @a extra
155 : */
156 : static void
157 9 : db_event_cb (void *cls,
158 : const void *extra,
159 : size_t extra_size)
160 : {
161 9 : struct ReservePoller *rp = cls;
162 : struct GNUNET_AsyncScopeSave old_scope;
163 :
164 : (void) extra;
165 : (void) extra_size;
166 9 : if (! rp->suspended)
167 0 : return; /* might get multiple wake-up events */
168 9 : GNUNET_async_scope_enter (&rp->rc->async_scope_id,
169 : &old_scope);
170 9 : TEH_check_invariants ();
171 9 : rp->suspended = false;
172 9 : MHD_resume_connection (rp->connection);
173 9 : TALER_MHD_daemon_trigger ();
174 9 : TEH_check_invariants ();
175 9 : GNUNET_async_scope_restore (&old_scope);
176 : }
177 :
178 :
179 : MHD_RESULT
180 43 : TEH_handler_reserves_get (
181 : struct TEH_RequestContext *rc,
182 : const struct TALER_ReservePublicKeyP *reserve_pub)
183 : {
184 43 : struct ReservePoller *rp = rc->rh_ctx;
185 :
186 43 : if (NULL == rp)
187 : {
188 34 : rp = GNUNET_new (struct ReservePoller);
189 34 : rp->connection = rc->connection;
190 34 : rp->rc = rc;
191 34 : rc->rh_ctx = rp;
192 34 : rc->rh_cleaner = &rp_cleanup;
193 34 : GNUNET_CONTAINER_DLL_insert (rp_head,
194 : rp_tail,
195 : rp);
196 34 : rp->reserve_pub = *reserve_pub;
197 34 : TALER_MHD_parse_request_timeout (rc->connection,
198 : &rp->timeout);
199 : }
200 :
201 43 : if ( (GNUNET_TIME_absolute_is_future (rp->timeout)) &&
202 20 : (NULL == rp->eh) )
203 : {
204 11 : struct TALER_ReserveEventP rep = {
205 11 : .header.size = htons (sizeof (rep)),
206 11 : .header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
207 : .reserve_pub = rp->reserve_pub
208 : };
209 :
210 11 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
211 : "Starting DB event listening until %s\n",
212 : GNUNET_TIME_absolute2s (rp->timeout));
213 22 : rp->eh = TEH_plugin->event_listen (
214 11 : TEH_plugin->cls,
215 : GNUNET_TIME_absolute_get_remaining (rp->timeout),
216 : &rep.header,
217 : &db_event_cb,
218 : rp);
219 : }
220 : {
221 : enum GNUNET_DB_QueryStatus qs;
222 :
223 43 : GNUNET_free (rp->origin_account.full_payto);
224 43 : qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
225 43 : &rp->reserve_pub,
226 : &rp->balance,
227 : &rp->origin_account);
228 43 : switch (qs)
229 : {
230 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
231 0 : GNUNET_break (0); /* single-shot query should never have soft-errors */
232 0 : return TALER_MHD_reply_with_error (rc->connection,
233 : MHD_HTTP_INTERNAL_SERVER_ERROR,
234 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
235 : "get_reserve_balance");
236 0 : case GNUNET_DB_STATUS_HARD_ERROR:
237 0 : GNUNET_break (0);
238 0 : return TALER_MHD_reply_with_error (rc->connection,
239 : MHD_HTTP_INTERNAL_SERVER_ERROR,
240 : TALER_EC_GENERIC_DB_FETCH_FAILED,
241 : "get_reserve_balance");
242 34 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
243 34 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
244 : "Got reserve balance of %s\n",
245 : TALER_amount2s (&rp->balance));
246 34 : return TALER_MHD_REPLY_JSON_PACK (
247 : rc->connection,
248 : MHD_HTTP_OK,
249 : GNUNET_JSON_pack_allow_null (
250 : GNUNET_JSON_pack_string (
251 : "last_origin",
252 : rp->origin_account.full_payto)),
253 : TALER_JSON_pack_amount ("balance",
254 : &rp->balance));
255 9 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
256 9 : if (! GNUNET_TIME_absolute_is_future (rp->timeout))
257 : {
258 0 : return TALER_MHD_reply_with_error (rc->connection,
259 : MHD_HTTP_NOT_FOUND,
260 : TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
261 : NULL);
262 : }
263 9 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
264 : "Long-polling on reserve for %s\n",
265 : GNUNET_STRINGS_relative_time_to_string (
266 : GNUNET_TIME_absolute_get_remaining (rp->timeout),
267 : true));
268 9 : rp->suspended = true;
269 9 : MHD_suspend_connection (rc->connection);
270 9 : return MHD_YES;
271 : }
272 : }
273 0 : GNUNET_break (0);
274 0 : return MHD_NO;
275 : }
276 :
277 :
278 : /* end of taler-exchange-httpd_reserves_get.c */
|