donau_api_batch_issue_receipts.c (11378B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2024 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it 6 under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, see 17 <http://www.gnu.org/licenses/> 18 */ 19 20 /** 21 * @file lib/donau_api_batch_issue_receipts.c 22 * @brief Implementation of the "handle" component of the donau's HTTP API 23 * @author Lukas Matyja 24 */ 25 #include <gnunet/gnunet_curl_lib.h> 26 #include <taler/taler_json_lib.h> 27 #include <taler/taler_curl_lib.h> 28 #include "donau_service.h" 29 #include "donau_util.h" 30 #include "donau_api_curl_defaults.h" 31 #include "donau_json_lib.h" 32 33 34 /** 35 * Handle for a POST /batch-issue/$CHARITY_ID request. 36 */ 37 struct DONAU_BatchIssueReceiptHandle 38 { 39 /** 40 * The url for the /batch-issue/$CHARITY_ID request. 41 */ 42 char *url; 43 44 /** 45 * Minor context that holds body and headers. 46 */ 47 struct TALER_CURL_PostContext post_ctx; 48 49 /** 50 * Entry for this request with the `struct GNUNET_CURL_Context`. 51 */ 52 struct GNUNET_CURL_Job *job; 53 54 /** 55 * Function to call with the result. 56 */ 57 DONAU_BatchIssueReceiptsCallback cb; 58 59 /** 60 * BUDI-key-pair signature. 61 */ 62 struct DONAU_CharitySignatureP charity_sig; 63 64 /** 65 * number of requested signatures. 66 */ 67 size_t num_blinded_sigs; 68 69 /** 70 * Closure to pass to @e cb. 71 */ 72 void *cb_cls; 73 74 /** 75 * Reference to the execution context. 76 */ 77 struct GNUNET_CURL_Context *ctx; 78 79 }; 80 81 82 /** 83 * Decode the JSON in @a resp_obj from the /batch-issue/$CHARITY_ID response 84 * and store the data in the @a biresp. 85 * 86 * @param[in] resp_obj JSON object to parse 87 * @param[in] birh contains the callback function 88 * @param[out] biresp where to store the results we decoded 89 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error 90 * (malformed JSON) 91 */ 92 static enum GNUNET_GenericReturnValue 93 handle_batch_issue_ok (const json_t *resp_obj, 94 struct DONAU_BatchIssueReceiptHandle *birh, 95 struct DONAU_BatchIssueResponse *biresp) 96 { 97 const json_t *j_blind_signatures; 98 struct GNUNET_JSON_Specification spec[] = { 99 TALER_JSON_spec_amount_any ("issued_amount", 100 &biresp->details.ok.issued_amount), 101 GNUNET_JSON_spec_array_const ("blind_signatures", 102 &j_blind_signatures), 103 GNUNET_JSON_spec_end () 104 }; 105 106 if (GNUNET_OK != 107 GNUNET_JSON_parse (resp_obj, 108 spec, 109 NULL, 110 NULL)) 111 { 112 GNUNET_break_op (0); 113 return GNUNET_SYSERR; 114 } 115 if ( (NULL == j_blind_signatures) || 116 (! json_is_array (j_blind_signatures)) ) 117 { 118 GNUNET_break (0); 119 return GNUNET_SYSERR; 120 } 121 biresp->details.ok.num_blinded_sigs 122 = json_array_size (j_blind_signatures); 123 if (biresp->details.ok.num_blinded_sigs != birh->num_blinded_sigs) 124 { 125 /* The Donau must return exactly as many signatures as we requested; 126 anything else would make the array indexing below go out of bounds. */ 127 GNUNET_break_op (0); 128 return GNUNET_SYSERR; 129 } 130 biresp->details.ok.blinded_sigs = 131 GNUNET_new_array (birh->num_blinded_sigs, 132 struct DONAU_BlindedDonationUnitSignature); 133 { 134 size_t index; 135 json_t *du_sig_obj; 136 137 json_array_foreach (j_blind_signatures, 138 index, 139 du_sig_obj) 140 { 141 struct GNUNET_JSON_Specification ispec[] = { 142 DONAU_JSON_spec_blinded_donation_unit_sig ( 143 "blinded_signature", 144 &biresp->details.ok.blinded_sigs[index]), 145 GNUNET_JSON_spec_end () 146 }; 147 148 if (GNUNET_OK != 149 GNUNET_JSON_parse (du_sig_obj, 150 ispec, 151 NULL, 152 NULL)) 153 { 154 GNUNET_break_op (0); 155 for (size_t k = 0; k < index; k++) 156 GNUNET_CRYPTO_blinded_sig_decref ( 157 biresp->details.ok.blinded_sigs[k].blinded_sig); 158 GNUNET_free (biresp->details.ok.blinded_sigs); 159 return GNUNET_SYSERR; 160 } 161 } 162 } 163 birh->cb (birh->cb_cls, 164 biresp); 165 birh->cb = NULL; 166 for (unsigned int i=0; i<biresp->details.ok.num_blinded_sigs; i++) 167 { 168 struct DONAU_BlindedDonationUnitSignature *sig 169 = &biresp->details.ok.blinded_sigs[i]; 170 171 GNUNET_CRYPTO_blinded_sig_decref (sig->blinded_sig); 172 } 173 GNUNET_free (biresp->details.ok.blinded_sigs); 174 return GNUNET_OK; 175 } 176 177 178 /** 179 * Transform issue receipt request into JSON. 180 * 181 * @param num_bkp number of budi-key-pairs in @a bkp 182 * @param bkp budi-key-pair array 183 * @param year corresponding year 184 * @param charity_sig signature from charity over @a bkp 185 */ 186 static json_t * 187 issue_receipt_body_to_json ( 188 const unsigned int num_bkp, 189 const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkp, 190 const uint64_t year, 191 const struct DONAU_CharitySignatureP *charity_sig) 192 { 193 json_t *budikeypairs = json_array (); 194 195 GNUNET_assert (NULL != budikeypairs); 196 for (size_t i = 0; i < num_bkp; i++) 197 { 198 json_t *budikeypair = GNUNET_JSON_PACK ( 199 GNUNET_JSON_pack_data_auto ("h_donation_unit_pub", 200 &bkp[i].h_donation_unit_pub.hash), 201 DONAU_JSON_pack_blinded_donation_identifier ("blinded_udi", 202 &bkp[i].blinded_udi)); 203 GNUNET_assert (0 == 204 json_array_append_new (budikeypairs, 205 budikeypair)); 206 } 207 return GNUNET_JSON_PACK ( 208 GNUNET_JSON_pack_array_steal ("budikeypairs", 209 budikeypairs), 210 GNUNET_JSON_pack_data_auto ("charity_sig", 211 &charity_sig->eddsa_sig), 212 GNUNET_JSON_pack_uint64 ("year", 213 year)); 214 } 215 216 217 /** 218 * Function called when we're done processing the 219 * HTTP POST /batch-issue/$CHARITY_ID request. 220 * 221 * @param cls the `struct KeysRequest` 222 * @param response_code HTTP response code, 0 on error 223 * @param resp_obj parsed JSON result, NULL on error 224 */ 225 static void 226 handle_batch_issue_finished (void *cls, 227 long response_code, 228 const void *resp_obj) 229 { 230 struct DONAU_BatchIssueReceiptHandle *birh = cls; 231 const json_t *j = resp_obj; 232 struct DONAU_BatchIssueResponse biresp = { 233 .hr.reply = j, 234 .hr.http_status = (unsigned int) response_code 235 }; 236 237 birh->job = NULL; 238 switch (response_code) 239 { 240 case MHD_HTTP_OK: 241 if (GNUNET_OK != 242 handle_batch_issue_ok (j, 243 birh, 244 &biresp)) 245 { 246 biresp.hr.http_status = 0; 247 biresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 248 } 249 break; 250 case MHD_HTTP_NO_CONTENT: 251 biresp.hr.ec = TALER_JSON_get_error_code (j); 252 biresp.hr.hint = TALER_JSON_get_error_hint (j); 253 break; 254 case MHD_HTTP_FORBIDDEN: 255 // invalid charity signature 256 biresp.hr.ec = TALER_JSON_get_error_code (j); 257 biresp.hr.hint = TALER_JSON_get_error_hint (j); 258 break; 259 case MHD_HTTP_NOT_FOUND: 260 // one or more donation units are not known to the Donau 261 biresp.hr.ec = TALER_JSON_get_error_code (j); 262 biresp.hr.hint = TALER_JSON_get_error_hint (j); 263 break; 264 case MHD_HTTP_CONTENT_TOO_LARGE: 265 biresp.hr.ec = TALER_JSON_get_error_code (j); 266 biresp.hr.hint = TALER_JSON_get_error_hint (j); 267 break; 268 case MHD_HTTP_CONFLICT: 269 // Donation limit is not sufficient 270 biresp.hr.ec = TALER_JSON_get_error_code (j); 271 biresp.hr.hint = TALER_JSON_get_error_hint (j); 272 break; 273 case MHD_HTTP_GONE: 274 // donation unit key is no longer valid 275 biresp.hr.ec = TALER_JSON_get_error_code (j); 276 biresp.hr.hint = TALER_JSON_get_error_hint (j); 277 break; 278 case MHD_HTTP_TOO_EARLY: 279 // donation unit key is not yet valid 280 biresp.hr.ec = TALER_JSON_get_error_code (j); 281 biresp.hr.hint = TALER_JSON_get_error_hint (j); 282 break; 283 default: 284 /* unexpected response code */ 285 GNUNET_break_op (0); 286 biresp.hr.ec = TALER_JSON_get_error_code (j); 287 biresp.hr.hint = TALER_JSON_get_error_hint (j); 288 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 289 "Unexpected response code %u/%d for POST %s\n", 290 (unsigned int) response_code, 291 (int) biresp.hr.ec, 292 birh->url); 293 break; 294 } 295 if (NULL != birh->cb) 296 { 297 birh->cb (birh->cb_cls, 298 &biresp); 299 birh->cb = NULL; 300 } 301 DONAU_charity_issue_receipt_cancel (birh); 302 } 303 304 305 struct DONAU_BatchIssueReceiptHandle * 306 DONAU_charity_issue_receipt ( 307 struct GNUNET_CURL_Context *ctx, 308 const char *url, 309 const struct DONAU_CharityPrivateKeyP *charity_priv, 310 const uint64_t charity_id, 311 const uint64_t year, 312 const size_t num_bkp, 313 const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkp, 314 DONAU_BatchIssueReceiptsCallback cb, 315 void *cb_cls) 316 { 317 CURL *eh; 318 json_t *body; 319 char arg_str[sizeof (charity_id) * 2 + 32]; 320 struct DONAU_BatchIssueReceiptHandle *birh; 321 322 birh = GNUNET_new (struct DONAU_BatchIssueReceiptHandle); 323 birh->num_blinded_sigs = num_bkp; 324 DONAU_charity_bkp_sign (num_bkp, bkp, 325 charity_priv, 326 &birh->charity_sig); 327 birh->cb = cb; 328 birh->cb_cls = cb_cls; 329 birh->ctx = ctx; 330 GNUNET_snprintf (arg_str, 331 sizeof (arg_str), 332 "batch-issue/%llu", 333 (unsigned long long) charity_id); 334 birh->url = TALER_url_join (url, 335 arg_str, 336 NULL); 337 if (NULL == birh->url) 338 { 339 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 340 "Could not construct request URL.\n"); 341 GNUNET_free (birh); 342 return NULL; 343 } 344 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 345 "issue_receipts_with_URL `%s'.\n", 346 birh->url); 347 body = issue_receipt_body_to_json (num_bkp, 348 bkp, 349 year, 350 &birh->charity_sig); 351 eh = DONAU_curl_easy_get_ (birh->url); 352 if ( (NULL == eh) || 353 (GNUNET_OK != 354 TALER_curl_easy_post (&birh->post_ctx, 355 eh, 356 body)) ) 357 { 358 GNUNET_break (0); 359 if (NULL != eh) 360 curl_easy_cleanup (eh); 361 json_decref (body); 362 GNUNET_free (birh->url); 363 GNUNET_free (birh); 364 return NULL; 365 } 366 json_decref (body); 367 birh->job = GNUNET_CURL_job_add2 (ctx, 368 eh, 369 birh->post_ctx.headers, 370 &handle_batch_issue_finished, 371 birh); 372 return birh; 373 } 374 375 376 void 377 DONAU_charity_issue_receipt_cancel ( 378 struct DONAU_BatchIssueReceiptHandle *birh) 379 { 380 if (NULL != birh->job) 381 { 382 GNUNET_CURL_job_cancel (birh->job); 383 birh->job = NULL; 384 } 385 TALER_curl_easy_post_finished (&birh->post_ctx); 386 GNUNET_free (birh->url); 387 GNUNET_free (birh); 388 }