donau_api_batch_issue_receipts.c (11464B)
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 // wrong year for the donation unit, or donation limit is not 270 // sufficient (the latter was a 400 before protocol v2) 271 biresp.hr.ec = TALER_JSON_get_error_code (j); 272 biresp.hr.hint = TALER_JSON_get_error_hint (j); 273 break; 274 case MHD_HTTP_GONE: 275 // donation unit key is no longer valid 276 biresp.hr.ec = TALER_JSON_get_error_code (j); 277 biresp.hr.hint = TALER_JSON_get_error_hint (j); 278 break; 279 case MHD_HTTP_TOO_EARLY: 280 // donation unit key is not yet valid 281 biresp.hr.ec = TALER_JSON_get_error_code (j); 282 biresp.hr.hint = TALER_JSON_get_error_hint (j); 283 break; 284 default: 285 /* unexpected response code */ 286 GNUNET_break_op (0); 287 biresp.hr.ec = TALER_JSON_get_error_code (j); 288 biresp.hr.hint = TALER_JSON_get_error_hint (j); 289 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 290 "Unexpected response code %u/%d for POST %s\n", 291 (unsigned int) response_code, 292 (int) biresp.hr.ec, 293 birh->url); 294 break; 295 } 296 if (NULL != birh->cb) 297 { 298 birh->cb (birh->cb_cls, 299 &biresp); 300 birh->cb = NULL; 301 } 302 DONAU_charity_issue_receipt_cancel (birh); 303 } 304 305 306 struct DONAU_BatchIssueReceiptHandle * 307 DONAU_charity_issue_receipt ( 308 struct GNUNET_CURL_Context *ctx, 309 const char *url, 310 const struct DONAU_CharityPrivateKeyP *charity_priv, 311 const uint64_t charity_id, 312 const uint64_t year, 313 const size_t num_bkp, 314 const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkp, 315 DONAU_BatchIssueReceiptsCallback cb, 316 void *cb_cls) 317 { 318 CURL *eh; 319 json_t *body; 320 char arg_str[sizeof (charity_id) * 2 + 32]; 321 struct DONAU_BatchIssueReceiptHandle *birh; 322 323 birh = GNUNET_new (struct DONAU_BatchIssueReceiptHandle); 324 birh->num_blinded_sigs = num_bkp; 325 DONAU_charity_bkp_sign (num_bkp, bkp, 326 charity_priv, 327 &birh->charity_sig); 328 birh->cb = cb; 329 birh->cb_cls = cb_cls; 330 birh->ctx = ctx; 331 GNUNET_snprintf (arg_str, 332 sizeof (arg_str), 333 "batch-issue/%llu", 334 (unsigned long long) charity_id); 335 birh->url = TALER_url_join (url, 336 arg_str, 337 NULL); 338 if (NULL == birh->url) 339 { 340 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 341 "Could not construct request URL.\n"); 342 GNUNET_free (birh); 343 return NULL; 344 } 345 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 346 "issue_receipts_with_URL `%s'.\n", 347 birh->url); 348 body = issue_receipt_body_to_json (num_bkp, 349 bkp, 350 year, 351 &birh->charity_sig); 352 eh = DONAU_curl_easy_get_ (birh->url); 353 if ( (NULL == eh) || 354 (GNUNET_OK != 355 TALER_curl_easy_post (&birh->post_ctx, 356 eh, 357 body)) ) 358 { 359 GNUNET_break (0); 360 if (NULL != eh) 361 curl_easy_cleanup (eh); 362 json_decref (body); 363 GNUNET_free (birh->url); 364 GNUNET_free (birh); 365 return NULL; 366 } 367 json_decref (body); 368 birh->job = GNUNET_CURL_job_add2 (ctx, 369 eh, 370 birh->post_ctx.headers, 371 &handle_batch_issue_finished, 372 birh); 373 return birh; 374 } 375 376 377 void 378 DONAU_charity_issue_receipt_cancel ( 379 struct DONAU_BatchIssueReceiptHandle *birh) 380 { 381 if (NULL != birh->job) 382 { 383 GNUNET_CURL_job_cancel (birh->job); 384 birh->job = NULL; 385 } 386 TALER_curl_easy_post_finished (&birh->post_ctx); 387 GNUNET_free (birh->url); 388 GNUNET_free (birh); 389 }