exchange_api_post-reserves-RESERVE_PUB-close.c (11093B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU 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 General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file lib/exchange_api_post-reserves-RESERVE_PUB-close.c 19 * @brief Implementation of the POST /reserves/$RESERVE_PUB/close requests 20 * @author Christian Grothoff 21 */ 22 #include <jansson.h> 23 #include <microhttpd.h> /* just for HTTP close codes */ 24 #include <gnunet/gnunet_util_lib.h> 25 #include <gnunet/gnunet_json_lib.h> 26 #include <gnunet/gnunet_curl_lib.h> 27 #include "taler/taler_json_lib.h" 28 #include "exchange_api_handle.h" 29 #include "taler/taler_signatures.h" 30 #include "exchange_api_curl_defaults.h" 31 32 33 /** 34 * @brief A POST /reserves/$RID/close Handle 35 */ 36 struct TALER_EXCHANGE_PostReservesCloseHandle 37 { 38 39 /** 40 * Reference to the execution context. 41 */ 42 struct GNUNET_CURL_Context *ctx; 43 44 /** 45 * Base URL of the exchange. 46 */ 47 char *base_url; 48 49 /** 50 * The url for this request, set during _start. 51 */ 52 char *url; 53 54 /** 55 * Context for #TEH_curl_easy_post(). Keeps the data that must 56 * persist for Curl to make the upload. 57 */ 58 struct TALER_CURL_PostContext post_ctx; 59 60 /** 61 * Handle for the request. 62 */ 63 struct GNUNET_CURL_Job *job; 64 65 /** 66 * Function to call with the result. 67 */ 68 TALER_EXCHANGE_PostReservesCloseCallback cb; 69 70 /** 71 * Closure for @a cb. 72 */ 73 TALER_EXCHANGE_POST_RESERVES_CLOSE_RESULT_CLOSURE *cb_cls; 74 75 /** 76 * Private key of the reserve. 77 */ 78 struct TALER_ReservePrivateKeyP reserve_priv; 79 80 /** 81 * Public key of the reserve we are querying. 82 */ 83 struct TALER_ReservePublicKeyP reserve_pub; 84 85 /** 86 * Target payto URI to send the reserve balance to (optional). 87 */ 88 struct TALER_FullPayto target_payto_uri; 89 90 /** 91 * When did we make the request. 92 */ 93 struct GNUNET_TIME_Timestamp ts; 94 95 }; 96 97 98 /** 99 * We received an #MHD_HTTP_OK close code. Handle the JSON 100 * response. 101 * 102 * @param prch handle of the request 103 * @param j JSON response 104 * @return #GNUNET_OK on success 105 */ 106 static enum GNUNET_GenericReturnValue 107 handle_reserves_close_ok (struct TALER_EXCHANGE_PostReservesCloseHandle *prch, 108 const json_t *j) 109 { 110 struct TALER_EXCHANGE_PostReservesCloseResponse rs = { 111 .hr.reply = j, 112 .hr.http_status = MHD_HTTP_OK, 113 }; 114 struct GNUNET_JSON_Specification spec[] = { 115 TALER_JSON_spec_amount_any ("wire_amount", 116 &rs.details.ok.wire_amount), 117 GNUNET_JSON_spec_end () 118 }; 119 120 if (GNUNET_OK != 121 GNUNET_JSON_parse (j, 122 spec, 123 NULL, 124 NULL)) 125 { 126 GNUNET_break_op (0); 127 return GNUNET_SYSERR; 128 } 129 prch->cb (prch->cb_cls, 130 &rs); 131 prch->cb = NULL; 132 GNUNET_JSON_parse_free (spec); 133 return GNUNET_OK; 134 } 135 136 137 /** 138 * Function called when we're done processing the 139 * HTTP /reserves/$RID/close request. 140 * 141 * @param cls the `struct TALER_EXCHANGE_PostReservesCloseHandle` 142 * @param response_code HTTP response code, 0 on error 143 * @param response parsed JSON result, NULL on error 144 */ 145 static void 146 handle_reserves_close_finished (void *cls, 147 long response_code, 148 const void *response) 149 { 150 struct TALER_EXCHANGE_PostReservesCloseHandle *prch = cls; 151 const json_t *j = response; 152 struct TALER_EXCHANGE_PostReservesCloseResponse rs = { 153 .hr.reply = j, 154 .hr.http_status = (unsigned int) response_code 155 }; 156 157 prch->job = NULL; 158 switch (response_code) 159 { 160 case 0: 161 rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 162 break; 163 case MHD_HTTP_OK: 164 if (GNUNET_OK != 165 handle_reserves_close_ok (prch, 166 j)) 167 { 168 GNUNET_break_op (0); 169 rs.hr.http_status = 0; 170 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 171 } 172 break; 173 case MHD_HTTP_BAD_REQUEST: 174 /* This should never happen, either us or the exchange is buggy 175 (or API version conflict); just pass JSON reply to the application */ 176 GNUNET_break (0); 177 rs.hr.ec = TALER_JSON_get_error_code (j); 178 rs.hr.hint = TALER_JSON_get_error_hint (j); 179 break; 180 case MHD_HTTP_FORBIDDEN: 181 /* This should never happen, either us or the exchange is buggy 182 (or API version conflict); just pass JSON reply to the application */ 183 GNUNET_break (0); 184 rs.hr.ec = TALER_JSON_get_error_code (j); 185 rs.hr.hint = TALER_JSON_get_error_hint (j); 186 break; 187 case MHD_HTTP_NOT_FOUND: 188 /* Nothing really to verify, this should never 189 happen, we should pass the JSON reply to the application */ 190 rs.hr.ec = TALER_JSON_get_error_code (j); 191 rs.hr.hint = TALER_JSON_get_error_hint (j); 192 break; 193 case MHD_HTTP_CONFLICT: 194 /* Insufficient balance to inquire for reserve close */ 195 rs.hr.ec = TALER_JSON_get_error_code (j); 196 rs.hr.hint = TALER_JSON_get_error_hint (j); 197 break; 198 case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: 199 rs.hr.ec = TALER_JSON_get_error_code (j); 200 rs.hr.hint = TALER_JSON_get_error_hint (j); 201 if (GNUNET_OK != 202 TALER_EXCHANGE_parse_451 (&rs.details.unavailable_for_legal_reasons, 203 j)) 204 { 205 GNUNET_break_op (0); 206 rs.hr.http_status = 0; 207 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 208 } 209 break; 210 case MHD_HTTP_INTERNAL_SERVER_ERROR: 211 /* Server had an internal issue; we should retry, but this API 212 leaves this to the application */ 213 rs.hr.ec = TALER_JSON_get_error_code (j); 214 rs.hr.hint = TALER_JSON_get_error_hint (j); 215 break; 216 default: 217 /* unexpected response code */ 218 GNUNET_break_op (0); 219 rs.hr.ec = TALER_JSON_get_error_code (j); 220 rs.hr.hint = TALER_JSON_get_error_hint (j); 221 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 222 "Unexpected response code %u/%d for reserves close\n", 223 (unsigned int) response_code, 224 (int) rs.hr.ec); 225 break; 226 } 227 if (NULL != prch->cb) 228 { 229 prch->cb (prch->cb_cls, 230 &rs); 231 prch->cb = NULL; 232 } 233 TALER_EXCHANGE_post_reserves_close_cancel (prch); 234 } 235 236 237 struct TALER_EXCHANGE_PostReservesCloseHandle * 238 TALER_EXCHANGE_post_reserves_close_create ( 239 struct GNUNET_CURL_Context *ctx, 240 const char *url, 241 const struct TALER_ReservePrivateKeyP *reserve_priv) 242 { 243 struct TALER_EXCHANGE_PostReservesCloseHandle *prch; 244 245 prch = GNUNET_new (struct TALER_EXCHANGE_PostReservesCloseHandle); 246 prch->ctx = ctx; 247 prch->base_url = GNUNET_strdup (url); 248 prch->reserve_priv = *reserve_priv; 249 GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, 250 &prch->reserve_pub.eddsa_pub); 251 prch->target_payto_uri.full_payto = NULL; 252 return prch; 253 } 254 255 256 enum GNUNET_GenericReturnValue 257 TALER_EXCHANGE_post_reserves_close_set_options_ ( 258 struct TALER_EXCHANGE_PostReservesCloseHandle *prch, 259 unsigned int num_options, 260 const struct TALER_EXCHANGE_PostReservesCloseOptionValue options[]) 261 { 262 for (unsigned int i = 0; i < num_options; i++) 263 { 264 const struct TALER_EXCHANGE_PostReservesCloseOptionValue *opt = &options[i]; 265 266 switch (opt->option) 267 { 268 case TALER_EXCHANGE_POST_RESERVES_CLOSE_OPTION_END: 269 return GNUNET_OK; 270 case TALER_EXCHANGE_POST_RESERVES_CLOSE_OPTION_PAYTO_URI: 271 GNUNET_free (prch->target_payto_uri.full_payto); 272 prch->target_payto_uri.full_payto 273 = GNUNET_strdup (opt->details.payto_uri.full_payto); 274 break; 275 } 276 } 277 return GNUNET_OK; 278 } 279 280 281 enum TALER_ErrorCode 282 TALER_EXCHANGE_post_reserves_close_start ( 283 struct TALER_EXCHANGE_PostReservesCloseHandle *prch, 284 TALER_EXCHANGE_PostReservesCloseCallback cb, 285 TALER_EXCHANGE_POST_RESERVES_CLOSE_RESULT_CLOSURE *cb_cls) 286 { 287 CURL *eh; 288 char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; 289 struct TALER_ReserveSignatureP reserve_sig; 290 struct TALER_FullPaytoHashP h_payto; 291 json_t *close_obj; 292 293 prch->cb = cb; 294 prch->cb_cls = cb_cls; 295 prch->ts = GNUNET_TIME_timestamp_get (); 296 { 297 char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; 298 char *end; 299 300 end = GNUNET_STRINGS_data_to_string ( 301 &prch->reserve_pub, 302 sizeof (prch->reserve_pub), 303 pub_str, 304 sizeof (pub_str)); 305 *end = '\0'; 306 GNUNET_snprintf (arg_str, 307 sizeof (arg_str), 308 "reserves/%s/close", 309 pub_str); 310 } 311 prch->url = TALER_url_join (prch->base_url, 312 arg_str, 313 NULL); 314 if (NULL == prch->url) 315 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 316 if (NULL != prch->target_payto_uri.full_payto) 317 TALER_full_payto_hash (prch->target_payto_uri, 318 &h_payto); 319 TALER_wallet_reserve_close_sign (prch->ts, 320 (NULL != prch->target_payto_uri.full_payto) 321 ? &h_payto 322 : NULL, 323 &prch->reserve_priv, 324 &reserve_sig); 325 close_obj = GNUNET_JSON_PACK ( 326 GNUNET_JSON_pack_allow_null ( 327 TALER_JSON_pack_full_payto ("payto_uri", 328 prch->target_payto_uri)), 329 GNUNET_JSON_pack_timestamp ("request_timestamp", 330 prch->ts), 331 GNUNET_JSON_pack_data_auto ("reserve_sig", 332 &reserve_sig)); 333 eh = TALER_EXCHANGE_curl_easy_get_ (prch->url); 334 if ( (NULL == eh) || 335 (GNUNET_OK != 336 TALER_curl_easy_post (&prch->post_ctx, 337 eh, 338 close_obj)) ) 339 { 340 GNUNET_break (0); 341 if (NULL != eh) 342 curl_easy_cleanup (eh); 343 json_decref (close_obj); 344 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 345 } 346 json_decref (close_obj); 347 prch->job = GNUNET_CURL_job_add2 (prch->ctx, 348 eh, 349 prch->post_ctx.headers, 350 &handle_reserves_close_finished, 351 prch); 352 if (NULL == prch->job) 353 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 354 return TALER_EC_NONE; 355 } 356 357 358 void 359 TALER_EXCHANGE_post_reserves_close_cancel ( 360 struct TALER_EXCHANGE_PostReservesCloseHandle *prch) 361 { 362 if (NULL != prch->job) 363 { 364 GNUNET_CURL_job_cancel (prch->job); 365 prch->job = NULL; 366 } 367 TALER_curl_easy_post_finished (&prch->post_ctx); 368 GNUNET_free (prch->url); 369 GNUNET_free (prch->base_url); 370 GNUNET_free (prch->target_payto_uri.full_payto); 371 GNUNET_free (prch); 372 } 373 374 375 /* end of exchange_api_post-reserves-RESERVE_PUB-close.c */