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